#compose_buttons { text-align: right; display: flex; column-gap: 4px; flex-direction: row; /* With precisely controlled line-heights in this area, stretch will both center and maintain uniform heights between the reply button and the new-message button. */ align-items: stretch; .compose_mobile_button { /* Keep the new message button sized to match adjacent buttons. */ min-width: inherit; padding: 3px 10px; border-radius: 4px; outline: none; /* This is ugly, but necessary to use the text + for the compose button. An icon would likely be a better choice here. 1.2em is 16.8px at 14px em. */ font-size: 1.2em; /* 1.2em is 16.8px at 14px em; this maintains the 20px em-equivalent compose line height, but at a 16.8px em. */ line-height: 1.19em; font-weight: 400; color: var(--color-text-default); background-color: var(--color-background-compose-new-message-button); border: 1px solid var(--color-border-compose-new-message-button); &:hover { background-color: var( --color-background-compose-new-message-button-hover ); border-color: var(--color-border-compose-new-message-button-hover); } &:active { background-color: var( --color-background-compose-new-message-button-active ); border-color: var(--color-border-compose-new-message-button-hover); } } .reply_button_container { display: flex; flex-grow: 1; /* Adjust flexbox default `min-width` to allow smaller container sizes. */ min-width: 0; /* Button-like styling */ border-radius: 4px; background-color: var( --color-compose-collapsed-reply-button-area-background ); border: 1px solid var(--color-compose-collapsed-reply-button-area-border); &:hover, &:focus { background-color: var( --color-compose-collapsed-reply-button-area-background-interactive ); border-color: var( --color-compose-collapsed-reply-button-area-border-interactive ); } #left_bar_compose_reply_button_big, #new_conversation_button { /* Keep the new message button sized to match adjacent buttons. */ font-size: inherit; min-width: inherit; padding: 3px 10px; outline: none; border: none; color: var(--color-text-default); background: var(--color-compose-embedded-button-background); border-radius: 3px; line-height: var(--line-height-compose-buttons); } .compose-reply-button-wrapper { flex-grow: 1; display: flex; overflow: hidden; } #left_bar_compose_reply_button_big { width: 100%; text-align: left; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } #new_conversation_button { /* Remove the `padding` to prevent margin from affecting parent height. */ padding: 0 10px; min-width: inherit; margin: 1px; flex-shrink: 0; align-self: stretch; color: var(--color-compose-embedded-button-text-color); &:hover { background-color: var( --color-compose-embedded-button-background-hover ); color: var(--color-compose-embedded-button-text-color-hover); } } } .mobile_button_container { @media (width >= $sm_min) { display: none; } } #new_conversation_button, .new_direct_message_button_container { flex-shrink: 0; line-height: var(--line-height-compose-buttons); @media (width < $sm_min) { /* Override inline style injected by jQuery hide() */ display: none !important; } } .new_direct_message_button_container { display: flex; } #new_direct_message_button { /* Keep the new message button sized to match adjacent buttons. */ align-self: stretch; font-size: inherit; min-width: inherit; line-height: var(--line-height-compose-buttons); padding: 3px 10px; border-radius: 4px; outline: none; color: var(--color-text-default); background-color: var(--color-background-compose-new-message-button); border: 1px solid var(--color-border-compose-new-message-button); &:hover { background-color: var( --color-background-compose-new-message-button-hover ); border-color: var(--color-border-compose-new-message-button-hover); } &:active { background-color: var( --color-background-compose-new-message-button-active ); border-color: var(--color-border-compose-new-message-button-hover); } } } /* Main geometry for this element is in zulip.css */ #compose-content { background-color: var(--color-compose-box-background); padding: 4px 4px 6px; border: 1px solid var(--color-border-compose-content); border-radius: 9px 9px 0 0; box-shadow: 0 0 0 hsl(236deg 11% 28%); height: 100%; display: flex; flex-flow: column; box-sizing: border-box; &:hover { .composebox-buttons > button { opacity: 1; } } } .message_comp { display: none; padding: 0 7px 0 0; #compose_banners { max-height: min(25vh, 240px); overflow-y: auto; /* Align to compose controls; that's 112px width, plus 4px of grid gap for 116px here. */ margin-right: calc(var(--compose-send-controls-width) + 4px); } } .compose_table { height: 100%; display: flex; flex-flow: column; #compose-recipient { &.compose-recipient-direct-selected { #compose_select_recipient_widget_wrapper { /* As the DM-pill area expands, we want to hold the DM picker to the first line. */ align-self: flex-start; } #compose_select_recipient_widget { border-radius: 4px !important; /* This height is necessary only for the DM picker, so that it does not stretch open taller with multiple rows of user pills. */ height: var(--compose-recipient-box-min-height); } .topic-marker-container { /* As the DM-pill area expands, we likewise want to keep the topic marker aligned with the DM picker. */ align-self: flex-start; } } .topic-marker-container { /* Ensure the marker ( < ) stays centered vertically with the dropdown, even when adjacent stacking pills in, e.g., a group DM. */ align-self: center; display: flex; align-items: center; /* Ensure horizontal centering, too. */ justify-content: center; /* Disallow shrinking or growth, which can cause little layout shifts with pills. */ flex: 0 0 auto; height: var(--compose-recipient-box-min-height); .conversation-arrow { font-size: 1.1429em; /* 16px / 14px em */ /* Fix the line-height to the font size to maintain a circle that scales. */ line-height: 1; border-radius: 50%; padding: 2px; margin: 0 3px; color: var(--color-compose-chevron-arrow); text-decoration: none; cursor: default; transition: 0.2s ease-in-out; transition-property: background, color; &.narrow_to_compose_recipients { background: var( --color-narrow-to-compose-recipients-background ); color: var(--color-narrow-to-compose-recipients); cursor: pointer; &:hover { background: var( --color-narrow-to-compose-recipients-background-hover ); color: var(--color-narrow-to-compose-recipients-hover); } } } } } #compose-direct-recipient { flex-grow: 1; display: grid; grid-template-columns: 1fr; align-items: stretch; } .message_header { background: none; background-color: hsl(0deg 0% 92%); border: none; border-radius: 0; box-shadow: none !important; } .messagebox { box-shadow: none !important; } } #send_message_form { margin: 0; height: 100%; .messagebox { /* normally 5px 14px; pull in the right and bottom a bit */ cursor: default; flex: 1; padding: 0; background: none; box-shadow: none; border: none; height: 100%; display: grid; /* Vlad's design calls for 122px for the send column at its widest; 112px accounts for 6px of gap and 4px outside padding. */ grid-template: minmax(0, 1fr) var(--compose-formatting-buttons-row-height) / minmax(0, 1fr) var(--compose-send-controls-width); grid-template-areas: "message-content-container message-send-controls-container" "message-formatting-controls-container message-send-controls-container"; gap: 0 4px; } .message_content { margin-right: 0; } } #message-content-container { grid-area: message-content-container; display: grid; grid-template: minmax(0, 1fr) / minmax(0, 1fr) var( --composebox-buttons-width ); grid-template-areas: "message-content composebox-buttons"; border-radius: 4px; border: 1px solid var(--color-message-content-container-border); &:has(.new_message_textarea:focus) { border-color: var(--color-message-content-container-border-focus); } &:has(.new_message_textarea.invalid), &:has(.new_message_textarea.invalid:focus) { border-color: var(--color-invalid-input-border); box-shadow: var(--invalid-input-box-shadow); } } #message-content-container:has(.new_message_textarea.textarea-over-limit), .edit-content-container:has(.message_edit_content.textarea-over-limit) { box-shadow: 0 0 0 1pt var(--color-message-content-container-border-over-limit); } #message-content-container:has( .new_message_textarea.textarea-approaching-limit ), .edit-content-container:has(.message_edit_content.textarea-approaching-limit) { box-shadow: 0 0 0 1pt var(--color-message-content-container-border-approaching-limit); } #message-content-container:has(.new_message_textarea.textarea-over-limit.flash), #message-content-container:has( .new_message_textarea.textarea-approaching-limit.flash ), .edit-content-container:has(.message_edit_content.textarea-over-limit.flash), .edit-content-container:has( .message_edit_content.textarea-approaching-limit.flash ) { /* This should align with the timing in compose_validate.validate_message_length. */ animation: message-limit-flash 0.5s ease-in-out 1; } #message-content-container .composebox-buttons { grid-area: composebox-buttons; /* z-index is needed to avoid flickering of cursor and the button when hovering it in preview mode. */ z-index: 1; height: max-content; button { width: 24px; /* Override any UA stylesheet padding, such as that added by mobile Safari. */ padding: 0; border: none; aspect-ratio: 1 / 1; background-color: var(--color-composebox-button-background); color: var(--color-composebox-button); border-radius: 3px; display: flex; align-items: center; justify-content: center; opacity: 0; transition: opacity 0.4s ease-in, color 0.1s ease-in, background-color 0.1s ease-in; &:hover { background-color: var(--color-composebox-button-background-hover); color: var(--color-composebox-button-hover); } &:focus { outline: 0; } &:focus-visible { outline: 2px solid var(--color-outline-focus); } } .collapse-composebox-button, .maximize-composebox-button { display: none; } } #compose-textarea, #preview-message-area-container { grid-area: message-content; } #compose-textarea, #preview_message_area { margin-right: calc(var(--composebox-buttons-width) * -1); padding-right: var(--composebox-buttons-width); background-color: var(--color-compose-message-content-background); color: var(--color-text-default); &.textarea-over-limit { background-color: var( --color-compose-message-content-background-over-limit ); } &.textarea-approaching-limit { background-color: var( --color-compose-message-content-background-approaching-limit ); } } .surround-formatting-buttons-row { /* This is to extend it under the formatting buttons row, so that any border / box-shadow styles applied to it surround the formatting buttons row as well. */ padding-bottom: var(--compose-formatting-buttons-row-height); /* The extra 1px of margin-bottom is to ensure the 1px of border-bottom shows below the formatting buttons row. */ margin-bottom: calc( (var(--compose-formatting-buttons-row-height) + 1px) * -1 ); textarea { /* Flatten the bottom edge of the textarea to merge with the flat top edge of the buttons row */ border-radius: 3px 3px 0 0; } } #preview-message-area-container { /* Keep preview container invisible outside of preview mode. */ display: none; } #message-send-controls-container { grid-area: message-send-controls-container; /* A columnar flex does a nice job here holding Drafts to the top of the container, and the send button to the bottom--even as the compose box expands or contracts. */ display: flex; flex-direction: column; /* With a columnar flex, this ensures that send controls occupy the same space as the adjacent textbox. */ justify-content: space-between; /* We add 6px of margin to the grid-gap of 6px, for 12px of space between the Send button and the textarea. */ margin-left: 6px; @media (width < $sm_min), ((width >= $sm_min) and (width < $mc_min)) { margin-left: 0; } } #message-formatting-controls-container { grid-area: message-formatting-controls-container; border-radius: 0 0 3px 3px; background-color: var(--color-message-formatting-controls-container); /* margin on either side to let the border of .message-content-container show through. */ margin: 0 1px; } .compose-scrolling-buttons-container { display: grid; /* The scroller buttons are set to a 48px width that does not scale; the idea being that that's a generous target area, and that it would be better not to consume more space at larger font sizes under narrower screen sizes. */ grid-template-columns: [scroller-backward-start buttons-start] 48px [scroller-backward-end] minmax( 0, 1fr ) [scroller-forward-start] 48px [scroller-forward-end buttons-end]; grid-template-rows: auto; } .compose-scrollable-buttons { grid-area: buttons; overflow-x: scroll; scrollbar-width: none; scroll-behavior: smooth; } .formatting-control-scroller-button { z-index: 5; display: flex; align-items: center; border-radius: 0; border: 0; color: var(--color-compose-scroll-icon); background: transparent; opacity: 0; pointer-events: none; transition: opacity 0.2s; &:focus { outline: 0; } } .formatting-scroller-backward { grid-area: scroller-backward; justify-content: flex-start; border-radius: 0 0 0 3px; background: var(--gradient-compose-scroll-backward); &:hover { background: var(--gradient-compose-scroll-backward-hover); } &:active { background: var(--gradient-compose-scroll-backward-active); .scroller-backward-icon { /* Subtly shift arrow in scroll direction. */ margin-left: -2px; } } } .formatting-scroller-forward { grid-area: scroller-forward; justify-content: flex-end; border-radius: 0 0 3px; background: var(--gradient-compose-scroll-forward); &:hover { background: var(--gradient-compose-scroll-forward-hover); } &:active { background: var(--gradient-compose-scroll-forward-active); .scroller-forward-icon { /* Subtly shift arrow in scroll direction. */ margin-right: -2px; } } } .can-scroll-backward .formatting-scroller-backward, .can-scroll-forward .formatting-scroller-forward { opacity: 1; pointer-events: all; } /* Hide the scroller buttons when someone has reached the formatting bar via Tab. Scroller-button state silently updates despite their being hidden, and scroller buttons reappear once focus has moved out of the formatting bar. */ .compose-scrolling-buttons-container:has( .compose-scrollable-buttons:focus-within ) { .formatting-scroller-backward, .formatting-scroller-forward { opacity: 0; pointer-events: none; } } .message-limit-indicator:not(:empty) { font-size: 0.8571em; /* 12px at 14px/em */ color: var(--color-limit-indicator); width: max-content; /* Keep the limit indicator just above and aligned with the send button. `:not(:empty)` prevents the padding and margin-top from affecting layout in the controls area when no indicator is present. */ margin-top: auto; padding: 3px 3px 3px 0; &.textarea-over-limit { color: var(--color-limit-indicator-over-limit); font-weight: bold; } } #compose { position: fixed; bottom: 0; left: 0; z-index: 4; } #compose-container { display: flex; flex-direction: column; width: 100%; margin: auto; } #compose_top { display: flex; justify-content: space-between; align-items: flex-start; /* Matched to 4px grid-gap on .messagebox grid. */ padding-bottom: 4px; /* Align to compose controls; that's 112px width, plus 4px of grid gap for 116px here. */ margin-right: calc(var(--compose-send-controls-width) + 4px); } #compose_close { position: absolute; top: 0; right: 0; color: var(--color-compose-send-control-button); background: transparent; /* 12.5px at 14px em */ font-size: 0.8928em; font-weight: normal; /* 3px 7px at 12.5px font size at 14px em */ line-height: 1.6em; opacity: 0.7; border: 0; /* 3px 7px at 12.5px font size at 14px em */ padding: 0.24em 0.56em; border-radius: 8px; vertical-align: unset; text-shadow: none; &:hover { opacity: 1; background: var(--color-compose-embedded-button-background-hover); color: var(--color-compose-send-control-button-interactive); } &:active { background-color: var( --color-compose-embedded-button-background-interactive ); } &:focus:not(:focus-visible) { outline: none; } &:focus-visible { outline-color: var(--color-compose-focus-ring); } } .main-view-banner { /* This is same spatial value as the 4px of padding around the edge of the compose box. */ margin-bottom: 4px; border-radius: 5px; border: 1px solid; display: flex; align-items: center; line-height: 1.2em; /* 18px at 15px/1em. */ .above_compose_banner_action_link { color: var(--color-main-view-banner-action-link); } .main-view-banner-elements-wrapper { display: flex; align-items: center; /* Allow this flex container to grow or shrink to fit the outer container. */ flex: 1 1 auto; /* Allow items to wrap; this supports an intrinsic layout for banner text and buttons, which will always occupy the space available, without our having to fiddle with tons of media queries. */ flex-wrap: wrap; } & .banner_content { /* Override Bootstrap when .banner_content is a paragraph element. */ margin: 0; /* 5px right padding + 10px left-margin of the neighbouring button will match the left padding */ padding: 8px 5px 8px 15px; /* The banner text uses a flex-basis of 150px, which is roughly the width at which banner text lines are still comfortably readable. Still, it can grow and shrink as needed. */ flex: 1 1 150px; & .banner_message { /* Override Bootstrap when .banner_content contains an inner .banner_message paragraph. */ margin: 0; } } .main-view-banner-action-button, .upload_banner_cancel_button { border: none; border-radius: 4px; padding: 5px 10px; font-weight: 600; margin-top: 4.5px; margin-bottom: 4.5px; /* Buttons take a minimum height for when their text fits on a single line. 2.1333em is 32px at 15px/1em. */ min-height: 2.1333em; /* When we're larger than large mobile scales ($ml_min), flex the button to its max-content, i.e., all its text on a single line. But do not grow in order to avoid awkward, oversized buttons within the flex group. */ flex: 0 1 max-content; /* Use this margin-left hack to keep the button to the righthand side of the banner. */ margin-left: auto; @media (width < $ml_min) { /* When we're smaller than large mobile scales, we allow the button to grow, so that it can span the full width of narrow, mobile-scale banners as it wraps onto a second line. We also allow the button to shrink, so that, for example, the text can wrap on the schedule-message button that appears when undoing a scheduled message. */ flex: 1 1 max-content; /* Use a 10px left margin to keep the button away from the edge of the banner box to match the banner text; we need this only at small scales, when the button grows to the full width of the flex container. */ margin-left: 10px; } /* Extra margin to ensure the layout is identical when there is no close button. */ &.right_edge { margin-right: 10px; } } .main-view-banner-action-button { /* Establish a uniform top and bottom space around the button, which also works with the space around the message text. */ margin-top: 8px; margin-bottom: 8px; /* Make as tall as two lines of banner message text, which have a line-height of 18px, but no more. 2.4em is two 1.2em lines in the banner area. */ max-height: 2.4em; /* Keep to the top of the box, but stretch taller based on how the box is flexing. */ min-height: 0; align-self: stretch; } .main-view-banner-close-button { text-decoration: none; /* Set same top and bottom margin as action buttons. */ margin: 8px 0; padding: 0 8px; /* Let the close button's box stretch, but no larger than the height of the banner box when the action button achieves its full height (margin, padding, and height), which keeps the X vertically centered with it. */ align-self: stretch; /* 2.4em is two 1.2em lines in the banner area. */ max-height: 2.4em; /* Display as flexbox to better control the X icon's position. This creates an anonymous flexbox item out of the ::before content where the icon sits. */ display: flex; align-items: center; } .banner_content + .main-view-banner-close-button { /* When there's no action button, set the max height for the typical height of the box when it contains only banner message text. This will keep the action button aligned with the first or only line of text. 2.2667 is 34px at 15px/1em; */ max-height: 2.2667em; } &.success { background-color: var(--color-background-success-main-view-banner); border: 1px solid var(--color-border-success-main-view-banner); color: var(--color-success-main-view-banner); .main-view-banner-close-button { color: var(--color-success-main-view-banner-close-button); &:hover { color: var(--color-success-main-view-banner-close-button-hover); } &:active { color: var( --color-success-main-view-banner-close-button-active ); } } .main-view-banner-action-button { background-color: var( --color-background-success-main-view-banner-action-button ); color: inherit; &:hover { background-color: var( --color-background-success-main-view-banner-action-button-hover ); } &:active { background-color: var( --color-background-success-main-view-banner-action-button-active ); } } } /* warning and warning-style classes have the same CSS; this is since the warning class has some associated javascript which we do not want for some of the banners, for which we use the warning-style class. */ &.warning, &.warning-style { background-color: var(--color-background-warning-main-view-banner); border-color: var(--color-border-warning-main-view-banner); color: var(--color-warning-main-view-banner); .main-view-banner-close-button { color: var(--color-warning-main-view-banner-close-button); &:hover { color: var(--color-warning-main-view-banner-close-button-hover); } &:active { color: var( --color-warning-main-view-banner-close-button-active ); } } .main-view-banner-action-button { background-color: var( --color-background-warning-main-view-banner-action-button ); color: var(--color-warning-main-view-banner-action-button); &:hover { background-color: var( --color-background-warning-main-view-banner-action-button-hover ); } &:active { background-color: var( --color-background-warning-main-view-banner-action-button-active ); } } } &.error { background-color: var(--color-background-error-main-view-banner); border-color: var(--color-border-error-main-view-banner); color: var(--color-error-main-view-banner); .main-view-banner-close-button { color: var(--color-error-main-view-banner-close-button); &:hover { color: var(--color-error-main-view-banner-close-button-hover); } &:active { color: var(--color-error-main-view-banner-close-button-active); } } .main-view-banner-action-button { background-color: var( --color-background-error-main-view-banner-action-button ); color: var(--color-error-main-view-banner-action-button); &:hover { background-color: var( --color-background-error-main-view-banner-action-button-hover ); } &:active { background-color: var( --color-background-error-main-view-banner-action-button-active ); } } } &.info { position: relative; background-color: var(--color-background-info-main-view-banner); border-color: var(--color-border-info-main-view-banner); color: var(--color-info-main-view-banner); .main-view-banner-close-button { color: var(--color-info-main-view-banner-close-button); &:hover { color: var(--color-info-main-view-banner-close-button-hover); } &:active { color: var(--color-info-main-view-banner-close-button-active); } } .main-view-banner-action-button, .upload_banner_cancel_button { background-color: var( --color-background-info-main-view-banner-action-button ); color: var(--color-info-main-view-banner-action-button); &:hover { background-color: var( --color-background-info-main-view-banner-action-button-hover ); } &:active { background-color: var( --color-background-info-main-view-banner-action-button-active ); } } } } .upload_banner { overflow: hidden; &.hidden { display: none; } .moving_bar { position: absolute; width: 0; /* The progress updates seem to come every second or so, so this is the smoothest it can probably get. */ transition: width 1s ease-in-out; /* stylelint-disable-line plugin/no-low-performance-animation-properties */ background: var(--color-background-main-view-banner-moving-bar); top: 0; bottom: 0; } /* Keep these elements visible above the .moving_bar element on file uploads. */ .upload_msg, .main-view-banner-close-button, .upload_banner_cancel_button { z-index: 1; position: relative; } } .composition-area { position: relative; flex: 1; } @keyframes message-limit-flash { 0% { box-shadow: none; } 100% { box-shadow: 0 0 0 1pt var(--color-message-content-container-border-over-limit); } } textarea.new_message_textarea { display: table-cell; padding: 5px 5px 0; height: 1.5em; max-height: 22em; margin: 0; resize: none !important; border-radius: 3px 3px 0 0; border: none; scrollbar-width: thin; scrollbar-color: hsl(0deg 0% 50%) transparent; box-shadow: none; &:focus { outline: 0; } &:read-only, &:disabled { background-color: hsl(0deg 0% 93%); } } #message-content-container, #compose_recipient_box { color: var(--color-text-default); } #compose_recipient_box { display: grid; /* When displaying #topic-not-mandatory-placeholder, we let it occupy the entire topic box. Otherwise, we set and preserve named areas for the ordinary topic text and the clear-topic button. */ grid-template-columns: [topic-box-start topic-start] minmax(0, 1fr) [topic-end topic-clear-button-start] auto [topic-clear-button-end topic-box-end]; grid-template-rows: [topic-box-start topic-start topic-clear-button-start] auto [topic-clear-button-end topic-end topic-box-end]; align-content: center; align-items: stretch; flex: 1 1 0; border: 1px solid var(--color-compose-recipient-box-border-color); border-radius: 4px; background: var(--color-compose-recipient-box-background-color); &:hover { border-color: var(--color-compose-recipient-box-hover); } /* Give the recipient box, a `