.hashtag { &:empty { &::after { content: "#"; line-height: 0; font-size: 18px; font-weight: 700; } } } .left-sidebar-title { color: var(--color-text-sidebar-heading); opacity: var(--opacity-sidebar-heading); transition: opacity 140ms linear; font-size: inherit; font-weight: var(--font-weight-sidebar-heading); letter-spacing: var(--letter-spacing-sidebar-heading); /* Override heading margin from Bootstrap. */ margin: 0; /* Show an ellipsis on a heading when it won't sit adjacent other icons or controls in the row. */ overflow: hidden; text-overflow: ellipsis; } #left-sidebar .unread_count { user-select: none; } .sidebar-topic-check, .topic-markers-and-unreads { cursor: pointer; } #left-sidebar-navigation-list .filter-icon i { color: var(--color-left-sidebar-navigation-icon); } #stream_filters, #left-sidebar-navigation-list { margin-right: var(--left-sidebar-right-margin); } #streams_inline_icon, .streams_filter_icon { color: var(--color-left-sidebar-heads-up-icon); border-radius: 3px; &:hover { color: var(--color-left-sidebar-heads-up-icon-hover); background-color: var( --background-color-left-sidebar-heads-up-icon-hover ); cursor: pointer; } } .streams_filter_icon.web_public { margin-right: 10px; } .tooltip { max-width: 18em; } .masked_unread_count { font-size: 8px; display: none; /* Masked unreads display as flex when revealed. */ align-items: center; justify-content: center; color: var(--color-unread-counter-muted); width: var(--left-sidebar-single-digit-unread-width); } #stream_filters { overflow: visible; margin-bottom: 5px; padding: 0; font-weight: normal; .topic_search_section { margin: 3px 0; .clear_search_button { grid-area: clear-button; /* Override app-component positioning. */ position: static; padding: 0; } } & li { & .sidebar-topic-name:hover { text-decoration: none; } & ul { margin-left: 0; &.topic-list li { padding: 0; } } } .stream-with-count.hide_unread_counts { .masked_unread_count { display: flex; } .unread_count { display: none; } } .narrow-filter > .bottom_left_row:hover > .stream-with-count.hide_unread_counts { .masked_unread_count { display: none; } .unread_count { display: inline; } } .stream-expanded { .channel-new-topic-button { display: flex; } .subscription_block .sidebar-menu-icon { display: flex; color: var(--color-vdots-visible); } .stream-with-count.hide_unread_counts { .masked_unread_count { display: none; } .unread_count { display: inline; } } } .has-unmuted-unreads.hide_unread_counts { .masked_unread_count { display: none; } .unread_count { display: inline; } } .inactive_stream:not(.active-filter) { opacity: 0.5; } .toggle_stream_mute { margin-right: 3px; opacity: 0.5; &:hover { opacity: 1; } } } .left-sidebar-navigation-area { & li a { &:hover { text-decoration: none; } } } #left_sidebar_scroll_container { outline: none; overflow: hidden auto; position: relative; z-index: 0; width: 100%; .direct-messages-container { margin-left: 0; } } #direct-messages-section-header { grid-template-columns: 0 var(--left-sidebar-header-icon-toggle-width) 0 minmax(0, 1fr) minmax(0, max-content) minmax(0, max-content) var( --left-sidebar-vdots-width ) 0; grid-template-rows: var(--line-height-sidebar-row-prominent); cursor: pointer; white-space: nowrap; border-radius: 4px; /* Prevent hover styles set on other rows when zoomed in. */ &:not(.zoom-in):hover { background-color: var(--color-background-hover-narrow-filter); box-shadow: inset 0 0 0 1px var(--color-shadow-sidebar-row-hover); .left-sidebar-title, .sidebar-heading-icon { opacity: var(--opacity-sidebar-heading-hover); } } #toggle-direct-messages-section-icon { grid-area: starting-anchor-element; /* Horizontally center the icon in its allotted grid area. */ justify-self: center; } .left-sidebar-title { grid-area: row-content; } .heading-markers-and-unreads { grid-area: markers-and-unreads; display: flex; gap: 5px; align-items: center; /* Extra margin for unreads. */ margin-right: var(--left-sidebar-unread-offset); &:has(.unread_count:empty) { margin-right: 0; } } #compose-new-direct-message, #show-all-direct-messages { color: var(--color-left-sidebar-heads-up-icon); display: none; align-items: center; justify-content: center; text-decoration: none; margin: 2px 0; border-radius: 3px; grid-row: 1 / 1; &:hover { color: var(--color-left-sidebar-heads-up-icon-hover); background-color: var( --background-color-left-sidebar-heads-up-icon-hover ); } @media (hover: none) { display: flex; } } &.hover-over-dm-section, &.zoom-in, &:hover { #compose-new-direct-message, #show-all-direct-messages { display: flex; } } } .direct-messages-container { /* Properly offset all the grid rows in the DM section. */ margin-right: var(--left-sidebar-right-margin); & ul.dm-list { list-style-type: none; font-weight: 400; margin-left: 0; margin-bottom: 0; line-height: var(--line-height-sidebar-row-prominent); & li.dm-list-item { & a { text-decoration: none; color: inherit; } .zulip-icon { color: var(--color-left-sidebar-dm-partners-icon); } } & li#show-more-direct-messages { color: var(--color-text-sidebar-action-heading); font-size: var(--font-size-sidebar-action-heading); font-weight: var(--font-weight-sidebar-action-heading); letter-spacing: var(--letter-spacing-sidebar-action-heading); font-variant: var(--font-variant-sidebar-action-heading); text-transform: var(--text-transform-sidebar-action-heading); cursor: pointer; /* The 'more conversations' line has no icons, so vertically align the text with the unread count, when one appears there. */ align-items: baseline; &:hover { background-color: var( --color-background-sidebar-action-heading-hover ); .dm-name { color: var(--color-text-sidebar-action-heading-hover); } } .unread_count { margin-top: 2px; } } } } .zulip-icon-heading-triangle-right { transition: opacity 140ms linear, rotate 140ms linear; } .zulip-icon-heading-triangle-right.rotate-icon-down { rotate: 90deg; } .zulip-icon-heading-triangle-right.rotate-icon-right { rotate: 0deg; } #toggle-direct-messages-section-icon, #toggle-top-left-navigation-area-icon { color: var(--color-text-sidebar-heading); opacity: var(--opacity-sidebar-heading-icon); &:focus { outline: 0; } /* This renders an outline when the caret is reached with the keyboard, although that is not at present easily accomplished. */ &:focus-visible { outline: 2px solid var(--color-outline-focus); } } .active-direct-messages-section { background-color: var(--color-background-active-narrow-filter); #direct-messages-section-header { border-radius: 4px 4px 0 0; } #direct-messages-list { border-radius: 0 0 4px 4px; } } .top_left_row, .bottom_left_row { /* Ensure a border radius on any interactive state that might show a highlight. */ border-radius: 4px; &:hover, &:has(.left_sidebar_menu_icon_visible) { background-color: var(--color-background-hover-narrow-filter); box-shadow: inset 0 0 0 1px var(--color-shadow-sidebar-row-hover); .sidebar-topic-check { background-color: var( --color-background-opaque-hover-narrow-filter ); } } &:has(.sidebar-topic-action-heading):hover { background-color: var(--color-background-sidebar-action-heading-hover); } &.active-filter, &.active-sub-filter { &:has(.left_sidebar_menu_icon_visible) { background-color: var(--color-background-active-narrow-filter); } } } #stream_filters .narrow-filter.highlighted_stream { &.active-filter > .bottom_left_row { background-color: var(--color-background-hover-narrow-filter); } &.active-filter .topic-list .bottom_left_row { background-color: var(--color-background-active-narrow-filter); } .bottom_left_row:not(.active-sub-filter) { background-color: var(--color-background-hover-narrow-filter); } } #login-link-container, #subscribe-to-more-streams { text-decoration: none; margin: 5px auto var(--left-sidebar-bottom-scrolling-buffer) var(--left-sidebar-toggle-width-offset); .subscribe-more-icon { min-width: 19px; text-align: center; &::before { padding-right: 3px; } } } #login-link-container { color: var(--color-text-url); display: flex; align-items: center; &:hover { cursor: pointer; color: var(--color-text-url-hover); &.login_button .login-text { color: var(--color-text-url-hover); text-decoration: underline; } } } ul.filters { list-style-type: none; margin-left: 0; line-height: var(--line-height-sidebar-row-prominent); .sidebar-topic-name, .left-sidebar-navigation-label-container { color: var(--color-text-sidebar-row); &:hover { /* Push back against a:hover color in dark_theme.css */ color: var(--color-text-sidebar-row) !important; } &:focus { text-decoration: none; } } & hr { margin-top: 10px; margin-bottom: 10px; } .has-only-muted-unreads { .unread_count { opacity: var(--opacity-left-sidebar-muted); } &:hover .unread_count { opacity: var(--opacity-left-sidebar-muted-hover); } } .has-only-muted-mentions { .unread_mention_info { opacity: var(--opacity-left-sidebar-muted); } &:hover .unread_mention_info { opacity: var(--opacity-left-sidebar-muted-hover); } } /* This is a noop in the current design, because unread counts for muted streams have the same opacity, but the logic is here to be explicit and because the design may change in the future. */ .more_topic_unreads_muted_only .unread_count { opacity: var(--opacity-left-sidebar-muted); } .zulip-icon-follow { opacity: 0.5; &:hover { opacity: 1; color: var(--color-left-sidebar-follow-icon-hover); } } & li.muted_topic { .sidebar-topic-check, .sidebar-topic-name, .unread_count { opacity: var(--opacity-left-sidebar-muted); } &:hover { .sidebar-topic-check, .sidebar-topic-name, .unread_count { opacity: var(--opacity-left-sidebar-muted-hover); } } } & li.out_of_home_view { .stream-privacy, .stream-name, .channel-new-topic-button, .unread_count, .masked_unread_count, .sidebar-menu-icon { opacity: var(--opacity-left-sidebar-muted); } &:hover { .stream-privacy, .stream-name, .channel-new-topic-button, .unread_count, .masked_unread_count, .sidebar-menu-icon { opacity: var(--opacity-left-sidebar-muted-hover); } } .has-unmuted-unreads { .unread_count { opacity: 1; } } & li.unmuted_or_followed_topic { color: var(--color-unmuted-or-followed-topic-list-item); .unread_count { opacity: 1; } } } } li.active-filter, li.active-sub-filter { font-weight: 600 !important; position: relative; border-radius: 4px; color: var(--color-text-active-narrow-filter); background-color: var(--color-background-active-narrow-filter); &:hover { background-color: var(--color-background-active-narrow-filter); .sidebar-topic-check { /* This variable is only set and used in dark mode. */ background-color: var( --color-background-opaque-hover-active-narrow-filter ); } } .sidebar-topic-check { background-color: var(--color-background-active-narrow-filter); } .sidebar-topic-name-inner { color: var(--color-text-active-narrow-filter); } } #stream_filters .narrow-filter.active-filter { .topic-list .filter-topics, > .bottom_left_row { background-color: var(--color-background-active-narrow-filter); border-radius: 4px; &:hover { background-color: var(--color-background-hover-narrow-filter); } } } #left-sidebar-navigation-list-condensed { display: flex; justify-content: center; .left-sidebar-navigation-condensed-item { /* 24px minimum width from Vlad's design. */ flex: 1 0 24px; /* Unset padding from individual top_left items */ padding: 0; border-radius: 4px; /* Set a positioning context for the unread dot. */ position: relative; .unread_count { position: absolute; } /* Show the same styles when each item is hovered or, via the keyboard, the `` element within receives focus. */ &:hover, &:focus-within { background: var(--color-background-navigation-item-hover); .unread_count { /* 6px at 12px/1em */ top: -0.5em; right: -0.5em; background: var(--color-background-unread-counter-no-alpha); } } &:not(:hover) .unread_count { top: 2px; right: 2px; width: 6px; height: 6px; font-size: 0; padding: 0; background-color: var(--color-background-unread-counter-dot); } &.top_left_starred_messages .unread_count { display: none; } } .left-sidebar-navigation-icon-container { /* Unset margin from full nav list anchor elements. */ margin: 0; /* Horizontally center icons within their boxes. */ text-align: center; &:focus { /* Unset inherited :focus outline. */ outline: 0; } } .active-filter { /* Don't display a background on condensed icons. */ background: unset; } .filter-icon { display: flex; align-items: center; justify-content: center; /* TODO: Set this 24px height value as a variable. Keep filter-icon height the full height of the box. */ height: 24px; /* Enlarge icons slightly in condensed views. */ font-size: 15px; color: var(--color-left-sidebar-navigation-icon); } } #left-sidebar-navigation-list { margin-bottom: var(--left-sidebar-sections-vertical-gutter); display: grid; line-height: var(--line-height-sidebar-row); /* Explicitly ensure parity with the line-height for the sake of low-resolution screens, whose font-rendering and rounding may cause icons to appear out of alignment. This grid feature should only apply in the expanded-navigation view. */ grid-auto-rows: var(--line-height-sidebar-row); .left-sidebar-navigation-label-container { .left-sidebar-navigation-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } } } /* Don't show unread counts on views... */ .top_left_my_reactions, .top_left_inbox, .top_left_recent_view, .top_left_all_messages { .unread_count { visibility: hidden; } /* ...unless it's the selected home view. */ &.selected-home-view .unread_count { visibility: visible; } } /* Don't show the scheduled messages item... */ li.top_left_scheduled_messages { display: none; /* ...unless there are scheduled messages to show. */ &.show-with-scheduled-messages { /* Use display: grid to preserve the grid layout when visible. */ display: grid; } } .left-sidebar-navigation-label-container { grid-area: row-content; /* The label container itself is also a grid, for laying out the items that are its children. Same template areas, different column widths. */ grid-template-columns: var(--left-sidebar-toggle-width-offset) var( --left-sidebar-icon-column-width ) var(--left-sidebar-icon-content-gap) minmax(0, 1fr) minmax( 0, max-content ) minmax(0, max-content) 0 0; .filter-icon { grid-area: starting-anchor-element; /* Use a flex container to handle icon centering within the grid area. */ display: flex; justify-content: center; } .left-sidebar-navigation-label { grid-area: row-content; padding-right: var(--left-sidebar-before-unread-count-padding); @media screen and (resolution <= 1x) { /* For the sake of low-resolution screens, we'll let the actual label take 1 as a line-height value, and allow grid to handle the alignment. */ line-height: 1; } } .unread_count { grid-area: markers-and-unreads; /* Extra margin for unreads. */ margin-right: var(--left-sidebar-unread-offset); &:empty { margin-right: 0; } } } /* Low-attention unreads have no bounding box, so their counters should be aligned on the same baseline as the navigation label. */ .top_left_starred_messages, .top_left_drafts, .top_left_scheduled_messages { .left-sidebar-navigation-label-container { align-items: baseline; } .left-sidebar-navigation-label { @media screen and (resolution <= 1x) { /* Owing to the baseline alignment in this area, we don't need the low-res line-height adjustment. */ line-height: inherit; } } .filter-icon { align-self: center; } } .top_left_starred_messages { &.hide_starred_message_count { .masked_unread_count { display: flex; grid-area: markers-and-unreads; /* Extra margin for unreads. */ margin-right: var(--left-sidebar-unread-offset); } .unread_count { display: none; } /* When starred message count is 0, we dont want to show unread_count or masked_unread_count. */ .unread_count.hide + .masked_unread_count { display: none; } } } .top_left_starred_messages:hover { &.hide_starred_message_count { .masked_unread_count { display: none; } .unread_count { display: inline; } .unread_count.hide { display: none; } } } .top_left_starred_messages.active-filter { &.hide_starred_message_count { .masked_unread_count { display: none; } .unread_count { display: inline; } .unread_count.hide { display: none; } } } .conversation-partners { grid-area: row-content; overflow: hidden; text-overflow: ellipsis; /* Use an inline grid to provide a modern layout for status emoji in DM rows, while preserving the expected ellipsis behavior on long usernames or large DM groups. The 16px-tall emoji will also align well vertically with the 18px line-height here. */ display: inline-grid; /* Provide a two-column grid, sized to accommodate the content of the conversation list and status emoji, if any. */ grid-template-columns: repeat(2, minmax(0, max-content)); align-items: center; } .conversation-partners .status-emoji { /* Prevent status emoji from colliding with unread counts. */ margin-right: 3px; } /* New .topic-box grid definitions here. */ #views-label-container, .zoom-in #hide-more-direct-messages, .top_left_row, .left-sidebar-navigation-label-container, .dm-box, .subscription_block, .topic-box, .searching-for-more-topics, .topic_search_section { display: grid; align-items: center; /* This general pattern of elements applies to every single row in the left sidebar, to some degree or another. Eventually, these template areas could be applied to all rows, with different `grid-template-column` values applied as needed (and shared as needed). For example, an element with no "starting-offset" sets that area to `0`; so too with other non- existent elements. The offsets themselves are meant to greedily assign all of the available horizontal space to the content area of the row. That space can then be modified or reassigned as needed, without running up against `padding` (which alters the box size) or `margin` (which notoriously bleeds outside of the element it's defined on). */ grid-template-areas: "starting-offset starting-anchor-element icon-content-gap row-content controls markers-and-unreads ending-anchor-element ending-offset"; } .top_left_row { /* We stretch the items on the overall nav row, so there's no unclickable gaps between nav rows. */ align-items: stretch; /* The row grid for the outer .top_left_row is chiefly for lefthand spacing and placing the inner row content and vdots. */ grid-template-columns: 0 0 0 minmax(0, 1fr) 0 0 var(--left-sidebar-vdots-width) 0; .sidebar-menu-icon { grid-area: ending-anchor-element; } } #direct-messages-section-header, #streams_header, #topics_header { display: grid; align-items: center; /* This extends the general pattern of left sidebar rows, but includes a second grid row for placing filter boxes. This pattern keeps the box aligned with the text content in `row-content` and includes the `ending-anchor-element` space. There is currently no filter box on DM rows, but we can prepare for that now by having it share this grid template. Its row and column definitions have the final say about dimensions and placement. */ grid-template-areas: "starting-offset starting-anchor-element icon-content-gap row-content controls markers-and-unreads ending-anchor-element ending-offset" ". . filter-box filter-box filter-box filter-box filter-box . "; } #views-label-container { margin-right: var(--left-sidebar-right-margin); grid-template-columns: 0 var(--left-sidebar-header-icon-toggle-width) 0 minmax(0, 0.5fr) 0 minmax( 0, 1fr ) var(--left-sidebar-vdots-width) 0; grid-template-rows: 28px; cursor: pointer; border-radius: 4px; &:not(.remove-pointer-for-spectator):hover, &:has(.left-sidebar-navigation-menu-icon[aria-expanded="true"]) { background-color: var(--color-background-hover-narrow-filter); box-shadow: inset 0 0 0 1px var(--color-shadow-sidebar-row-hover); .left-sidebar-title, .sidebar-heading-icon { opacity: var(--opacity-sidebar-heading-hover); } } &.showing-expanded-navigation { /* When the expanded navigation is visible, hide the condensed navigation's controls. */ #left-sidebar-navigation-list-condensed, .left-sidebar-navigation-menu-icon { display: none; } /* Give the sidebar title through the end of the markers area, if needed. */ .left-sidebar-title { grid-column: row-content-start / markers-and-unreads-end; } } /* Use a next-sibling combinator (+) to use CSS to show and hide filter rows as needed, based on the narrow. */ &.showing-condensed-navigation { + #left-sidebar-navigation-list { /* In the condensed state, we don't want to generate auto rows, or there will be a footprint where the expanded nav sits. */ grid-auto-rows: unset; /* When the navigation area is condensed, hide all the rows in the full navigation list... */ & .top_left_row { display: none; } /* ...except when there is an active filter in place: that row should still be shown. */ & .top_left_row.active-filter { display: grid; /* In the absence of auto rows in the condensed state, we set an explicit height on the active filter. */ height: var(--line-height-sidebar-row); } } } /* Remove the cursor: pointer property of Views label for the spectators. */ &.remove-pointer-for-spectator { cursor: default; } #toggle-top-left-navigation-area-icon { grid-area: starting-anchor-element; /* Horizontally center the icon in its allotted grid area. */ justify-self: center; } .left-sidebar-title { grid-area: row-content; } #left-sidebar-navigation-list-condensed { margin: 0; grid-area: markers-and-unreads; } .left-sidebar-navigation-menu-icon { grid-area: ending-anchor-element; /* Horizontally center vdots. */ justify-self: stretch; /* Properly size vdots. */ font-size: 17px; /* Occupy same clickable height as other condensed-view icons */ height: 24px; /* Vertically center dots with flexbox. */ display: flex; align-items: center; justify-content: center; margin-right: 2px; border-radius: 3px; color: var(--color-vdots-visible); &:hover { color: var(--color-vdots-hover); background-color: var(--color-background-sidebar-action-hover); } } } .subscription_block { grid-template-columns: var(--left-sidebar-toggle-width-offset) var( --left-sidebar-icon-column-width ) var(--left-sidebar-icon-content-gap) minmax(0, 1fr) minmax( 0, max-content ) minmax(0, max-content) var(--left-sidebar-vdots-width) 0; white-space: nowrap; .stream-privacy { grid-area: starting-anchor-element; display: flex; place-content: center center; .zulip-icon.zulip-icon-globe { /* 12px at 14px/1em */ font-size: 0.8571em; } .zulip-icon.zulip-icon-hashtag { /* 13px at 14px/1em */ font-size: 0.9286em; } .zulip-icon.zulip-icon-lock { /* 13px at 14px/1em */ font-size: 0.9286em; } } .stream-name { grid-area: row-content; color: inherit; &:hover { text-decoration: none; } } } .topic-box, .searching-for-more-topics { grid-template-columns: 0 var(--left-sidebar-icon-column-width) var( --left-sidebar-icon-content-gap ) minmax(0, 1fr) minmax(0, max-content) minmax(0, max-content) var(--left-sidebar-vdots-width) 0; } .searching-for-more-topics { margin-left: var(--left-sidebar-toggle-width-offset); height: var(--line-height-sidebar-row-prominent); } .topic_search_section { grid-template-columns: var(--left-sidebar-toggle-width-offset) 0 0 [filter-box-start] minmax(0, 1fr) minmax(0, max-content) [clear-button-start] var(--left-sidebar-vdots-width) [clear-button-end filter-box-end] 0; } .topic-box .zero_count { display: none; } .sidebar-topic-name, .sidebar-topic-action-heading { cursor: pointer; grid-area: row-content; padding: 0 var(--left-sidebar-before-unread-count-padding) 0 0; /* TODO: Consolidate these styles with conversation partners and stream name once grid rewrite is complete on all sidebar rows. Also: note that these styles will be moot for topic names once we allow for multiline topics. If we hold multiline topics to a certain number of lines, we'll likely need a JavaScript-based solution like Clamp.js to display an ellipsis on the final visible line. */ white-space: nowrap; /* Both `hidden` and `clip` are shown for the sake of older browsers that do not support `clip`. */ overflow-x: hidden; overflow-x: clip; text-overflow: ellipsis; span.sidebar-topic-name-inner { white-space: pre; } &:hover { color: inherit; } &:focus { text-decoration: none; } } .sidebar-topic-action-heading { color: var(--color-text-sidebar-action-heading); font-size: var(--font-size-sidebar-action-heading); font-weight: var(--font-weight-sidebar-action-heading); font-variant: var(--font-variant-sidebar-action-heading); font-feature-settings: var(--font-feature-settings-sidebar-action-heading); text-transform: var(--text-transform-sidebar-action-heading); &:hover { text-decoration: none; /* Push back against a:hover color in dark_theme.css. */ color: var(--color-text-sidebar-action-heading-hover) !important; } } .topic-list-filter { grid-area: filter-box; } #filter-topic-input:placeholder-shown + #clear_search_topic_button { display: none; } .searching-for-more-topics img { height: 16px; grid-area: row-content; } .sidebar-topic-check { grid-area: starting-anchor-element; justify-self: end; /* 15px at 14px/1em */ font-size: 1.0714em; /* Use background to mask part of grouping bracket. */ padding-left: 3px; /* Keep background from affecting rounded corners on .active-sub-filter by reducing the checkbox line-height to match its font size. */ line-height: 1; /* As a grid item, adjust the checkmark's z-index here so that the background color appears above the grouping bracket's bottom line. Its value must less than the z-index set on the #streams_header selector. */ z-index: 1; } .left-sidebar-controls { grid-area: controls; display: grid; /* We won't know in advance how many controls a given row has, but this allows grid to generate as many as needed, sized to a shared icon width. */ grid-auto-columns: var(--left-sidebar-header-icon-width); grid-template-rows: var(--line-height-sidebar-row-prominent); place-content: stretch stretch; margin-right: 1px; } .stream-markers-and-unreads, .topic-markers-and-unreads { grid-area: markers-and-unreads; display: flex; /* Present a uniform space between icons */ gap: 5px; align-items: center; justify-content: center; /* Extra margin for unreads. */ margin-right: var(--left-sidebar-unread-offset); &:has(.unread_count:empty) { margin-right: 0; } .unread_mention_info { /* Unset margin in favor of flex gap. */ margin: 0; } .unread_count { /* Height is set here by the flexbox; this decouples .unread_count from the app-wide definition. */ height: auto; } } .channel-new-topic-button { /* display: flex; is set on visible channels and channel-row hovers. */ display: none; align-items: center; justify-content: center; color: var(--color-left-sidebar-heads-up-icon); margin: 2px 0; border-radius: 3px; &:hover { color: var(--color-left-sidebar-heads-up-icon-hover); background-color: var( --background-color-left-sidebar-heads-up-icon-hover ); } } .narrow-filter:hover { .channel-new-topic-button { display: flex; } } .bottom_left_row .sidebar-menu-icon { grid-area: ending-anchor-element; } .conversation-partners-list, .stream-name { white-space: nowrap; overflow-x: hidden; overflow-x: clip; text-overflow: ellipsis; padding: 0 var(--left-sidebar-before-unread-count-padding) 0 0; } /* All of our left sidebar handlers use absolute positioning. We should fix that. */ .bottom_left_row .sidebar-menu-icon, .top_left_row .sidebar-menu-icon { display: none; cursor: pointer; /* Use a flex container to handle icon centering within the grid area. :hover actually sets the `display: flex`, so it remains hidden otherwise. */ justify-content: center; align-items: center; text-align: center; /* Ensure icons are vertically aligned, in case they appear in a grid definition, like the nav rows, that use a different centering regime for the row. */ align-self: stretch; border-radius: 3px; margin: 2px 2px 2px 1px; /* This helps horizontally align the vdots, given the reduced margin-left above. */ padding-left: 1px; /* Set the icon size, which will be inherited by .zulip-icon */ font-size: 17px; /* If you hover directly over the ellipsis itself, show it in black. */ &:hover { color: var(--color-vdots-hover); } /* Hover does not work for touch-based devices like mobile phones. Hence the icons does not appear, making the user unaware of its presence on such devices. The following media property displays the icon by default for such behaviour. */ @media (hover: none) { display: flex; /* Show dots on touchscreens in a less distracting, lighter shade. */ color: var(--color-vdots-hint); } } /* When you hover over list items, we hover the vdots in light gray. The stream icon should always display when any topic is hovered, which is why it gets a more specific selector here. */ #stream_filters li:hover .stream-sidebar-menu-icon, .top_left_row:hover .sidebar-menu-icon, .bottom_left_row:hover .sidebar-menu-icon, .app-main .column-left .left-sidebar .left_sidebar_menu_icon_visible { /* We push against `display: none` with `display: flex` because the sidebar vdots all expect to be displayed as flex items when visible. Their vertical alignment depends on it, too. */ display: flex; color: var(--color-vdots-visible); &:hover { color: var(--color-vdots-hover); background-color: var(--color-background-sidebar-action-hover); } } /* For topic ellipsis-v(vertical 3 dots) we use a slightly smaller font to show they're "lower" in the hierarchy, which also affects it positioning. */ .topic-sidebar-menu-icon { /* 0.9em, but with a pixel value because the ordinary 17px vdots size is not inherited here, so we can't express an em-value directly. Pixels are easier to reason about with icons, anyway. */ font-size: 15.3px; } ul.topic-list { line-height: var(--line-height-sidebar-row-prominent); list-style-type: none; font-weight: normal; } ul.topic-list.topic-list-has-topics::before { content: " "; display: block; position: absolute; /* 12px at 16px/1em */ top: 0.75em; bottom: 0.75em; left: 9px; border: 1px solid var(--color-topic-indent-border); border-right: 0; border-radius: 9px 0 0 9px; width: 6px; pointer-events: none; } ul.topic-list.topic-list-has-topics::after { content: " "; display: block; position: absolute; /* -14px at 16px/1em */ top: -0.875em; /* 12px at 16px/1em */ bottom: 0.75em; left: 16px; width: 12px; border-bottom: 1px solid var(--color-topic-indent-border); pointer-events: none; } ul.topic-list:has(.show-more-topics)::after { /* When the show all topics control is displayed, extend the bottom bracket. */ width: 18px; } /* The grouping border should not be shown on zoomed-in views. */ .zoom-in .topic-list.topic-list-has-topics::before, .zoom-in .topic-list.topic-list-has-topics::after { border: 0; } li.topic-list-item { position: relative; padding-right: 5px; margin-left: var(--left-sidebar-toggle-width-offset); } .dm-box { grid-template-columns: var(--left-sidebar-toggle-width-offset) [action-heading-start] var( --left-sidebar-icon-column-width ) var(--left-sidebar-icon-content-gap) minmax(0, 1fr) [action-heading-end] 0 minmax( 0, max-content ) var(--left-sidebar-vdots-width) 0; grid-template-rows: [action-heading-start] auto [action-heading-end]; .conversation-partners-icon { grid-area: starting-anchor-element; place-self: center; } .dm-name { grid-area: action-heading; } .user_circle { width: var(--length-user-status-circle); height: var(--length-user-status-circle); @media screen and (resolution <= 1x) { /* User circles appear to sag a bit on low-resolution screens, so this fix targets them without disrupting line-height values that may become important in multi-line DM groups, etc. in the future. */ margin-top: -2.5px; } } .unread_count { grid-area: markers-and-unreads; /* TODO: This is an alternative method for presenting a 16px-tall unread counter, regardless of context. This method could be used app-wide once all of the layout methods for presenting unreads have been modernized. Set the line-height to match the font-size, so that the numerals sit in a 12px box. */ line-height: 12px; /* Use flexbox to vertically center the 12px-high text node within the 16px-high unread box. */ display: flex; align-items: center; /* Extra margin for unreads. */ margin-right: var(--left-sidebar-unread-offset); &:empty { margin-right: 0; } } } /* Since direct-messages-sticky-header also has the `input-append` class accompanying it. The display property of that class will overwrite display: none if we don't have a more specific CSS rule. It will also overwrite `display: none` even if `.zoom-out` properties are declared after the `.input-append` properties since the latter is more specific. */ #direct-messages-sticky-header.zoom-out, .zoom-out { #topics_header { display: none; } .zoom-out-hide { display: none; } } #topics_header { position: sticky; top: 0; z-index: 2; grid-template-columns: [topics-content-area-start] var(--left-sidebar-toggle-width-offset) 0 0 minmax(0, 1fr) 0 max-content 0 var(--left-sidebar-vdots-width) [topics-content-area-end] var(--left-sidebar-right-margin); grid-template-rows: [topics-content-area-start] var(--line-height-sidebar-row-prominent) [topics-content-area-end]; padding-top: var(--left-sidebar-sections-vertical-gutter); color: hsl(0deg 0% 43%); background-color: var(--color-background); .show-all-streams { grid-area: topics-content-area; padding-left: var(--left-sidebar-toggle-width-offset); font-size: var(--font-size-sidebar-action-heading); font-weight: var(--font-weight-sidebar-action-heading); font-variant: var(--font-variant-sidebar-action-heading); text-transform: var(--text-transform-sidebar-action-heading); color: var(--color-text-sidebar-action-heading); text-decoration: none; &:hover { background-color: var( --color-background-sidebar-action-heading-hover ); box-shadow: inset 0 0 0 1px var(--color-shadow-sidebar-row-hover); border-radius: 4px; } } .unread_count { grid-area: markers-and-unreads; /* Extra margin for unreads. */ margin-right: var(--left-sidebar-unread-offset); &:empty { margin-right: 0; } } } #streams_header { grid-template-columns: var(--left-sidebar-toggle-width-offset) 0 0 minmax(0, 1fr) minmax( 0, max-content ) minmax(0, max-content) var(--left-sidebar-vdots-width) 0; /* Keep the stream-search area rows collapsed. */ grid-template-rows: var(--line-height-sidebar-row-prominent) 0 0; cursor: pointer; margin: var(--left-sidebar-sections-vertical-gutter) var(--left-sidebar-right-margin) 3px 0; position: sticky; /* Keep sticky within SimpleBar context. */ top: 0; z-index: 2; background-color: var(--color-background); border-radius: 4px; &.showing-stream-search-section { /* Open up the stream-search rows. The 10px row maintains space with the streams list below. */ grid-template-rows: var(--line-height-sidebar-row-prominent) 28px 10px; /* When the search section is showing, switch off the hover effects on the row. */ &:hover { background-color: var(--color-background); box-shadow: unset; } } .left-sidebar-title { grid-area: row-content; } .heading-markers-and-unreads { grid-area: markers-and-unreads; height: 100%; display: flex; align-items: center; grid-gap: 5px; /* Extra margin for unreads. */ margin-right: var(--left-sidebar-unread-offset); &:has(.unread_count:empty) { margin-right: 0; } } #filter_streams_tooltip { display: none; align-items: center; justify-content: center; grid-row: 1 / 1; margin: 2px 0; @media (hover: none) { display: flex; } } #add_streams_tooltip { grid-row: 1 / 1; margin: 2px 0; } #streams_inline_icon { display: none; align-items: center; justify-content: center; /* Ensure the clickable area grows to the height of the controls grid. */ height: 100%; @media (hover: none) { display: flex; } } &:hover, &.showing-streams-popover { background-color: var(--color-background-opaque-hover-narrow-filter); box-shadow: inset 0 0 0 1px var(--color-shadow-sidebar-row-hover); .left-sidebar-title, .sidebar-heading-icon { opacity: var(--opacity-sidebar-heading-hover); } #filter_streams_tooltip, #streams_inline_icon { display: flex; } } .stream_search_section { grid-area: filter-box; display: flex; justify-content: stretch; /* Set an even line-height for better vertical centering. */ line-height: 20px; white-space: nowrap; } &.hide_unread_counts { .unread_count, .unread_count.hide, .masked_unread_count { display: none; } /* When an empty unread count is hidden, hide the masked unread count, too. */ .unread_count.hide + .masked_unread_count { display: none; } .masked_unread_count { display: flex; } &:hover { .unread_count { display: block; } .hide, .masked_unread_count { display: none; } } } } .stream_search_section, .direct-messages-search-section { .stream-list-filter, .direct-messages-list-filter { /* Use the border-box model so flex can do its thing despite whatever padding and border we specify. */ box-sizing: border-box; flex: 1 0 100%; /* Match the input height exactly with the row height for a perfect fit and better vertical alignment. */ height: 28px; /* Pad the entire clear-button area, so that input text does not bleed into there. */ padding-right: 30px; } .clear_search_button { /* Use the border-box model so flex can do its thing despite whatever padding and border we specify. */ box-sizing: border-box; /* Clear inherited positioning. */ position: static; /* We're going to use flexbox, not positioning, to get the clear button over top of the input. This -30px margin accomplishes that, in tandem with the 30px width of this element, which is shared with the ending anchor element in left sidebar header rows. */ width: 30px; margin-left: -30px; /* Flexbox respects z-index; this just ensures the button remains over top of the input. */ z-index: 1; /* Make the button itself a flex container, so we can perfectly center the X icon. */ display: flex; justify-content: center; align-items: center; /* Flexbox will pull the element open to make a generous clickable area. */ padding: 0; } .direct-messages-list-filter:placeholder-shown + #clear-direct-messages-search-button { display: none; } } /* Prepare an adjusted grid for the logged-out state, one that reassigns the vdots space to markers and controls. */ .spectator-view #streams_header { grid-template-columns: var(--left-sidebar-toggle-width-offset) 0 0 minmax(0, 1fr) 0 minmax(var(--left-sidebar-vdots-width), max-content) 0 0; margin-right: var(--left-sidebar-right-margin); /* With markers and controls now sized the same as the ordinary vdots area (but allowed to grow, care of `minmax(30px, max-content)`, should additional logged-out icons be added in the future), let's center the icon in that area, just like vdots would be. */ .heading-markers-and-unreads { justify-content: center; } } .streams_subheader { /* 14px at 16px/1em */ font-size: 0.875em; font-weight: normal; /* 16px line-height at 0.8em (11.2px at 14px legacy em) */ line-height: 1.4286em; letter-spacing: 0.04em; padding-left: var(--left-sidebar-toggle-width-offset); cursor: pointer; text-align: center; margin-right: var(--left-sidebar-right-margin); & .streams-subheader-wrapper { display: flex; flex-direction: row; width: 100%; left: 0.5em; right: 0.5em; color: var(--color-text-sidebar-base); } & .streams-subheader-wrapper::before, .streams-subheader-wrapper::after { content: " "; flex: 1 1; vertical-align: middle; margin: auto; border-top: 1px solid var(--color-border-sidebar-subheader); } & .streams-subheader-wrapper::before { margin-right: 0.2em; } & .streams-subheader-wrapper::after { margin-left: 0.2em; } .streams-subheader-name { opacity: 0.4; } } .stream-list-filter { white-space: nowrap; text-overflow: ellipsis; overflow: hidden; } .zero_count { visibility: hidden; } .zero-topic-unreads.show-more-topics .topic-box { margin-right: 30px; } .zoom-in { .narrow-filter > .bottom_left_row { position: sticky; top: calc( var(--left-sidebar-sections-vertical-gutter) + var(--line-height-sidebar-row-prominent) ); z-index: 2; padding-bottom: 1px; background-color: var(--color-background); &:hover { /* Prevent hover styles set on other rows. */ box-shadow: none; background-color: var(--color-background); } } #streams_header, #subscribe-to-more-streams, .show-more-topics { display: none; } &.direct-messages-container ul.dm-list { margin-bottom: var(--left-sidebar-bottom-scrolling-buffer); } #hide-more-direct-messages { grid-template-columns: inherit; grid-column: 1 / -1; line-height: var(--line-height-sidebar-row); text-decoration: none; color: inherit; .hide-more-direct-messages-text { color: var(--color-text-sidebar-action-heading); font-size: var(--font-size-sidebar-action-heading); font-weight: var(--font-weight-sidebar-action-heading); font-variant: var(--font-variant-sidebar-action-heading); text-transform: var(--text-transform-sidebar-action-heading); grid-area: row-content; } &:hover { background-color: var( --color-background-sidebar-action-heading-hover ); box-shadow: inset 0 0 0 1px var(--color-shadow-sidebar-row-hover); border-radius: 4px; } } .direct-messages-search-section { display: flex; grid-column: row-content / markers-and-unreads; margin-top: 5px; margin-bottom: 5px; } .zoom-in-hide { display: none; } .zoom-in-sticky { position: sticky; top: 0; z-index: 1; } }