@noxlovette/material 0.7.1 → 0.8.1
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/components/time/TimeField.svelte +1 -4
- package/dist/styles/components.css +6 -0
- package/dist/styles/icon-font.css +17 -0
- package/dist/styles/motion.css +35 -0
- package/dist/utils/icon/MaterialSymbolsProvider.svelte +7 -1
- package/dist/utils/icon/MaterialSymbolsProvider.svelte.d.ts +3 -0
- package/dist/utils/icon/types.d.ts +11 -0
- package/package.json +2 -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',
|
|
@@ -98,10 +98,7 @@ It combines a Material 3 Textfield with a TimepickerInput for intuitive time sel
|
|
|
98
98
|
<div {...wrapperProps}>
|
|
99
99
|
<div
|
|
100
100
|
{...props}
|
|
101
|
-
class={[
|
|
102
|
-
props.class,
|
|
103
|
-
'max-h-(--bits-floating-available-height) min-w-(--bits-floating-anchor-width) overflow-auto'
|
|
104
|
-
]}
|
|
101
|
+
class={[props.class, 'min-w-(--bits-floating-anchor-width)']}
|
|
105
102
|
{@attach presence(() => open, enterExit.scale)}
|
|
106
103
|
>
|
|
107
104
|
<TimepickerInput
|
|
@@ -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
|
/*
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Optional, for `<App iconProviderProps={{ selfHosted: true }}>`. Google's stylesheet normally
|
|
3
|
+
binds each `.material-symbols-*` class to its font family; a self-hoster imports this instead and
|
|
4
|
+
writes only the `@font-face` rules (variable, with the opsz, wght, FILL and GRAD axes).
|
|
5
|
+
The rest of the class metrics (line-height, ligatures, smoothing…) ship in `./styles`.
|
|
6
|
+
*/
|
|
7
|
+
.material-symbols-rounded {
|
|
8
|
+
font-family: 'Material Symbols Rounded';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.material-symbols-outlined {
|
|
12
|
+
font-family: 'Material Symbols Outlined';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.material-symbols-sharp {
|
|
16
|
+
font-family: 'Material Symbols Sharp';
|
|
17
|
+
}
|
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
|
+
}
|
|
@@ -2,12 +2,16 @@
|
|
|
2
2
|
@component
|
|
3
3
|
A helper that handles google icons. Should not be used directly.
|
|
4
4
|
Use the App.svelte component instead
|
|
5
|
+
|
|
6
|
+
With `selfHosted` it renders nothing, so no request goes to fonts.googleapis.com. The consumer then
|
|
7
|
+
supplies the `@font-face` rules; see `@noxlovette/material/styles/icon-font.css`.
|
|
5
8
|
-->
|
|
6
9
|
<script lang="ts">
|
|
7
10
|
import { baseIcons } from './base-icons.js';
|
|
8
11
|
import type { MaterialSymbolsProviderProps } from './types.js';
|
|
9
12
|
|
|
10
13
|
const {
|
|
14
|
+
selfHosted = false,
|
|
11
15
|
extraIcons = [],
|
|
12
16
|
display = 'swap',
|
|
13
17
|
subset = true,
|
|
@@ -41,5 +45,7 @@ Use the App.svelte component instead
|
|
|
41
45
|
</script>
|
|
42
46
|
|
|
43
47
|
<svelte:head>
|
|
44
|
-
|
|
48
|
+
{#if !selfHosted}
|
|
49
|
+
<link rel="stylesheet" {href} />
|
|
50
|
+
{/if}
|
|
45
51
|
</svelte:head>
|
|
@@ -2,6 +2,9 @@ import type { MaterialSymbolsProviderProps } from './types.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* A helper that handles google icons. Should not be used directly.
|
|
4
4
|
* Use the App.svelte component instead
|
|
5
|
+
*
|
|
6
|
+
* With `selfHosted` it renders nothing, so no request goes to fonts.googleapis.com. The consumer then
|
|
7
|
+
* supplies the `@font-face` rules; see `@noxlovette/material/styles/icon-font.css`.
|
|
5
8
|
*/
|
|
6
9
|
declare const MaterialSymbolsProvider: import("svelte").Component<MaterialSymbolsProviderProps, {}, "">;
|
|
7
10
|
type MaterialSymbolsProvider = ReturnType<typeof MaterialSymbolsProvider>;
|
|
@@ -59,6 +59,17 @@ export type LoadingIndicatorProps = HTMLAttributes<SVGAElement> & LoadingIndicat
|
|
|
59
59
|
center?: boolean;
|
|
60
60
|
};
|
|
61
61
|
export type MaterialSymbolsProviderProps = {
|
|
62
|
+
/**
|
|
63
|
+
* Skip the Google Fonts `<link>` so the page makes no request to `fonts.googleapis.com`, and
|
|
64
|
+
* supply the font yourself. Define an `@font-face` for each family in use (`"Material Symbols
|
|
65
|
+
* Rounded"`, `"Material Symbols Outlined"`, `"Material Symbols Sharp"`), variable, with the
|
|
66
|
+
* `opsz`, `wght`, `FILL` and `GRAD` axes that `Icon` sets through `font-variation-settings`, and
|
|
67
|
+
* import `@noxlovette/material/styles/icon-font.css` for the `font-family` of each
|
|
68
|
+
* `.material-symbols-*` class. The remaining class metrics ship with `@noxlovette/material/styles`.
|
|
69
|
+
* With this set, `icons`, `extraIcons`, `subset`, `families` and `display` have no effect.
|
|
70
|
+
* @default false
|
|
71
|
+
*/
|
|
72
|
+
selfHosted?: boolean;
|
|
62
73
|
icons?: MaterialSymbolName[];
|
|
63
74
|
/** Icon names to load on top of `baseIcons`. Ignored when `subset` is false. */
|
|
64
75
|
extraIcons?: MaterialSymbolName[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noxlovette/material",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A Material Design 3 component library for Svelte, built on Bits UI.",
|
|
6
6
|
"keywords": [
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"svelte": "./dist/index.js"
|
|
58
58
|
},
|
|
59
59
|
"./styles": "./dist/index.css",
|
|
60
|
+
"./styles/icon-font.css": "./dist/styles/icon-font.css",
|
|
60
61
|
"./theme/*": "./dist/styles/theme/*.css"
|
|
61
62
|
},
|
|
62
63
|
"peerDependencies": {
|