discourse/app/assets/stylesheets/common/base/modal.scss
Kris 9d53cb3a43
UX: minor expanding textarea fixes (#40910)
There were some minor regressions to the expanding textarea after it was
adjusted in an earlier commit... this adds a new css variable to adjust
padding as needed, fixes some `@value` references and adjusts specific
styles on the /tags page. Also added a modifier class to the child
textarea so it can more easily be separated from normal textareas.

before:
<img width="700" alt="image"
src="https://github.com/user-attachments/assets/c7cb28ce-a626-49e2-90d1-7bbee166b79b"
/>


after:
<img width="700" alt="image"
src="https://github.com/user-attachments/assets/0393c9ba-9ea9-43f1-87ba-5c34076cd1c1"
/>
2026-06-15 15:23:25 -04:00

362 lines
6.3 KiB
SCSS
Vendored

@use "lib/viewport";
html.modal-open {
// prevents bg scrolling when modal is open
overflow: hidden;
scrollbar-gutter: stable;
}
html.keyboard-visible.mobile-view {
.d-modal {
max-height: calc(var(--composer-vh, 1dvh) * 100);
height: calc(var(--composer-vh, 1dvh) * 100);
bottom: 0;
}
.d-modal__container {
max-height: calc(var(--composer-vh, 1dvh) * 100);
height: calc(var(--composer-vh, 1dvh) * 100);
}
}
html:not(.keyboard-visible).mobile-device {
.d-modal__container {
padding-bottom: env(safe-area-inset-bottom, 1rem);
}
}
.d-modal {
pointer-events: none; // Allow clicks through wrappers so they hit the adjacent backdrop element
display: flex;
align-items: center;
width: 100%;
height: 100%;
position: fixed;
top: 0;
z-index: z("modal", "content");
overflow: auto;
@include viewport.until(sm) {
align-items: flex-end;
}
@include viewport.from(sm) {
--modal-max-width: 600px;
--modal-width: 30em; // set in ems to scale with user font-size
--modal-min-width: 400px;
}
&__container {
display: flex;
flex-direction: column;
pointer-events: auto;
box-sizing: border-box;
border-radius: var(--d-border-radius);
margin: 0 auto;
background-color: var(--secondary);
box-shadow: var(--shadow-modal);
max-height: var(--modal-max-height, 80vh); // unset, optional theme utility
@include viewport.until(sm) {
// this is a hack to prevent issues on safari with transforms
position: fixed;
width: 100%;
max-width: 100%;
max-height: calc(var(--composer-vh, 1dvh) * 85);
&.is-entering {
@include d-animation(modal-enter-mobile, 250ms, ease);
}
&.is-exiting {
@include d-animation(modal-exit-mobile, 250ms, ease);
}
}
@include viewport.from(sm) {
max-width: var(--modal-max-width);
min-width: var(--modal-min-width);
min-height: var(--modal-min-height); // unset, optional theme utility
.d-modal.--large & {
max-width: 800px;
}
.d-modal.--max & {
max-width: 90vw;
}
.d-modal.has-search & {
height: 80vh; // fixed height so the modal doesnt jump when the search results appear/filter
}
&.is-entering {
@include d-animation(
modal-enter-desktop,
250ms,
cubic-bezier(0.4, 0, 0.2, 1)
);
}
&.is-exiting {
@include d-animation(
modal-exit-desktop,
250ms,
cubic-bezier(0.4, 0, 0.2, 1)
);
}
}
}
&__header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem;
border-bottom: 1px solid var(--content-border-color);
&:not(.--has-primary-action) {
padding: 0.5rem 0.5rem 0.5rem 1.5rem; // offset for close button padding
}
.modal-close {
margin-left: auto;
align-self: start;
.d-icon {
font-size: var(--font-up-2);
color: var(--primary-high);
}
&:focus-visible {
.d-icon {
color: var(--primary);
}
}
}
}
.d-modal__primary-action,
.d-modal__dismiss-action {
.btn {
text-transform: capitalize;
}
}
&__title-text {
font-size: var(--font-up-1-rem);
line-height: var(--line-height-medium);
@include viewport.from(sm) {
font-size: var(--font-up-3);
}
}
&__title-text,
&__subtitle-text {
margin: 0;
}
&__body {
overflow-y: auto;
padding: 1rem 1.5rem;
box-sizing: border-box;
&.empty {
display: none;
}
input {
width: auto;
&[type="text"],
&[type="password"],
&[type="number"] {
width: 100%;
}
}
textarea {
width: 100%;
&:not(.--expandable) {
height: 80px;
}
}
.password-confirmation {
display: none;
}
section.field {
padding: 0.25em 0;
margin-bottom: 5px;
&.with-items {
display: flex;
align-items: flex-start;
}
.field-item {
display: inline-block;
margin-right: 10px;
}
}
pre code {
white-space: pre-wrap;
max-width: 100%;
}
}
&__footer {
display: flex;
flex-wrap: wrap;
align-items: center;
padding: 1rem 1.5rem;
border-top: 1px solid var(--content-border-color);
gap: 0.5rem;
@include viewport.until(sm) {
&__footer {
margin-top: auto;
.--stacked & {
flex-direction: column;
align-items: stretch;
}
}
}
}
&__backdrop {
user-select: none;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: z("modal", "overlay");
background-color: #000;
animation: fade 0.3s forwards;
&.fade {
opacity: 0;
}
@media (prefers-reduced-motion) {
animation-duration: 0s;
}
&.is-exiting {
@include d-animation(
backdrop-fade-out,
250ms,
cubic-bezier(0.4, 0, 0.2, 1)
);
}
}
#modal-alert {
padding-left: 1.5rem;
}
.ios-device & {
&__footer {
margin-top: auto;
}
}
// fixes modal placement on Android when keyboard is visible
html.keyboard-visible:not(.ios-device) & {
height: calc(100% - env(keyboard-inset-height));
.d-modal__container {
max-height: 100%;
min-height: 100%;
height: 100%;
}
}
pre code {
white-space: pre-wrap;
max-width: var(--modal-max-width);
}
@include viewport.from(sm) {
.category-chooser {
width: 50%;
}
}
.input-hint-text {
margin-left: 0.5em;
color: var(--secondary-high);
}
}
// fade in
@keyframes fade {
from {
opacity: 0;
}
to {
opacity: 0.6;
}
}
// Modal animations
@keyframes modal-enter-desktop {
from {
transform: scale(0.7);
opacity: 0;
}
to {
transform: scale(1);
opacity: 1;
}
}
@keyframes modal-exit-desktop {
from {
transform: scale(1);
opacity: 1;
}
to {
transform: scale(0.7);
opacity: 0;
}
}
@keyframes modal-enter-mobile {
from {
transform: translateY(100%);
}
to {
transform: translateY(0);
}
}
@keyframes modal-exit-mobile {
from {
transform: translateY(0);
}
to {
transform: translateY(100%);
}
}
@keyframes backdrop-fade-out {
from {
opacity: 0.6;
}
to {
opacity: 0;
}
}