@noxlovette/material 0.7.1 → 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/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 +47 -2
- package/dist/components/nav/appbar/AppBar.svelte.d.ts +3 -2
- package/dist/components/nav/appbar/theme.js +1 -1
- 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
|
@@ -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
|
|
|
@@ -28,6 +29,7 @@ A `ButtonIcon` anywhere inside it defaults to `variant="standard"`, per M3; pass
|
|
|
28
29
|
import clsx from 'clsx';
|
|
29
30
|
import ButtonIcon from '../../buttons/ButtonIcon.svelte';
|
|
30
31
|
import { setButtonIconVariant } from '../../buttons/context.js';
|
|
32
|
+
import SearchView from '../../forms/search/SearchView.svelte';
|
|
31
33
|
|
|
32
34
|
let {
|
|
33
35
|
children,
|
|
@@ -47,6 +49,9 @@ A `ButtonIcon` anywhere inside it defaults to `variant="standard"`, per M3; pass
|
|
|
47
49
|
query = $bindable(''),
|
|
48
50
|
searchProps,
|
|
49
51
|
searchTrailing,
|
|
52
|
+
searchResults,
|
|
53
|
+
searchOpen = $bindable(false),
|
|
54
|
+
searchLayout,
|
|
50
55
|
scrollContainer,
|
|
51
56
|
ghost = false,
|
|
52
57
|
...rest
|
|
@@ -69,6 +74,33 @@ A `ButtonIcon` anywhere inside it defaults to `variant="standard"`, per M3; pass
|
|
|
69
74
|
});
|
|
70
75
|
|
|
71
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
|
+
);
|
|
72
104
|
const noLeading = $derived(!leading && !showBack);
|
|
73
105
|
|
|
74
106
|
const s = $derived(
|
|
@@ -118,10 +150,11 @@ A `ButtonIcon` anywhere inside it defaults to `variant="standard"`, per M3; pass
|
|
|
118
150
|
{#if title}
|
|
119
151
|
<h1 {...titleProps} class={clsx('sr-only', titleProps?.class)}>{title}</h1>
|
|
120
152
|
{/if}
|
|
121
|
-
<label class={s.search()}>
|
|
153
|
+
<label class={s.search()} bind:this={searchBar}>
|
|
122
154
|
<input
|
|
123
155
|
type="search"
|
|
124
156
|
{...searchProps}
|
|
157
|
+
{...opener}
|
|
125
158
|
placeholder={search}
|
|
126
159
|
aria-label={searchProps?.['aria-label'] ?? search}
|
|
127
160
|
bind:value={query}
|
|
@@ -156,6 +189,18 @@ A `ButtonIcon` anywhere inside it defaults to `variant="standard"`, per M3; pass
|
|
|
156
189
|
{/if}
|
|
157
190
|
</nav>
|
|
158
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
|
+
|
|
159
204
|
{#if ghost}
|
|
160
205
|
<div class={s.ghost()} style="height: {barHeight}px" aria-hidden="true"></div>
|
|
161
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
|
*
|
|
@@ -22,6 +23,6 @@ import type { AppBarProps } from './types.js';
|
|
|
22
23
|
*
|
|
23
24
|
* @see https://m3.material.io/components/app-bars/specs
|
|
24
25
|
*/
|
|
25
|
-
declare const AppBar: import("svelte").Component<AppBarProps, {}, "query">;
|
|
26
|
+
declare const AppBar: import("svelte").Component<AppBarProps, {}, "query" | "searchOpen">;
|
|
26
27
|
type AppBar = ReturnType<typeof AppBar>;
|
|
27
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
|
},
|
|
@@ -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
|
+
}
|