Merge branch 'worktree-agent-ab53265ffbd837742'
This commit is contained in:
@@ -4,10 +4,11 @@
|
||||
*
|
||||
* small 64px, title-large title (label-medium subtitle), 4px from the edges, the title 16px
|
||||
* from the start or straight after the navigation icon
|
||||
* center the same, the title centred
|
||||
* center the same, the title centred — a three-column grid, so it shrinks against whatever is
|
||||
* beside it rather than ellipsising underneath it
|
||||
* medium 112px (136px with a subtitle): the 64px row of icons over a headline-medium title
|
||||
* large 120px (152px with a subtitle): a display-small title
|
||||
* search the row holds a search bar
|
||||
* search the row holds a search bar, full width to 312px and then half of what is left
|
||||
*
|
||||
* The container is the surface, and surface-container once content scrolls under it. A medium or
|
||||
* large bar collapses into the small one without script moving anything: the bar is sticky at a
|
||||
@@ -118,21 +119,44 @@
|
||||
letter-spacing: var(--md-sys-typescale-label-md-tracking);
|
||||
}
|
||||
|
||||
/* Centred: the headline spans the row and centres its text between whatever is at either end. */
|
||||
[data-app-bar][data-variant="center"] [data-app-bar-headline] {
|
||||
position: absolute;
|
||||
inset-inline: 3.5rem;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
pointer-events: none;
|
||||
/* Centred: a three-column row — leading, headline, trailing — so the headline is centred in the
|
||||
window and shrinks against whatever is actually beside it. M3 allows two trailing icon
|
||||
buttons, which a fixed inset for one would have let a long title ellipsise underneath. */
|
||||
[data-app-bar][data-variant="center"] [data-app-bar-row] {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
}
|
||||
|
||||
/* The search bar fills the row. */
|
||||
[data-app-bar][data-variant="center"] [data-app-bar-leading] {
|
||||
grid-column: 1;
|
||||
justify-self: start;
|
||||
}
|
||||
|
||||
[data-app-bar][data-variant="center"] [data-app-bar-headline] {
|
||||
grid-column: 2;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
justify-self: center;
|
||||
padding-inline: 0.75rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
[data-app-bar][data-variant="center"] [data-app-bar-trailing] {
|
||||
grid-column: 3;
|
||||
justify-self: end;
|
||||
margin-inline-start: 0;
|
||||
}
|
||||
|
||||
/* The search bar fills the space between the leading and trailing elements until it is 312dp
|
||||
wide, and then grows into only half of what is left
|
||||
(docs/reference/m3/components-navigation-selection-inputs.md § Top app bar). The percentage
|
||||
is of the row rather than of that space, which CSS cannot name without measuring it; below
|
||||
312px the cap never binds, so a phone still gets the whole row. */
|
||||
[data-app-bar-search] {
|
||||
min-width: 0;
|
||||
max-width: calc(19.5rem + (100% - 19.5rem) / 2);
|
||||
flex: 1 1 0%;
|
||||
margin-inline: auto;
|
||||
padding-inline: 0.25rem;
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,9 @@
|
||||
* rail in the layout rather than one that opens over a scrim; 75rem (1200px) is `large`, where the
|
||||
* rail starts expanded instead of collapsed. The bar's own item layout is a *container* query at
|
||||
* the same 37.5rem, so a bar in a narrow column lays out by its own width.
|
||||
*
|
||||
* Everything here is in `@layer components` except the last block, which restyles a FAB the
|
||||
* application put in the rail: that one has to beat a utility, and a layer never does.
|
||||
*/
|
||||
|
||||
@custom-variant rail-collapsed {
|
||||
@@ -56,6 +59,14 @@
|
||||
@slot;
|
||||
}
|
||||
|
||||
/* Below `medium` a 220–360px rail would be most of the screen, so a rail whose width is the
|
||||
visitor's choice is held collapsed there whatever they chose. */
|
||||
@media (width < 37.5rem) {
|
||||
&:where([data-navigation-rail='collapsible'], [data-navigation-rail='collapsible'] *) {
|
||||
@slot;
|
||||
}
|
||||
}
|
||||
|
||||
@media (width < 52.5rem) {
|
||||
&:where([data-navigation-rail='adaptive']:not([data-open]), [data-navigation-rail='adaptive']:not([data-open]) *) {
|
||||
@slot;
|
||||
@@ -157,14 +168,16 @@
|
||||
min-width: max-content;
|
||||
}
|
||||
|
||||
/* The label stays label-medium: NavigationBarTokens.LabelTextFont is the bar's only label
|
||||
token, and Compose's ShortNavigationBarItem passes it for both icon positions. (The
|
||||
*rail's* horizontal item is label-large — NavigationRailHorizontalItemTokens — which is
|
||||
a different component.) */
|
||||
[data-navigation-bar-item] [data-navigation-pill] {
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
flex-direction: row;
|
||||
height: 2.5rem;
|
||||
padding-inline: 1rem;
|
||||
font: var(--md-sys-typescale-label-lg);
|
||||
letter-spacing: var(--md-sys-typescale-label-lg-tracking);
|
||||
}
|
||||
|
||||
[data-navigation-bar-item] [data-navigation-label] {
|
||||
@@ -210,16 +223,27 @@
|
||||
[data-navigation-rail='adaptive'] {
|
||||
width: 0;
|
||||
}
|
||||
|
||||
/* "Compact → don't use a standard rail, use a navigation bar." A `collapsible` rail takes
|
||||
the width the visitor chose, which on a 360px screen would be two-thirds of it, so it
|
||||
is floored at the collapsed 96px. `collapsed` and `expanded` mean what they say and are
|
||||
left alone: wrap one in a `medium:` element if it must not show on a phone. */
|
||||
[data-navigation-rail='collapsible'] {
|
||||
width: 6rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* `--material-banner` is the height of a bar pinned across the top of the window — what
|
||||
<x-app-shell>'s `banner` slot holds, if the application made it sticky. The rail sticks
|
||||
under it rather than behind it; 0 without one. */
|
||||
[data-navigation-rail-panel] {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
top: var(--material-banner, 0px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-height: 100dvh;
|
||||
max-height: calc(100dvh - var(--material-banner, 0px));
|
||||
/* Clip, not hide: no scroll container, so the destinations below can still scroll and
|
||||
nothing sticky breaks. What only an expanded rail draws — a label, the brand — is drawn
|
||||
at once when the rail expands, while the width is still growing; the clip keeps it
|
||||
@@ -233,6 +257,18 @@
|
||||
background-color var(--md-sys-motion-effects-default-duration) var(--md-sys-motion-effects-default);
|
||||
}
|
||||
|
||||
/* M3's two optional container treatments: "optional vertical divider separates rail from
|
||||
content, placed on the content-adjacent edge" — also its answer to a page scrolling under a
|
||||
fixed rail — and "container fill can be turned off (transparent) as long as items keep ≥3:1
|
||||
contrast". Neither applies to a rail open over a scrim, which is a surface over the page. */
|
||||
[data-navigation-rail][data-divider]:not([data-open]) > [data-navigation-rail-panel] {
|
||||
border-inline-end: 1px solid var(--md-sys-color-outline-variant);
|
||||
}
|
||||
|
||||
[data-navigation-rail][data-fill='false']:not([data-open]) > [data-navigation-rail-panel] {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/* Open: expanded over a scrim, in surface-container with a large corner at its inner edge. */
|
||||
[data-navigation-rail][data-open] > [data-navigation-rail-panel] {
|
||||
position: fixed;
|
||||
@@ -535,6 +571,14 @@
|
||||
transition: opacity var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast);
|
||||
}
|
||||
|
||||
/* A navigation item's state layer is on-secondary-container, active or not: the only
|
||||
state-layer tokens M3 states for one are NavigationRailColorTokens', which give that colour
|
||||
to all six. The rail follows them two blocks below; the bar follows them here, over its
|
||||
secondary-container pill. */
|
||||
[data-navigation-bar-item] :is([data-navigation-indicator], [data-navigation-pill])::before {
|
||||
background-color: var(--md-sys-color-on-secondary-container);
|
||||
}
|
||||
|
||||
@container (width < 37.5rem) {
|
||||
[data-navigation-bar-item] [data-navigation-pill]::before {
|
||||
display: none;
|
||||
@@ -601,3 +645,45 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* A FAB in the rail's header rests at elevation 0, not the 3 a standalone FAB has
|
||||
* (docs/reference/m3/components-navigation-selection-inputs.md § Navigation rail: "when nested
|
||||
* within another component, such as the navigation rail, the FAB's resting elevation should be
|
||||
* level 0"). Unlayered on purpose: the FAB draws its shadow with a utility, and a rule in any
|
||||
* layer loses to a utility — the same reason toolbar.css gives for its vibrant recolouring. */
|
||||
[data-navigation-rail-header] [data-fab],
|
||||
[data-navigation-rail-header] [data-fab]:hover {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* "Rail transitions from collapsed to expanded should animate the FAB into and out of an extended
|
||||
* FAB" — so the header takes one <x-fab label icon> and morphs it, rather than swapping two by
|
||||
* `display`, which showed the collapsed one at once while the rail's width was still springing.
|
||||
* The label's width springs on the rail's own spatial spring; with the gap closed and the extended
|
||||
* FAB's own minimum width off, `aspect-ratio` squares what is left against the height its size
|
||||
* already sets — 56, 80 or 96px, the three FAB sizes — so no number is repeated here. The label
|
||||
* stays in the accessibility tree at both widths, so the FAB keeps its name. Unlayered for the
|
||||
* same reason as the rule above: `gap-2` and `min-w-20` are utilities, and a layer would lose to
|
||||
* them. The cap is there because a transition needs a length to travel to; a rail FAB's label is a
|
||||
* word or two. */
|
||||
[data-navigation-rail-header] [data-fab] > span {
|
||||
max-width: 16rem;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
transition:
|
||||
max-width var(--md-sys-motion-spatial-default-duration) var(--md-sys-motion-spatial-default),
|
||||
opacity var(--md-sys-motion-effects-default-duration) var(--md-sys-motion-effects-default);
|
||||
|
||||
@variant rail-collapsed {
|
||||
max-width: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
[data-navigation-rail-header] [data-fab] {
|
||||
@variant rail-collapsed {
|
||||
min-width: 0;
|
||||
aspect-ratio: 1;
|
||||
gap: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,20 +6,38 @@
|
||||
*
|
||||
* The active indicator is drawn in every tab and shown under the chosen one, so it is right before
|
||||
* Alpine starts and after a morph: 3px with rounded top corners under the content of a primary tab
|
||||
* (at least 24px wide), 2px across the whole of a secondary one. When the choice changes, a view
|
||||
* transition moves it from the old tab to the new (resources/js/tabs.js), as M3's slides.
|
||||
* (at least 24px wide, inset 2px at each side), 2px across the whole of a secondary one. When the
|
||||
* choice changes, a view transition moves it from the old tab to the new (resources/js/tabs.js),
|
||||
* as M3's slides.
|
||||
*
|
||||
* The bar scrolls sideways, so it clips whatever leaves it — including the focus ring, which the
|
||||
* rest of the package draws 3px thick 2px outside the element (tokens/state.css). `--tabs-ring`
|
||||
* is the room that takes: the bar carries it as padding on every side, and the state layer and the
|
||||
* indicator reach back out into it, so the divider still sits against the tabs and the indicator
|
||||
* still sits on the divider. That makes the bar 10px taller than M3's 48px tab.
|
||||
*/
|
||||
|
||||
@layer components {
|
||||
[data-tabs-bar] {
|
||||
--tabs-ring: 5px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
overscroll-behavior-x: contain;
|
||||
padding: var(--tabs-ring);
|
||||
scroll-padding-inline: var(--tabs-ring);
|
||||
border-bottom: 1px solid var(--md-sys-color-outline-variant);
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
/* "Offset the first scrollable tab 52dp from the leading edge so it's clear that more content
|
||||
is available" (docs/reference/m3/components-navigation-selection-inputs.md § Tabs). Logical,
|
||||
so a right-to-left page mirrors it. */
|
||||
[data-tabs-bar][data-scrollable] {
|
||||
padding-inline-start: 3.25rem;
|
||||
scroll-padding-inline-start: 3.25rem;
|
||||
}
|
||||
|
||||
/* A section nav's links sit in list items that share the width. */
|
||||
[data-tabs-bar] > li {
|
||||
flex: 1 1 0%;
|
||||
@@ -47,6 +65,7 @@
|
||||
transition: color var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast);
|
||||
}
|
||||
|
||||
[data-tabs-bar][data-scrollable] > li,
|
||||
[data-tabs-bar][data-scrollable] [data-tab] {
|
||||
flex: none;
|
||||
}
|
||||
@@ -55,10 +74,12 @@
|
||||
height: 4rem;
|
||||
}
|
||||
|
||||
/* The state layer covers the tab and the ring's room around it, so the wash meets the divider. */
|
||||
[data-tab]::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
inset-block: calc(-1 * var(--tabs-ring));
|
||||
inset-inline: 0;
|
||||
pointer-events: none;
|
||||
transition: background-color var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast);
|
||||
}
|
||||
@@ -81,10 +102,12 @@
|
||||
[data-tab]:focus-visible {
|
||||
color: var(--md-sys-color-on-surface);
|
||||
outline: 3px solid var(--md-sys-color-secondary);
|
||||
outline-offset: -3px;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
[data-tab][aria-selected="true"] {
|
||||
/* A link-based bar marks the page with aria-current, not aria-selected; both are the chosen
|
||||
tab, and both take the variant's active colour. */
|
||||
[data-tab]:is([aria-selected="true"], [aria-current="page"]) {
|
||||
color: var(--md-sys-color-primary);
|
||||
}
|
||||
|
||||
@@ -116,10 +139,12 @@
|
||||
gap: 0.125rem;
|
||||
}
|
||||
|
||||
/* Primary indicators are inset 2dp at each side; both variants sit on the divider, which the
|
||||
ring's room has pushed below the tab. */
|
||||
[data-tab-indicator] {
|
||||
position: absolute;
|
||||
inset-inline: 0;
|
||||
bottom: 0;
|
||||
inset-inline: 2px;
|
||||
bottom: calc(-1 * var(--tabs-ring));
|
||||
height: 3px;
|
||||
border-radius: 3px 3px 0 0;
|
||||
background-color: var(--md-sys-color-primary);
|
||||
@@ -134,6 +159,7 @@
|
||||
}
|
||||
|
||||
[data-tabs-bar][data-variant="secondary"] [data-tab-indicator] {
|
||||
inset-inline: 0;
|
||||
height: 2px;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
@@ -7,8 +7,12 @@
|
||||
* floating a 64px pill in surface-container (or primary-container, `vibrant`) at elevation 3,
|
||||
* 8px at its ends and 4px between controls; `vertical` stands it on end
|
||||
*
|
||||
* Unlayered on purpose: a vibrant toolbar recolours the icon buttons inside it, which draw their
|
||||
* ink as utilities, and a rule in any layer loses to a utility.
|
||||
* Placed over the page, a horizontal toolbar keeps 16dp from the window's edge and a vertical one
|
||||
* 24dp, which is M3's minimum for each.
|
||||
*
|
||||
* Unlayered on purpose: a toolbar recolours the icon buttons inside it — primary in a standard
|
||||
* one, on-primary-container in a vibrant one — and they draw their ink as utilities, which a rule
|
||||
* in any layer loses to.
|
||||
*/
|
||||
|
||||
[data-toolbar] {
|
||||
@@ -60,6 +64,13 @@
|
||||
color: var(--md-sys-color-on-surface);
|
||||
}
|
||||
|
||||
/* M3's colour list for a standard toolbar ends "Standard button (Primary)", as the vibrant list
|
||||
ends "Standard button (On primary container)" — the row above. An icon button carries its own
|
||||
ink as a utility, so the container's `color` never reaches it and this rule has to. */
|
||||
[data-toolbar]:not([data-vibrant]) [data-icon-button]:not([aria-pressed="true"]) {
|
||||
color: var(--md-sys-color-primary);
|
||||
}
|
||||
|
||||
/* A floating toolbar and its FAB, side by side (or stacked, vertical). */
|
||||
[data-toolbar-group] {
|
||||
display: inline-flex;
|
||||
@@ -71,10 +82,17 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Placed over the page: centred above the bottom edge, or centred against the end edge. */
|
||||
/* Placed over the page: centred above the bottom edge, or centred against the end edge.
|
||||
*
|
||||
* A toolbar at the bottom shares the screen region with a navigation bar, so it clears the one
|
||||
* <x-app-shell> publishes as --material-bottom-bar, exactly as the FAB and the snackbar do —
|
||||
* `max()`, not a sum, because that height already swallows the bottom safe area; without a bar
|
||||
* the safe area alone applies. A *docked* toolbar and a navigation bar must never be on screen
|
||||
* together at all (docs/reference/m3/components-navigation-selection-inputs.md § Toolbars); the
|
||||
* offset is the backstop, not a licence. */
|
||||
[data-toolbar-place="bottom"] {
|
||||
position: fixed;
|
||||
bottom: calc(1rem + var(--material-safe-bottom, env(safe-area-inset-bottom)));
|
||||
bottom: calc(max(var(--material-bottom-bar, 0px), var(--material-safe-bottom, env(safe-area-inset-bottom))) + 1rem);
|
||||
left: 50%;
|
||||
z-index: 30;
|
||||
translate: -50% 0;
|
||||
@@ -88,9 +106,17 @@
|
||||
translate: 0 -50%;
|
||||
}
|
||||
|
||||
/* "Horizontal toolbars should have a minimum 16dp margin from the edge of the window. Vertical
|
||||
toolbars should have a minimum 24dp margin"
|
||||
(docs/reference/m3/components-navigation-selection-inputs.md § Toolbars). */
|
||||
[data-toolbar][data-vertical][data-toolbar-place="end"],
|
||||
[data-toolbar-group][data-vertical][data-toolbar-place="end"] {
|
||||
inset-inline-end: calc(1.5rem + var(--material-safe-right, env(safe-area-inset-right)));
|
||||
}
|
||||
|
||||
[data-toolbar][data-variant="docked"][data-toolbar-place="bottom"] {
|
||||
inset-inline: 0;
|
||||
bottom: 0;
|
||||
bottom: var(--material-bottom-bar, 0px);
|
||||
left: 0;
|
||||
translate: none;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user