Components read the device's safe-area insets straight from env(), which a browser test cannot fake and an application drawing its own status strip cannot extend. Every inset the package reads, in its CSS and its views, is now var(--material-safe-top|bottom|left|right, env(safe-area-inset-…)): unchanged while the variables are unset. The app shell's --material-bottom-bar also adds var(--material-bottom-extra, 0px), so an application that docks something on the phone's navigation bar (an offline banner) sets its height once and the snackbar, a fab button and the page's bottom padding clear it. A guard test fails on any env(safe-area-inset-*) outside such a variable, and AppShellTest's assertion on the bar height's class follows the new value. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RHoXZSHc8gGpZjFmA5fPc2
189 lines
6.2 KiB
CSS
189 lines
6.2 KiB
CSS
/*
|
|
* M3 Expressive's top app bars (AppBarTokens, AppBarSmallTokens, AppBarMediumFlexibleTokens,
|
|
* AppBarLargeFlexibleTokens, androidx Compose Material 3, Apache-2.0).
|
|
*
|
|
* 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
|
|
* 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
|
|
*
|
|
* 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
|
|
* negative top, so it scrolls up until only 64px of it remain, while its row of icons is sticky at
|
|
* 0 inside it and stays put — the big title scrolls away under the row. Script only says when that
|
|
* has happened (resources/js/app-bar.js), so the small title can fade in and the colour change.
|
|
*/
|
|
|
|
@layer components {
|
|
[data-app-bar] {
|
|
--app-bar-row: 4rem;
|
|
--app-bar-height: var(--app-bar-row);
|
|
|
|
z-index: 20;
|
|
display: block;
|
|
min-height: var(--app-bar-height);
|
|
padding-top: var(--material-safe-top, env(safe-area-inset-top));
|
|
background-color: var(--md-sys-color-surface);
|
|
color: var(--md-sys-color-on-surface);
|
|
transition: background-color var(--md-sys-motion-effects-default-duration) var(--md-sys-motion-effects-default);
|
|
}
|
|
|
|
[data-app-bar][data-variant="medium"] {
|
|
--app-bar-height: 7rem;
|
|
}
|
|
|
|
[data-app-bar][data-variant="medium"][data-subtitled] {
|
|
--app-bar-height: 8.5rem;
|
|
}
|
|
|
|
[data-app-bar][data-variant="large"] {
|
|
--app-bar-height: 7.5rem;
|
|
}
|
|
|
|
[data-app-bar][data-variant="large"][data-subtitled] {
|
|
--app-bar-height: 9.5rem;
|
|
}
|
|
|
|
[data-app-bar][data-sticky] {
|
|
position: sticky;
|
|
top: 0;
|
|
}
|
|
|
|
/* `--app-bar-measured` is the bar's real height (a title can wrap), set by app-bar.js. */
|
|
[data-app-bar][data-sticky]:is([data-variant="medium"], [data-variant="large"]) {
|
|
top: calc(var(--app-bar-row) - var(--app-bar-measured, var(--app-bar-height)));
|
|
}
|
|
|
|
[data-app-bar][data-scrolled] {
|
|
background-color: var(--md-sys-color-surface-container);
|
|
}
|
|
|
|
[data-app-bar-row] {
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 1;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0;
|
|
min-height: var(--app-bar-row);
|
|
padding-inline: 0.25rem;
|
|
background-color: inherit;
|
|
}
|
|
|
|
[data-app-bar-leading],
|
|
[data-app-bar-trailing] {
|
|
display: flex;
|
|
flex: none;
|
|
align-items: center;
|
|
color: var(--md-sys-color-on-surface-variant);
|
|
}
|
|
|
|
[data-app-bar-leading] {
|
|
color: var(--md-sys-color-on-surface);
|
|
}
|
|
|
|
[data-app-bar-trailing] {
|
|
margin-inline-start: auto;
|
|
}
|
|
|
|
[data-app-bar-headline] {
|
|
display: flex;
|
|
min-width: 0;
|
|
flex: 1 1 0%;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
padding-inline: 0.75rem;
|
|
}
|
|
|
|
[data-app-bar-row]:has([data-app-bar-leading]) [data-app-bar-headline] {
|
|
padding-inline-start: 0.25rem;
|
|
}
|
|
|
|
[data-app-bar-title] {
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
font: var(--md-sys-typescale-title-lg);
|
|
letter-spacing: var(--md-sys-typescale-title-lg-tracking);
|
|
}
|
|
|
|
[data-app-bar-subtitle] {
|
|
overflow: hidden;
|
|
color: var(--md-sys-color-on-surface-variant);
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
font: var(--md-sys-typescale-label-md);
|
|
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;
|
|
}
|
|
|
|
/* The search bar fills the row. */
|
|
[data-app-bar-search] {
|
|
min-width: 0;
|
|
flex: 1 1 0%;
|
|
padding-inline: 0.25rem;
|
|
}
|
|
|
|
/* Medium and large: the row's title is the collapsed one, shown once the big one has gone. */
|
|
[data-app-bar]:is([data-variant="medium"], [data-variant="large"]) [data-app-bar-row] [data-app-bar-headline] {
|
|
opacity: 0;
|
|
transition: opacity var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast);
|
|
}
|
|
|
|
[data-app-bar][data-collapsed] [data-app-bar-row] [data-app-bar-headline] {
|
|
opacity: 1;
|
|
}
|
|
|
|
/* Heights from the tokens: the title (and subtitle) lines and the space under them fill what the
|
|
row leaves — 36 + 12px medium, 36 + 20 + 16px with a subtitle; 44 + 12px large, 44 + 24 + 20px. */
|
|
[data-app-bar-expanded] {
|
|
display: flex;
|
|
min-height: calc(var(--app-bar-height) - var(--app-bar-row));
|
|
flex-direction: column;
|
|
justify-content: flex-end;
|
|
padding-inline: 1rem;
|
|
padding-bottom: 0.75rem;
|
|
}
|
|
|
|
[data-app-bar][data-subtitled] [data-app-bar-expanded] {
|
|
padding-bottom: 1rem;
|
|
}
|
|
|
|
[data-app-bar][data-variant="large"][data-subtitled] [data-app-bar-expanded] {
|
|
padding-bottom: 1.25rem;
|
|
}
|
|
|
|
[data-app-bar-expanded] [data-app-bar-title] {
|
|
white-space: normal;
|
|
font: var(--md-sys-typescale-headline-md);
|
|
letter-spacing: var(--md-sys-typescale-headline-md-tracking);
|
|
}
|
|
|
|
[data-app-bar-expanded] [data-app-bar-subtitle] {
|
|
font: var(--md-sys-typescale-label-lg);
|
|
letter-spacing: var(--md-sys-typescale-label-lg-tracking);
|
|
}
|
|
|
|
[data-app-bar][data-variant="large"] [data-app-bar-expanded] [data-app-bar-title] {
|
|
font: var(--md-sys-typescale-display-sm);
|
|
letter-spacing: var(--md-sys-typescale-display-sm-tracking);
|
|
}
|
|
|
|
[data-app-bar][data-variant="large"] [data-app-bar-expanded] [data-app-bar-subtitle] {
|
|
font: var(--md-sys-typescale-title-md);
|
|
letter-spacing: var(--md-sys-typescale-title-md-tracking);
|
|
}
|
|
}
|