@noxlovette/material 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/claude-skill/material-design/references/motion-guide.md +9 -9
- package/dist/animation/containerTransform.d.ts +0 -27
- package/dist/animation/containerTransform.js +40 -9
- package/dist/components/buttons/ButtonIcon.svelte +6 -2
- package/dist/components/buttons/ButtonIcon.svelte.d.ts +1 -1
- package/dist/components/buttons/context.d.ts +10 -0
- package/dist/components/buttons/context.js +9 -0
- package/dist/components/containers/popover/theme.d.ts +3 -3
- package/dist/components/forms/checkbox/Checkbox.svelte +1 -1
- package/dist/components/forms/checkbox/theme.d.ts +0 -12
- package/dist/components/forms/checkbox/theme.js +2 -7
- package/dist/components/forms/search/Search.mdx +76 -8
- package/dist/components/forms/search/Search.stories.svelte +107 -0
- package/dist/components/forms/search/Search.stories.svelte.d.ts +2 -17
- package/dist/components/forms/search/Search.svelte +55 -2
- package/dist/components/forms/search/Search.svelte.d.ts +4 -1
- package/dist/components/forms/search/SearchView.svelte +269 -0
- package/dist/components/forms/search/SearchView.svelte.d.ts +21 -0
- package/dist/components/forms/search/index.d.ts +1 -0
- package/dist/components/forms/search/index.js +1 -0
- package/dist/components/forms/search/theme.d.ts +68 -1
- package/dist/components/forms/search/theme.js +91 -2
- package/dist/components/forms/search/types.d.ts +75 -2
- package/dist/components/nav/appbar/AppBar.mdx +5 -1
- package/dist/components/nav/appbar/AppBar.stories.svelte +38 -0
- package/dist/components/nav/appbar/AppBar.svelte +55 -2
- package/dist/components/nav/appbar/AppBar.svelte.d.ts +6 -2
- package/dist/components/nav/appbar/theme.js +13 -2
- package/dist/components/nav/appbar/types.d.ts +18 -1
- package/dist/components/table/theme.js +3 -1
- package/dist/styles/components.css +6 -0
- package/dist/styles/motion.css +35 -0
- package/package.json +1 -1
|
@@ -1,10 +1,78 @@
|
|
|
1
1
|
import type { IconProps } from '../../../utils/index.js';
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
4
|
+
import type { Responsive } from '../../containers/pane/theme.js';
|
|
5
|
+
/**
|
|
6
|
+
* Where the search view puts its suggestions and results
|
|
7
|
+
* (https://m3.material.io/components/search/guidelines).
|
|
8
|
+
* - `fullScreen`: the view covers the window; for compact windows.
|
|
9
|
+
* - `docked`: the bar widens in place and results open in a container below it.
|
|
10
|
+
*/
|
|
11
|
+
export type SearchLayout = 'fullScreen' | 'docked';
|
|
12
|
+
/**
|
|
13
|
+
* Handed to the `results` snippet. Spread it onto the `List` that holds the suggestions, and give
|
|
14
|
+
* each item `role="option"`, so the field can drive it as a combobox.
|
|
15
|
+
*
|
|
16
|
+
* ```svelte
|
|
17
|
+
* {#snippet results(listbox)}
|
|
18
|
+
* <List {...listbox}>
|
|
19
|
+
* <ListItem role="option" asChild headline="Material" onclick={pick} />
|
|
20
|
+
* </List>
|
|
21
|
+
* {/snippet}
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export interface SearchResultsProps {
|
|
25
|
+
id: string;
|
|
26
|
+
role: 'listbox';
|
|
27
|
+
'aria-label': string;
|
|
28
|
+
}
|
|
29
|
+
/** Props shared by the search view and the components that open it. */
|
|
30
|
+
export interface SearchViewOptions {
|
|
31
|
+
/**
|
|
32
|
+
* Suggestions and results, shown in the search view. Setting it gives the bar a search view,
|
|
33
|
+
* opened by clicking or typing in the bar.
|
|
34
|
+
*/
|
|
35
|
+
results?: Snippet<[SearchResultsProps]>;
|
|
36
|
+
/**
|
|
37
|
+
* The search view's layout, one value or one per window tier.
|
|
38
|
+
* @default { small: 'fullScreen', medium: 'docked' }
|
|
39
|
+
*/
|
|
40
|
+
layout?: Responsive<SearchLayout>;
|
|
41
|
+
/** Accessible label for the back button that closes the search view. */
|
|
42
|
+
backLabel?: string;
|
|
43
|
+
/** Accessible name of the search view and its suggestion list. Defaults to the placeholder. */
|
|
44
|
+
resultsLabel?: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Props for the SearchView component.
|
|
48
|
+
*/
|
|
49
|
+
export interface SearchViewProps extends Omit<SearchViewOptions, 'results'> {
|
|
50
|
+
/** Whether the view is open. Opening and closing morph between `anchor` and the view. */
|
|
51
|
+
open?: boolean;
|
|
52
|
+
/** The search query, shared with the bar that opened the view. */
|
|
53
|
+
value?: string | null;
|
|
54
|
+
/**
|
|
55
|
+
* The search bar the view opens from: the container transform starts there, the docked layout
|
|
56
|
+
* is placed over it, and focus returns to its input on close.
|
|
57
|
+
*/
|
|
58
|
+
anchor: HTMLElement | undefined;
|
|
59
|
+
/** Suggestions and results. */
|
|
60
|
+
results: Snippet<[SearchResultsProps]>;
|
|
61
|
+
/** Hint text in the view's field. */
|
|
62
|
+
placeholder?: string;
|
|
63
|
+
/** Actions after the clear button in the view's bar, e.g. a mic icon button. */
|
|
64
|
+
trailing?: Snippet;
|
|
65
|
+
/** Accessible label for the clear button. */
|
|
66
|
+
clearLabel?: string;
|
|
67
|
+
/** Extra props for the view's `<input>`. */
|
|
68
|
+
inputProps?: HTMLInputAttributes;
|
|
69
|
+
/** Reference to the view's `<input>` while it is open. */
|
|
70
|
+
inputRef?: HTMLInputElement;
|
|
71
|
+
}
|
|
4
72
|
/**
|
|
5
73
|
* Props for the Search component.
|
|
6
74
|
*/
|
|
7
|
-
export interface SearchProps extends Omit<HTMLInputAttributes, 'size'
|
|
75
|
+
export interface SearchProps extends Omit<HTMLInputAttributes, 'size' | 'results'>, SearchViewOptions {
|
|
8
76
|
/**
|
|
9
77
|
* The current value of the search input.
|
|
10
78
|
*/
|
|
@@ -13,12 +81,17 @@ export interface SearchProps extends Omit<HTMLInputAttributes, 'size'> {
|
|
|
13
81
|
* Reference to the HTML input element.
|
|
14
82
|
*/
|
|
15
83
|
elementRef?: HTMLInputElement;
|
|
84
|
+
/**
|
|
85
|
+
* Whether the search view is open. Only has an effect with `results`.
|
|
86
|
+
*/
|
|
87
|
+
open?: boolean;
|
|
16
88
|
/**
|
|
17
89
|
* A leading action, e.g. a menu or back icon button. Replaces the leading icon.
|
|
18
90
|
*/
|
|
19
91
|
leading?: Snippet;
|
|
20
92
|
/**
|
|
21
|
-
* Trailing actions and/or an avatar, e.g. a mic icon button. Rendered after the clear button
|
|
93
|
+
* Trailing actions and/or an avatar, e.g. a mic icon button. Rendered after the clear button,
|
|
94
|
+
* in the bar and in the search view.
|
|
22
95
|
*/
|
|
23
96
|
trailing?: Snippet;
|
|
24
97
|
/**
|
|
@@ -24,12 +24,16 @@ The props form a union, so the bar is one of two kinds:
|
|
|
24
24
|
`{ small: 'small', large: 'large' }` to grow the bar on wider windows. The switch happens in CSS,
|
|
25
25
|
so server rendering needs no JS.
|
|
26
26
|
- **Search bar.** Pass `search` (the hint text) instead. The bar is always 64dp. `title` then
|
|
27
|
-
becomes a screen-reader-only heading.
|
|
27
|
+
becomes a screen-reader-only heading. Per M3, selecting the field should open the search view:
|
|
28
|
+
pass `searchResults` (and optionally `bind:searchOpen`, `searchLayout`) and it opens the same
|
|
29
|
+
view as `Search`, with a container transform from the field. See Forms/Search for the view.
|
|
28
30
|
|
|
29
31
|
<Canvas of={AppBarStories.MediumFlexible} />
|
|
30
32
|
|
|
31
33
|
<Canvas of={AppBarStories.Search} />
|
|
32
34
|
|
|
35
|
+
<Canvas of={AppBarStories.SearchView} />
|
|
36
|
+
|
|
33
37
|
## Placement
|
|
34
38
|
|
|
35
39
|
The bar is fixed at the top at every window size. Pass `ghost` to render a same-height spacer
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import { defineMeta } from '@storybook/addon-svelte-csf';
|
|
3
3
|
import AppBar from './AppBar.svelte';
|
|
4
4
|
import ButtonIcon from '../../buttons/ButtonIcon.svelte';
|
|
5
|
+
import List from '../../containers/list/List.svelte';
|
|
6
|
+
import ListItem from '../../containers/list/ListItem.svelte';
|
|
5
7
|
|
|
6
8
|
const { Story } = defineMeta({
|
|
7
9
|
title: 'Navigation/App Bar',
|
|
@@ -30,6 +32,11 @@
|
|
|
30
32
|
|
|
31
33
|
<script lang="ts">
|
|
32
34
|
let scroller = $state<HTMLElement | null>(null);
|
|
35
|
+
|
|
36
|
+
const products = ['Headphones', 'Keyboard', 'Laptop stand', 'Monitor', 'Mouse', 'Webcam'];
|
|
37
|
+
let query = $state('');
|
|
38
|
+
let searchOpen = $state(false);
|
|
39
|
+
const found = $derived(products.filter((p) => p.toLowerCase().includes(query.toLowerCase())));
|
|
33
40
|
</script>
|
|
34
41
|
|
|
35
42
|
<Story name="Playground">
|
|
@@ -125,6 +132,37 @@
|
|
|
125
132
|
</AppBar>
|
|
126
133
|
</Story>
|
|
127
134
|
|
|
135
|
+
<!-- Selecting the search field opens the search view: full-screen here, docked from medium up. -->
|
|
136
|
+
<Story
|
|
137
|
+
name="Search view"
|
|
138
|
+
asChild
|
|
139
|
+
parameters={{ docs: { story: { inline: false, height: '560px' } } }}
|
|
140
|
+
>
|
|
141
|
+
<AppBar search="Search products" title="Products" bind:query bind:searchOpen ghost>
|
|
142
|
+
{#snippet leading()}
|
|
143
|
+
<ButtonIcon variant="standard" iconProps={{ name: 'menu' }} aria-label="Menu" />
|
|
144
|
+
{/snippet}
|
|
145
|
+
{#snippet searchTrailing()}
|
|
146
|
+
<ButtonIcon variant="standard" iconProps={{ name: 'mic' }} aria-label="Voice search" />
|
|
147
|
+
{/snippet}
|
|
148
|
+
{#snippet searchResults(listbox)}
|
|
149
|
+
<List {...listbox}>
|
|
150
|
+
{#each found as product (product)}
|
|
151
|
+
<ListItem
|
|
152
|
+
role="option"
|
|
153
|
+
asChild
|
|
154
|
+
headline={product}
|
|
155
|
+
onclick={() => {
|
|
156
|
+
query = product;
|
|
157
|
+
searchOpen = false;
|
|
158
|
+
}}
|
|
159
|
+
/>
|
|
160
|
+
{/each}
|
|
161
|
+
</List>
|
|
162
|
+
{/snippet}
|
|
163
|
+
</AppBar>
|
|
164
|
+
</Story>
|
|
165
|
+
|
|
128
166
|
<Story name="Scroll container" asChild>
|
|
129
167
|
<div bind:this={scroller} class="h-[480px] overflow-y-auto">
|
|
130
168
|
<AppBar title="Scroll this box" scrollContainer={scroller} ghost />
|
|
@@ -8,7 +8,8 @@ They’re used for branding, screen titles, navigation, and actions.
|
|
|
8
8
|
Sizes: `small` (64dp, one line) and the Expressive `medium`/`large` flexible bars, whose
|
|
9
9
|
headline block wraps and grows. `size` also takes one value per breakpoint
|
|
10
10
|
(`{ small: 'small', large: 'large' }`), switched in CSS so server rendering needs no JS.
|
|
11
|
-
`align="center"` centres the title. Setting `search` makes it a search app bar
|
|
11
|
+
`align="center"` centres the title. Setting `search` makes it a search app bar; add
|
|
12
|
+
`searchResults` and selecting the field opens the search view, as with `Search`.
|
|
12
13
|
|
|
13
14
|
The bar takes its on-scroll color once `scrollContainer` (default: the page) scrolls.
|
|
14
15
|
|
|
@@ -17,6 +18,9 @@ Publishes its measured height as `--appbar-height` on the document root, so any
|
|
|
17
18
|
that amount with no props or wiring on either side — see Pane.svelte's doc
|
|
18
19
|
comment.
|
|
19
20
|
|
|
21
|
+
A `ButtonIcon` anywhere inside it defaults to `variant="standard"`, per M3; pass
|
|
22
|
+
`variant` to override.
|
|
23
|
+
|
|
20
24
|
@see https://m3.material.io/components/app-bars/specs
|
|
21
25
|
-->
|
|
22
26
|
<script lang="ts">
|
|
@@ -24,6 +28,8 @@ comment.
|
|
|
24
28
|
import type { AppBarProps } from './types.js';
|
|
25
29
|
import clsx from 'clsx';
|
|
26
30
|
import ButtonIcon from '../../buttons/ButtonIcon.svelte';
|
|
31
|
+
import { setButtonIconVariant } from '../../buttons/context.js';
|
|
32
|
+
import SearchView from '../../forms/search/SearchView.svelte';
|
|
27
33
|
|
|
28
34
|
let {
|
|
29
35
|
children,
|
|
@@ -43,11 +49,18 @@ comment.
|
|
|
43
49
|
query = $bindable(''),
|
|
44
50
|
searchProps,
|
|
45
51
|
searchTrailing,
|
|
52
|
+
searchResults,
|
|
53
|
+
searchOpen = $bindable(false),
|
|
54
|
+
searchLayout,
|
|
46
55
|
scrollContainer,
|
|
47
56
|
ghost = false,
|
|
48
57
|
...rest
|
|
49
58
|
}: AppBarProps = $props();
|
|
50
59
|
|
|
60
|
+
// M3 app bar icon buttons are standard (no container); a filled one over-emphasizes a back or
|
|
61
|
+
// action button. Applies to buttons passed in `leading`/`trailing`/`children` too.
|
|
62
|
+
setButtonIconVariant('standard');
|
|
63
|
+
|
|
51
64
|
let scrolled = $state(false);
|
|
52
65
|
let barHeight = $state(64);
|
|
53
66
|
|
|
@@ -61,6 +74,33 @@ comment.
|
|
|
61
74
|
});
|
|
62
75
|
|
|
63
76
|
const isSearch = $derived(search !== undefined);
|
|
77
|
+
|
|
78
|
+
let searchBar = $state<HTMLElement>();
|
|
79
|
+
|
|
80
|
+
// Same as Search: click, typing or ↓ opens the view; focus alone doesn't.
|
|
81
|
+
const opener = $derived(
|
|
82
|
+
searchResults
|
|
83
|
+
? {
|
|
84
|
+
'aria-haspopup': 'dialog' as const,
|
|
85
|
+
'aria-expanded': searchOpen,
|
|
86
|
+
onclick: (e: MouseEvent & { currentTarget: HTMLInputElement }) => {
|
|
87
|
+
searchProps?.onclick?.(e);
|
|
88
|
+
if (!e.defaultPrevented) searchOpen = true;
|
|
89
|
+
},
|
|
90
|
+
oninput: (e: Event & { currentTarget: HTMLInputElement }) => {
|
|
91
|
+
searchProps?.oninput?.(e);
|
|
92
|
+
if (!e.defaultPrevented) searchOpen = true;
|
|
93
|
+
},
|
|
94
|
+
onkeydown: (e: KeyboardEvent & { currentTarget: HTMLInputElement }) => {
|
|
95
|
+
searchProps?.onkeydown?.(e);
|
|
96
|
+
if (!e.defaultPrevented && e.key === 'ArrowDown') {
|
|
97
|
+
e.preventDefault();
|
|
98
|
+
searchOpen = true;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
: {}
|
|
103
|
+
);
|
|
64
104
|
const noLeading = $derived(!leading && !showBack);
|
|
65
105
|
|
|
66
106
|
const s = $derived(
|
|
@@ -110,10 +150,11 @@ comment.
|
|
|
110
150
|
{#if title}
|
|
111
151
|
<h1 {...titleProps} class={clsx('sr-only', titleProps?.class)}>{title}</h1>
|
|
112
152
|
{/if}
|
|
113
|
-
<label class={s.search()}>
|
|
153
|
+
<label class={s.search()} bind:this={searchBar}>
|
|
114
154
|
<input
|
|
115
155
|
type="search"
|
|
116
156
|
{...searchProps}
|
|
157
|
+
{...opener}
|
|
117
158
|
placeholder={search}
|
|
118
159
|
aria-label={searchProps?.['aria-label'] ?? search}
|
|
119
160
|
bind:value={query}
|
|
@@ -148,6 +189,18 @@ comment.
|
|
|
148
189
|
{/if}
|
|
149
190
|
</nav>
|
|
150
191
|
|
|
192
|
+
{#if isSearch && searchResults}
|
|
193
|
+
<SearchView
|
|
194
|
+
bind:open={searchOpen}
|
|
195
|
+
bind:value={query}
|
|
196
|
+
anchor={searchBar}
|
|
197
|
+
results={searchResults}
|
|
198
|
+
layout={searchLayout}
|
|
199
|
+
placeholder={search}
|
|
200
|
+
trailing={searchTrailing}
|
|
201
|
+
/>
|
|
202
|
+
{/if}
|
|
203
|
+
|
|
151
204
|
{#if ghost}
|
|
152
205
|
<div class={s.ghost()} style="height: {barHeight}px" aria-hidden="true"></div>
|
|
153
206
|
{/if}
|
|
@@ -8,7 +8,8 @@ import type { AppBarProps } from './types.js';
|
|
|
8
8
|
* Sizes: `small` (64dp, one line) and the Expressive `medium`/`large` flexible bars, whose
|
|
9
9
|
* headline block wraps and grows. `size` also takes one value per breakpoint
|
|
10
10
|
* (`{ small: 'small', large: 'large' }`), switched in CSS so server rendering needs no JS.
|
|
11
|
-
* `align="center"` centres the title. Setting `search` makes it a search app bar
|
|
11
|
+
* `align="center"` centres the title. Setting `search` makes it a search app bar; add
|
|
12
|
+
* `searchResults` and selecting the field opens the search view, as with `Search`.
|
|
12
13
|
*
|
|
13
14
|
* The bar takes its on-scroll color once `scrollContainer` (default: the page) scrolls.
|
|
14
15
|
*
|
|
@@ -17,8 +18,11 @@ import type { AppBarProps } from './types.js';
|
|
|
17
18
|
* that amount with no props or wiring on either side — see Pane.svelte's doc
|
|
18
19
|
* comment.
|
|
19
20
|
*
|
|
21
|
+
* A `ButtonIcon` anywhere inside it defaults to `variant="standard"`, per M3; pass
|
|
22
|
+
* `variant` to override.
|
|
23
|
+
*
|
|
20
24
|
* @see https://m3.material.io/components/app-bars/specs
|
|
21
25
|
*/
|
|
22
|
-
declare const AppBar: import("svelte").Component<AppBarProps, {}, "query">;
|
|
26
|
+
declare const AppBar: import("svelte").Component<AppBarProps, {}, "query" | "searchOpen">;
|
|
23
27
|
type AppBar = ReturnType<typeof AppBar>;
|
|
24
28
|
export default AppBar;
|
|
@@ -131,7 +131,7 @@ export const appbar = tv({
|
|
|
131
131
|
subtitle: 'text-md-sys-color-on-surface-variant',
|
|
132
132
|
leading: 'text-md-sys-color-on-surface col-start-1 row-start-1 flex shrink-0 items-center justify-self-start',
|
|
133
133
|
trailing: 'text-md-sys-color-on-surface-variant col-start-3 row-start-1 flex shrink-0 items-center justify-self-end',
|
|
134
|
-
search: 'state-layer relative flex h-spacing-700 w-full min-w-spacing-0 cursor-text items-center rounded-
|
|
134
|
+
search: 'state-layer relative flex h-spacing-700 w-full min-w-spacing-0 cursor-text items-center rounded-xl ps-spacing-200 pe-spacing-200 transition-colors md-sys-motion-effects before:rounded-xl hover:before:bg-md-sys-color-on-surface/8 focus-within:outline-3 focus-within:outline-offset-2 focus-within:outline-md-sys-color-secondary [:root:active-view-transition_&]:outline-0',
|
|
135
135
|
searchInput: 'md-sys-typescale-body-large text-md-sys-color-on-surface placeholder:text-md-sys-color-on-surface-variant w-full min-w-spacing-0 bg-transparent text-start outline-none placeholder-shown:text-center [&::-webkit-search-cancel-button]:hidden',
|
|
136
136
|
searchTrailing: 'text-md-sys-color-on-surface-variant flex shrink-0 items-center'
|
|
137
137
|
},
|
|
@@ -141,12 +141,23 @@ export const appbar = tv({
|
|
|
141
141
|
// 1fr side columns centre a small bar's title on the bar, not between uneven button groups.
|
|
142
142
|
center: { row: 'grid-cols-[1fr_auto_1fr]', textContainer: 'items-center text-center' }
|
|
143
143
|
},
|
|
144
|
+
/*
|
|
145
|
+
From medium up, a Pane rounds its top corners (md:rounded-t-*) and sits as a surface card
|
|
146
|
+
on the App's surface-container, beside a surface-container Rail. The bar is part of that
|
|
147
|
+
chrome there, so it stays surface-container whether scrolled or not: a surface bar
|
|
148
|
+
would read as a strip glued over the card, with the App's colour showing through the
|
|
149
|
+
pane's corners beneath it. Below medium the pane is flat and full-bleed, so the bar keeps
|
|
150
|
+
M3's surface → surface-container on-scroll change.
|
|
151
|
+
*/
|
|
144
152
|
scrolled: {
|
|
145
153
|
true: {
|
|
146
154
|
base: 'bg-md-sys-color-surface-container',
|
|
147
155
|
search: 'bg-md-sys-color-surface-container-highest'
|
|
148
156
|
},
|
|
149
|
-
false: {
|
|
157
|
+
false: {
|
|
158
|
+
base: 'bg-md-sys-color-surface md:bg-md-sys-color-surface-container',
|
|
159
|
+
search: 'bg-md-sys-color-surface-container md:bg-md-sys-color-surface-container-highest'
|
|
160
|
+
}
|
|
150
161
|
},
|
|
151
162
|
// Search app bar: the search container fills the text column, 8dp from the buttons.
|
|
152
163
|
searchContainer: {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
2
|
import type { HTMLAttributes, HTMLInputAttributes } from 'svelte/elements';
|
|
3
3
|
import type { Responsive } from '../../containers/pane/theme.js';
|
|
4
|
+
import type { SearchLayout, SearchResultsProps } from '../../forms/search/types.js';
|
|
4
5
|
import type { AppBarSize, AppbarVariants } from './theme.js';
|
|
5
6
|
type AppBarBaseProps = Pick<AppbarVariants, 'align'> & HTMLAttributes<HTMLElementTagNameMap['nav']> & {
|
|
6
7
|
/** Extra content rendered in a full-width row below the title, e.g. a Breadcrumb trail. */
|
|
@@ -43,6 +44,9 @@ export type TitleAppBarProps = AppBarBaseProps & {
|
|
|
43
44
|
query?: never;
|
|
44
45
|
searchProps?: never;
|
|
45
46
|
searchTrailing?: never;
|
|
47
|
+
searchResults?: never;
|
|
48
|
+
searchOpen?: never;
|
|
49
|
+
searchLayout?: never;
|
|
46
50
|
};
|
|
47
51
|
/** A search app bar: a search field in place of the title, always 64dp. */
|
|
48
52
|
export type SearchAppBarProps = AppBarBaseProps & {
|
|
@@ -54,8 +58,21 @@ export type SearchAppBarProps = AppBarBaseProps & {
|
|
|
54
58
|
title?: string;
|
|
55
59
|
/** Props for the search `<input>`, e.g. `onfocus` to open a search view. */
|
|
56
60
|
searchProps?: HTMLInputAttributes;
|
|
57
|
-
/** An action inside the search field, e.g. a mic icon button. */
|
|
61
|
+
/** An action inside the search field, e.g. a mic icon button. Also shown in the search view. */
|
|
58
62
|
searchTrailing?: Snippet;
|
|
63
|
+
/**
|
|
64
|
+
* Suggestions and results. Setting it gives the bar a search view (`SearchView`), opened by
|
|
65
|
+
* clicking or typing in the field. Spread the snippet's argument onto a `List` of
|
|
66
|
+
* `role="option"` items.
|
|
67
|
+
*/
|
|
68
|
+
searchResults?: Snippet<[SearchResultsProps]>;
|
|
69
|
+
/** Whether the search view is open. Only has an effect with `searchResults`. */
|
|
70
|
+
searchOpen?: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* The search view's layout, one value or one per window tier.
|
|
73
|
+
* @default { small: 'fullScreen', medium: 'docked' }
|
|
74
|
+
*/
|
|
75
|
+
searchLayout?: Responsive<SearchLayout>;
|
|
59
76
|
size?: never;
|
|
60
77
|
};
|
|
61
78
|
/**
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { tv } from '../../utils/tv.js';
|
|
2
2
|
export const table = tv({
|
|
3
3
|
slots: {
|
|
4
|
-
|
|
4
|
+
// `isolate` scopes the sticky head's z-10 to the table, so it only covers the rows
|
|
5
|
+
// scrolling beneath it and never app bars, menus or sheets outside the table.
|
|
6
|
+
root: 'isolate w-full border-collapse text-left md-sys-typescale-body-medium text-md-sys-color-on-surface',
|
|
5
7
|
head: '',
|
|
6
8
|
body: '[&_tr:last-child]:border-b-0',
|
|
7
9
|
foot: 'border-t border-md-sys-color-outline-variant',
|
|
@@ -11,6 +11,12 @@
|
|
|
11
11
|
--md-comp-nav-rail-expanded-top-space: 2.75rem; /* 44dp */
|
|
12
12
|
--md-comp-nav-rail-expanded-container-width-minimum: 13.75rem; /* 220dp */
|
|
13
13
|
--md-comp-nav-rail-expanded-container-width-maximum: 22.5rem; /* 360dp */
|
|
14
|
+
|
|
15
|
+
/* Search view, contained style, docked layout: https://m3.material.io/components/search/specs */
|
|
16
|
+
--md-comp-search-view-contained-docked-container-width-minimum: 22.5rem; /* 360dp */
|
|
17
|
+
--md-comp-search-view-contained-docked-container-width-maximum: 45rem; /* 720dp */
|
|
18
|
+
--md-comp-search-view-contained-docked-container-height-minimum: 15rem; /* 240dp */
|
|
19
|
+
--md-comp-search-view-contained-docked-container-height-maximum: calc(100dvh * 2 / 3);
|
|
14
20
|
}
|
|
15
21
|
|
|
16
22
|
/*
|
package/dist/styles/motion.css
CHANGED
|
@@ -331,3 +331,38 @@
|
|
|
331
331
|
transition-duration: var(--md-sys-motion-duration-slow-effects-spring);
|
|
332
332
|
transition-timing-function: var(--md-sys-motion-timing-function-slow-effects-spring);
|
|
333
333
|
}
|
|
334
|
+
|
|
335
|
+
/*
|
|
336
|
+
Container transform (animation/containerTransform.ts): both snapshots keep their own width and
|
|
337
|
+
sit at the container's top, clipped by it as it grows or shrinks. Motion's crop would otherwise
|
|
338
|
+
`object-fit: cover` them, which blows a short bar's snapshot up to the height of a full view.
|
|
339
|
+
Motion injects its per-name rule at runtime, after this sheet and at the same specificity, so
|
|
340
|
+
these need `!important` to win.
|
|
341
|
+
*/
|
|
342
|
+
::view-transition-old(.md-container-transform),
|
|
343
|
+
::view-transition-new(.md-container-transform) {
|
|
344
|
+
top: 0 !important;
|
|
345
|
+
width: 100% !important;
|
|
346
|
+
height: auto !important;
|
|
347
|
+
object-fit: fill !important;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/*
|
|
351
|
+
Outgoing on top, fading off the incoming snapshot underneath, which stays opaque. Normal
|
|
352
|
+
blending: the browser runs `plus-lighter` on the pair as an animation, which only
|
|
353
|
+
`!important` outranks, and adding the outgoing bar onto the opaque view flashed it white.
|
|
354
|
+
The incoming snapshot also drops the browser's default fade-in.
|
|
355
|
+
*/
|
|
356
|
+
::view-transition-old(.md-container-transform) {
|
|
357
|
+
z-index: 1;
|
|
358
|
+
mix-blend-mode: normal !important;
|
|
359
|
+
}
|
|
360
|
+
::view-transition-new(.md-container-transform) {
|
|
361
|
+
mix-blend-mode: normal !important;
|
|
362
|
+
animation-name: none !important;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/* Filled with the destination's own background while it morphs (set by containerTransform). */
|
|
366
|
+
::view-transition-group(.md-container-transform) {
|
|
367
|
+
background-color: var(--md-container-transform-color, transparent);
|
|
368
|
+
}
|