feat(design): dialogs open by purpose and keep their commit row pinned (E28 S3f1)

Modal takes a required initialFocus (field / close / action), a field that
loads late takes the focus while it still rests on the close button, and
ModalFooter portals a dialog's commit row under the scrolling body from any
depth. The header and footer fades follow the body's scroll timeline, and the
viewport's interactive-widget=resizes-content keeps the footer above an
on-screen keyboard.
This commit is contained in:
2026-09-27 01:33:37 +02:00
parent 4c63d6660d
commit 3ef2c38eb4
34 changed files with 505 additions and 113 deletions
+36
View File
@@ -184,6 +184,26 @@
}
}
}
/* A panel's fixed header and footer (C4: a dialog's). The scroller between them is the
`panel-scroller`; the panel names its timeline for both edges with `panel-scroll-scope`, since they
sit outside it. The header's fade shows once content has slid under it (the first 1rem of scroll);
the footer's while there is more below (it fades out over the last 1rem). A body that fits has no
scroll range, its timeline is inactive and neither fade draws — nor does an engine without scroll
timelines: no fade rather than one over a resting first or last line. */
@utility panel-scroll-scope {
timeline-scope: --panel-scroll;
}
@utility panel-scroller {
scroll-timeline: --panel-scroll block;
}
@keyframes fade-edge-out {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@utility fade-below-panel {
&::after {
content: "";
@@ -193,6 +213,14 @@
top: 100%;
height: 1rem;
background: linear-gradient(to bottom, var(--surface), transparent);
opacity: 0;
}
@supports (timeline-scope: none) {
&::after {
animation: fade-edge-in linear both;
animation-timeline: --panel-scroll;
animation-range: 0 1rem;
}
}
}
@utility fade-above-panel {
@@ -204,6 +232,14 @@
bottom: 100%;
height: 1rem;
background: linear-gradient(to top, var(--surface), transparent);
opacity: 0;
}
@supports (timeline-scope: none) {
&::before {
animation: fade-edge-out linear both;
animation-timeline: --panel-scroll;
animation-range: calc(100% - 1rem) 100%;
}
}
}
/* B9: the clipped ends of an overflowing chip row. */