@noxlovette/material 0.6.1 → 0.7.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/SKILL.md +5 -2
- package/claude-skill/material-design/references/accessibility-checklist.md +5 -0
- package/claude-skill/material-design/references/component-patterns.md +24 -3
- package/claude-skill/material-design/references/motion-guide.md +53 -11
- package/claude-skill/material-design/references/tokens-and-styles.md +4 -0
- package/dist/animation/TransitionPatterns.stories.svelte +50 -0
- package/dist/animation/containerTransform.js +10 -4
- package/dist/animation/reducedMotion.d.ts +2 -0
- package/dist/animation/reducedMotion.js +2 -0
- package/dist/animation/sharedAxisTransition.d.ts +20 -3
- package/dist/animation/sharedAxisTransition.js +67 -8
- package/dist/attachments/drag.d.ts +52 -0
- package/dist/attachments/drag.js +217 -0
- package/dist/attachments/index.d.ts +1 -0
- package/dist/attachments/index.js +1 -0
- 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/chips/Chip.mdx +24 -11
- package/dist/components/chips/Chip.stories.svelte +10 -0
- package/dist/components/chips/Chip.svelte +58 -18
- package/dist/components/chips/Chip.svelte.d.ts +3 -1
- package/dist/components/chips/ChipGroup.stories.svelte +73 -0
- package/dist/components/chips/ChipGroup.stories.svelte.d.ts +4 -0
- package/dist/components/chips/ChipGroup.svelte +242 -0
- package/dist/components/chips/ChipGroup.svelte.d.ts +33 -0
- package/dist/components/chips/index.d.ts +1 -0
- package/dist/components/chips/index.js +1 -0
- package/dist/components/chips/theme.d.ts +182 -21
- package/dist/components/chips/theme.js +116 -25
- package/dist/components/chips/types.d.ts +36 -2
- package/dist/components/containers/app/theme.d.ts +1 -1
- package/dist/components/containers/app/theme.js +1 -1
- package/dist/components/containers/bottom-sheet/BottomSheet.stories.svelte +1 -1
- package/dist/components/containers/list/List.svelte +8 -2
- package/dist/components/containers/list/ListItem.mdx +6 -0
- package/dist/components/containers/list/ListItem.svelte +77 -2
- package/dist/components/containers/list/context.d.ts +14 -0
- package/dist/components/containers/list/theme.d.ts +6 -0
- package/dist/components/containers/list/theme.js +10 -2
- package/dist/components/containers/pane/DraggablePane.svelte +25 -63
- package/dist/components/containers/pane/Pane.svelte +1 -1
- package/dist/components/containers/pane/theme.js +32 -6
- package/dist/components/containers/popover/theme.d.ts +3 -3
- package/dist/components/date/theme.d.ts +6 -6
- package/dist/components/forms/checkbox/theme.js +1 -1
- package/dist/components/forms/command/theme.d.ts +3 -3
- package/dist/components/misc/Avatar.stories.svelte +56 -7
- package/dist/components/misc/Avatar.stories.svelte.d.ts +2 -17
- package/dist/components/misc/Avatar.svelte +82 -8
- package/dist/components/misc/Avatar.svelte.d.ts +5 -0
- package/dist/components/misc/theme.d.ts +6 -0
- package/dist/components/misc/theme.js +5 -3
- package/dist/components/misc/types.d.ts +17 -1
- package/dist/components/nav/appbar/AppBar.svelte +8 -0
- package/dist/components/nav/appbar/AppBar.svelte.d.ts +3 -0
- package/dist/components/nav/appbar/theme.js +14 -2
- package/dist/components/nav/currentRoute.d.ts +11 -0
- package/dist/components/nav/currentRoute.js +28 -0
- package/dist/components/nav/navbar/NavbarItem.svelte +3 -6
- package/dist/components/nav/navbar/theme.d.ts +3 -3
- package/dist/components/nav/rail/Rail.mdx +65 -23
- package/dist/components/nav/rail/Rail.stories.svelte +24 -4
- package/dist/components/nav/rail/Rail.stories.svelte.d.ts +2 -17
- package/dist/components/nav/rail/Rail.svelte +163 -48
- package/dist/components/nav/rail/Rail.svelte.d.ts +6 -3
- package/dist/components/nav/rail/RailItem.svelte +122 -65
- package/dist/components/nav/rail/RailItem.svelte.d.ts +3 -2
- package/dist/components/nav/rail/theme.d.ts +69 -84
- package/dist/components/nav/rail/theme.js +79 -66
- package/dist/components/nav/tabs/Tabs.stories.svelte +2 -1
- package/dist/index.css +2 -0
- package/dist/styles/components.css +50 -0
- package/dist/styles/icon.css +13 -0
- package/dist/utils/icon/Icon.stories.svelte +97 -0
- package/dist/utils/icon/Icon.stories.svelte.d.ts +4 -0
- package/dist/utils/icon/Icon.svelte +101 -12
- package/dist/utils/icon/Icon.svelte.d.ts +9 -0
- package/dist/utils/icon/MaterialSymbolsProvider.svelte +11 -4
- package/dist/utils/icon/base-icons.d.ts +1 -1
- package/dist/utils/icon/base-icons.js +1 -1
- package/dist/utils/icon/icon-names.d.ts +2 -0
- package/dist/utils/icon/icon-names.js +2 -0
- package/dist/utils/icon/swap.d.ts +6 -0
- package/dist/utils/icon/swap.js +45 -0
- package/dist/utils/icon/types.d.ts +59 -7
- package/dist/utils/tv.d.ts +3 -4
- package/dist/utils/tv.js +14 -6
- package/package.json +2 -1
|
@@ -92,11 +92,22 @@ const marginClasses = {
|
|
|
92
92
|
xl: 'xl:m-spacing-400'
|
|
93
93
|
}
|
|
94
94
|
};
|
|
95
|
+
/*
|
|
96
|
+
A pane's flex-basis is its size along the grid's axis, so it depends on the direction. Each
|
|
97
|
+
Pane publishes the basis it wants in a row as --pane-basis (0 for a flexible pane, so flexible
|
|
98
|
+
siblings share the free space; its px width for a fixed one), and each tier here applies it to
|
|
99
|
+
the children: that basis in a row, `auto` in a column, where panes stack at their content
|
|
100
|
+
height. A basis of 0 in a column made a flexible pane exactly as tall as its min-height, with
|
|
101
|
+
the rest of its content clipped. The `*:` classes beat the Pane's own `basis-*`.
|
|
102
|
+
*/
|
|
95
103
|
const directionClasses = {
|
|
96
|
-
small: { row: 'flex-row', column: 'flex-col' },
|
|
97
|
-
medium: { row: 'md:flex-row', column: 'md:flex-col' },
|
|
98
|
-
large: { row: 'lg:flex-row', column: 'lg:flex-col' },
|
|
99
|
-
extraLarge: {
|
|
104
|
+
small: { row: 'flex-row *:basis-(--pane-basis)', column: 'flex-col *:basis-auto' },
|
|
105
|
+
medium: { row: 'md:flex-row md:*:basis-(--pane-basis)', column: 'md:flex-col md:*:basis-auto' },
|
|
106
|
+
large: { row: 'lg:flex-row lg:*:basis-(--pane-basis)', column: 'lg:flex-col lg:*:basis-auto' },
|
|
107
|
+
extraLarge: {
|
|
108
|
+
row: 'xl:flex-row xl:*:basis-(--pane-basis)',
|
|
109
|
+
column: 'xl:flex-col xl:*:basis-auto'
|
|
110
|
+
}
|
|
100
111
|
};
|
|
101
112
|
const visibleFromClasses = {
|
|
102
113
|
small: '',
|
|
@@ -167,9 +178,10 @@ export const pane = tv({
|
|
|
167
178
|
lg: { content: 'gap-spacing-200' },
|
|
168
179
|
xl: { content: 'gap-spacing-300' }
|
|
169
180
|
},
|
|
181
|
+
// The basis outside a PaneGrid; inside one, the grid's direction sets it from --pane-basis.
|
|
170
182
|
flexible: {
|
|
171
|
-
true: { base: 'min-w-spacing-0 grow basis-spacing-0' },
|
|
172
|
-
false: { base: 'shrink-0' }
|
|
183
|
+
true: { base: 'min-w-spacing-0 grow basis-spacing-0 [--pane-basis:0px]' },
|
|
184
|
+
false: { base: 'shrink-0 basis-(--pane-basis)' }
|
|
173
185
|
},
|
|
174
186
|
sticky: {
|
|
175
187
|
true: {
|
|
@@ -178,6 +190,20 @@ export const pane = tv({
|
|
|
178
190
|
false: ''
|
|
179
191
|
}
|
|
180
192
|
},
|
|
193
|
+
compoundVariants: [
|
|
194
|
+
/*
|
|
195
|
+
A full pane is a page that scrolls with the window, so its content ends 72dp (a 56dp FAB
|
|
196
|
+
plus its 16dp margin) above the pane's bottom edge instead of on the preset's bottom
|
|
197
|
+
padding. The last line can then scroll clear of a FAB (M3: keep a FAB from covering
|
|
198
|
+
content at the end of a scroll) and doesn't finish flush against the window edge. Each
|
|
199
|
+
tier restates it, since the presets set `md:py-*`/`lg:py-*`.
|
|
200
|
+
*/
|
|
201
|
+
{
|
|
202
|
+
full: true,
|
|
203
|
+
padding: ['sm', 'md', 'lg'],
|
|
204
|
+
class: { content: 'pb-spacing-900 md:pb-spacing-900 lg:pb-spacing-900' }
|
|
205
|
+
}
|
|
206
|
+
],
|
|
181
207
|
defaultVariants: {
|
|
182
208
|
rounded: true,
|
|
183
209
|
flexible: true
|
|
@@ -4,20 +4,20 @@ export declare const popover: import("tailwind-variants").TVReturnType<{
|
|
|
4
4
|
[key: string]: {
|
|
5
5
|
[key: string]: import("tailwind-variants").ClassValue | {
|
|
6
6
|
title?: import("tailwind-variants").ClassValue;
|
|
7
|
-
close?: import("tailwind-variants").ClassValue;
|
|
8
7
|
base?: import("tailwind-variants").ClassValue;
|
|
9
8
|
body?: import("tailwind-variants").ClassValue;
|
|
10
9
|
header?: import("tailwind-variants").ClassValue;
|
|
10
|
+
close?: import("tailwind-variants").ClassValue;
|
|
11
11
|
};
|
|
12
12
|
};
|
|
13
13
|
} | {
|
|
14
14
|
[x: string]: {
|
|
15
15
|
[x: string]: import("tailwind-variants").ClassValue | {
|
|
16
16
|
title?: import("tailwind-variants").ClassValue;
|
|
17
|
-
close?: import("tailwind-variants").ClassValue;
|
|
18
17
|
base?: import("tailwind-variants").ClassValue;
|
|
19
18
|
body?: import("tailwind-variants").ClassValue;
|
|
20
19
|
header?: import("tailwind-variants").ClassValue;
|
|
20
|
+
close?: import("tailwind-variants").ClassValue;
|
|
21
21
|
};
|
|
22
22
|
};
|
|
23
23
|
} | {}, {
|
|
@@ -30,10 +30,10 @@ export declare const popover: import("tailwind-variants").TVReturnType<{
|
|
|
30
30
|
[key: string]: {
|
|
31
31
|
[key: string]: import("tailwind-variants").ClassValue | {
|
|
32
32
|
title?: import("tailwind-variants").ClassValue;
|
|
33
|
-
close?: import("tailwind-variants").ClassValue;
|
|
34
33
|
base?: import("tailwind-variants").ClassValue;
|
|
35
34
|
body?: import("tailwind-variants").ClassValue;
|
|
36
35
|
header?: import("tailwind-variants").ClassValue;
|
|
36
|
+
close?: import("tailwind-variants").ClassValue;
|
|
37
37
|
};
|
|
38
38
|
};
|
|
39
39
|
} | {}, {
|
|
@@ -251,10 +251,10 @@ export declare const dateCalendar: import("tailwind-variants").TVReturnType<{
|
|
|
251
251
|
[key: string]: {
|
|
252
252
|
[key: string]: import("tailwind-variants").ClassValue | {
|
|
253
253
|
grid?: import("tailwind-variants").ClassValue;
|
|
254
|
-
row?: import("tailwind-variants").ClassValue;
|
|
255
|
-
surface?: import("tailwind-variants").ClassValue;
|
|
256
254
|
header?: import("tailwind-variants").ClassValue;
|
|
257
255
|
nav?: import("tailwind-variants").ClassValue;
|
|
256
|
+
row?: import("tailwind-variants").ClassValue;
|
|
257
|
+
surface?: import("tailwind-variants").ClassValue;
|
|
258
258
|
cell?: import("tailwind-variants").ClassValue;
|
|
259
259
|
heading?: import("tailwind-variants").ClassValue;
|
|
260
260
|
weekday?: import("tailwind-variants").ClassValue;
|
|
@@ -265,10 +265,10 @@ export declare const dateCalendar: import("tailwind-variants").TVReturnType<{
|
|
|
265
265
|
[x: string]: {
|
|
266
266
|
[x: string]: import("tailwind-variants").ClassValue | {
|
|
267
267
|
grid?: import("tailwind-variants").ClassValue;
|
|
268
|
-
row?: import("tailwind-variants").ClassValue;
|
|
269
|
-
surface?: import("tailwind-variants").ClassValue;
|
|
270
268
|
header?: import("tailwind-variants").ClassValue;
|
|
271
269
|
nav?: import("tailwind-variants").ClassValue;
|
|
270
|
+
row?: import("tailwind-variants").ClassValue;
|
|
271
|
+
surface?: import("tailwind-variants").ClassValue;
|
|
272
272
|
cell?: import("tailwind-variants").ClassValue;
|
|
273
273
|
heading?: import("tailwind-variants").ClassValue;
|
|
274
274
|
weekday?: import("tailwind-variants").ClassValue;
|
|
@@ -289,10 +289,10 @@ export declare const dateCalendar: import("tailwind-variants").TVReturnType<{
|
|
|
289
289
|
[key: string]: {
|
|
290
290
|
[key: string]: import("tailwind-variants").ClassValue | {
|
|
291
291
|
grid?: import("tailwind-variants").ClassValue;
|
|
292
|
-
row?: import("tailwind-variants").ClassValue;
|
|
293
|
-
surface?: import("tailwind-variants").ClassValue;
|
|
294
292
|
header?: import("tailwind-variants").ClassValue;
|
|
295
293
|
nav?: import("tailwind-variants").ClassValue;
|
|
294
|
+
row?: import("tailwind-variants").ClassValue;
|
|
295
|
+
surface?: import("tailwind-variants").ClassValue;
|
|
296
296
|
cell?: import("tailwind-variants").ClassValue;
|
|
297
297
|
heading?: import("tailwind-variants").ClassValue;
|
|
298
298
|
weekday?: import("tailwind-variants").ClassValue;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { tv } from '../../../utils/tv.js';
|
|
2
2
|
export const checkbox = tv({
|
|
3
3
|
slots: {
|
|
4
|
-
root: 'group inline-flex min-h-spacing-500 select-none items-start gap-spacing-150 text-md-sys-color-on-surface
|
|
4
|
+
root: 'group inline-flex min-h-spacing-500 select-none items-start gap-spacing-150 text-md-sys-color-on-surface cursor-pointer',
|
|
5
5
|
container: 'relative inline-flex size-[18px] shrink-0',
|
|
6
6
|
control: 'layer-container absolute -inset-[11px] rounded-full text-md-sys-color-on-surface-variant state-layer before:rounded-full group-focus-visible:outline group-focus-visible:outline-3 group-focus-visible:outline-offset-2 group-focus-visible:outline-md-sys-color-secondary transition-colors md-sys-motion-fast-effects',
|
|
7
7
|
box: 'absolute inset-[11px] rounded-[4px] border-2 border-current bg-md-sys-color-surface transition-colors md-sys-motion-fast-effects',
|
|
@@ -4,8 +4,8 @@ export declare const command: import("tailwind-variants").TVReturnType<{
|
|
|
4
4
|
[key: string]: {
|
|
5
5
|
[key: string]: import("tailwind-variants").ClassValue | {
|
|
6
6
|
item?: import("tailwind-variants").ClassValue;
|
|
7
|
-
input?: import("tailwind-variants").ClassValue;
|
|
8
7
|
base?: import("tailwind-variants").ClassValue;
|
|
8
|
+
input?: import("tailwind-variants").ClassValue;
|
|
9
9
|
group?: import("tailwind-variants").ClassValue;
|
|
10
10
|
list?: import("tailwind-variants").ClassValue;
|
|
11
11
|
separator?: import("tailwind-variants").ClassValue;
|
|
@@ -19,8 +19,8 @@ export declare const command: import("tailwind-variants").TVReturnType<{
|
|
|
19
19
|
[x: string]: {
|
|
20
20
|
[x: string]: import("tailwind-variants").ClassValue | {
|
|
21
21
|
item?: import("tailwind-variants").ClassValue;
|
|
22
|
-
input?: import("tailwind-variants").ClassValue;
|
|
23
22
|
base?: import("tailwind-variants").ClassValue;
|
|
23
|
+
input?: import("tailwind-variants").ClassValue;
|
|
24
24
|
group?: import("tailwind-variants").ClassValue;
|
|
25
25
|
list?: import("tailwind-variants").ClassValue;
|
|
26
26
|
separator?: import("tailwind-variants").ClassValue;
|
|
@@ -45,8 +45,8 @@ export declare const command: import("tailwind-variants").TVReturnType<{
|
|
|
45
45
|
[key: string]: {
|
|
46
46
|
[key: string]: import("tailwind-variants").ClassValue | {
|
|
47
47
|
item?: import("tailwind-variants").ClassValue;
|
|
48
|
-
input?: import("tailwind-variants").ClassValue;
|
|
49
48
|
base?: import("tailwind-variants").ClassValue;
|
|
49
|
+
input?: import("tailwind-variants").ClassValue;
|
|
50
50
|
group?: import("tailwind-variants").ClassValue;
|
|
51
51
|
list?: import("tailwind-variants").ClassValue;
|
|
52
52
|
separator?: import("tailwind-variants").ClassValue;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script module lang="ts">
|
|
2
2
|
import { defineMeta } from '@storybook/addon-svelte-csf';
|
|
3
3
|
import Avatar from './Avatar.svelte';
|
|
4
|
+
import { shapeNames } from '../../animation/shapeMorph.svelte.js';
|
|
4
5
|
|
|
5
6
|
const { Story } = defineMeta({
|
|
6
7
|
title: 'Misc/Avatar',
|
|
@@ -9,18 +10,34 @@
|
|
|
9
10
|
argTypes: {
|
|
10
11
|
size: { control: 'select', options: ['sm', 'md', 'lg'] },
|
|
11
12
|
seed: { control: 'text' },
|
|
12
|
-
dicebearStyle: { control: 'text' }
|
|
13
|
+
dicebearStyle: { control: 'text' },
|
|
14
|
+
shape: { control: 'select', options: shapeNames },
|
|
15
|
+
spin: { control: 'inline-radio', options: ['none', 'clockwise', 'counterclockwise'] }
|
|
13
16
|
},
|
|
14
17
|
args: {
|
|
15
18
|
size: 'lg',
|
|
16
|
-
seed: 'ogonek'
|
|
19
|
+
seed: 'ogonek',
|
|
20
|
+
shape: 'circle',
|
|
21
|
+
spin: 'none'
|
|
17
22
|
}
|
|
18
23
|
});
|
|
19
24
|
</script>
|
|
20
25
|
|
|
26
|
+
<script lang="ts">
|
|
27
|
+
const morphShapes = ['circle', 'sunny', 'fourSidedCookie', 'pentagon', 'flower'] as const;
|
|
28
|
+
let morphIndex = $state(0);
|
|
29
|
+
const morphShape = $derived(morphShapes[morphIndex % morphShapes.length]);
|
|
30
|
+
</script>
|
|
31
|
+
|
|
21
32
|
<Story name="Playground">
|
|
22
33
|
{#snippet template(args)}
|
|
23
|
-
<Avatar
|
|
34
|
+
<Avatar
|
|
35
|
+
size={args.size}
|
|
36
|
+
seed={args.seed}
|
|
37
|
+
dicebearStyle={args.dicebearStyle}
|
|
38
|
+
shape={args.shape}
|
|
39
|
+
spin={args.spin}
|
|
40
|
+
/>
|
|
24
41
|
{/snippet}
|
|
25
42
|
</Story>
|
|
26
43
|
|
|
@@ -55,10 +72,42 @@
|
|
|
55
72
|
<Avatar seed="clickable" onclick={() => console.log('Avatar clicked')} />
|
|
56
73
|
</Story>
|
|
57
74
|
|
|
58
|
-
<Story name="
|
|
75
|
+
<Story name="Styles" asChild>
|
|
76
|
+
<!-- gaze with the animation tag is the default; ['!animation'] forces the still variant. -->
|
|
59
77
|
<div class="gap-spacing-200 flex items-end">
|
|
60
|
-
<Avatar seed="alice"
|
|
61
|
-
<Avatar seed="
|
|
62
|
-
<Avatar seed="
|
|
78
|
+
<Avatar seed="alice" />
|
|
79
|
+
<Avatar seed="alice" tags={['!animation']} />
|
|
80
|
+
<Avatar seed="bob" dicebearStyle="shapes" />
|
|
81
|
+
<Avatar seed="carol" dicebearStyle="blobs" />
|
|
82
|
+
</div>
|
|
83
|
+
</Story>
|
|
84
|
+
|
|
85
|
+
<Story name="Shapes" asChild>
|
|
86
|
+
<div class="gap-spacing-200 grid grid-cols-5 sm:grid-cols-7">
|
|
87
|
+
{#each shapeNames as name (name)}
|
|
88
|
+
<Avatar size="md" seed={name} shape={name} title={name} />
|
|
89
|
+
{/each}
|
|
90
|
+
</div>
|
|
91
|
+
</Story>
|
|
92
|
+
|
|
93
|
+
<Story name="Shape morph" asChild>
|
|
94
|
+
<div class="gap-spacing-200 flex items-center">
|
|
95
|
+
<Avatar seed="morph" shape={morphShape} onclick={() => morphIndex++} />
|
|
96
|
+
<span class="md-sys-typescale-body-medium text-md-sys-color-on-surface-variant">
|
|
97
|
+
Click the avatar: {morphShape}
|
|
98
|
+
</span>
|
|
99
|
+
</div>
|
|
100
|
+
</Story>
|
|
101
|
+
|
|
102
|
+
<Story name="Spin" asChild>
|
|
103
|
+
<div class="gap-spacing-200 flex items-end">
|
|
104
|
+
<Avatar seed="clockwise" shape="sunny" spin="clockwise" />
|
|
105
|
+
<Avatar seed="counter" shape="eightLeafClover" spin="counterclockwise" />
|
|
106
|
+
<Avatar
|
|
107
|
+
seed="burst"
|
|
108
|
+
shape="softBurst"
|
|
109
|
+
spin="clockwise"
|
|
110
|
+
src="https://picsum.photos/seed/ogonek-2/200"
|
|
111
|
+
/>
|
|
63
112
|
</div>
|
|
64
113
|
</Story>
|
|
@@ -1,19 +1,4 @@
|
|
|
1
1
|
import Avatar from './Avatar.svelte';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
$$bindings?: Bindings;
|
|
5
|
-
} & Exports;
|
|
6
|
-
(internal: unknown, props: {
|
|
7
|
-
$$events?: Events;
|
|
8
|
-
$$slots?: Slots;
|
|
9
|
-
}): Exports & {
|
|
10
|
-
$set?: any;
|
|
11
|
-
$on?: any;
|
|
12
|
-
};
|
|
13
|
-
z_$$bindings?: Bindings;
|
|
14
|
-
}
|
|
15
|
-
declare const Avatar: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
16
|
-
[evt: string]: CustomEvent<any>;
|
|
17
|
-
}, {}, {}, string>;
|
|
18
|
-
type Avatar = InstanceType<typeof Avatar>;
|
|
2
|
+
declare const Avatar: import("svelte").Component<Record<string, never>, {}, "">;
|
|
3
|
+
type Avatar = ReturnType<typeof Avatar>;
|
|
19
4
|
export default Avatar;
|
|
@@ -2,11 +2,53 @@
|
|
|
2
2
|
@component
|
|
3
3
|
Avatars can be used to represent people or objects.
|
|
4
4
|
|
|
5
|
+
The avatar is cut to one of the M3 Expressive shapes (`shape`, a circle by default), morphing on
|
|
6
|
+
`fastSpatial` when the shape changes. `spin` turns the shape at a constant speed, clockwise or
|
|
7
|
+
counterclockwise, while the picture inside stays upright.
|
|
8
|
+
|
|
5
9
|
@see https://m3.material.io/components/avatars/overview
|
|
10
|
+
@see https://m3.material.io/styles/shape
|
|
6
11
|
-->
|
|
12
|
+
<script module lang="ts">
|
|
13
|
+
import { animate } from 'motion';
|
|
14
|
+
import { prefersReducedMotion } from '../../animation/reducedMotion.js';
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
One full turn of a spinning shape. A constant, linear spin has no spring to take from the
|
|
18
|
+
motion tokens, like the loading indicator's global rotation (4.666s a turn); an avatar is
|
|
19
|
+
decoration beside content, not a progress signal, so it turns about three times slower.
|
|
20
|
+
*/
|
|
21
|
+
const SPIN_TURN_S = 14;
|
|
22
|
+
|
|
23
|
+
/*
|
|
24
|
+
The shapes sit in a 48 × 48 box with the circle spanning 8..40, so this maps the circle onto
|
|
25
|
+
the avatar's bounding box (clip-path objectBoundingBox units are 0..1). Wider shapes
|
|
26
|
+
(clamshell, the bursts) run to the avatar's edges.
|
|
27
|
+
*/
|
|
28
|
+
const FIT = 'scale(0.03125) translate(-8 -8)';
|
|
29
|
+
|
|
30
|
+
/** Turns the clip path around the shape's centre; never under reduced motion (decorative). */
|
|
31
|
+
const spinClip = (path: SVGPathElement, direction: 'clockwise' | 'counterclockwise') => {
|
|
32
|
+
if (prefersReducedMotion()) return;
|
|
33
|
+
const sign = direction === 'clockwise' ? 1 : -1;
|
|
34
|
+
const controls = animate(0, 360, {
|
|
35
|
+
duration: SPIN_TURN_S,
|
|
36
|
+
ease: 'linear',
|
|
37
|
+
repeat: Infinity,
|
|
38
|
+
onUpdate: (deg) => path.setAttribute('transform', `${FIT} rotate(${sign * deg} 24 24)`)
|
|
39
|
+
});
|
|
40
|
+
return () => {
|
|
41
|
+
controls.stop();
|
|
42
|
+
path.setAttribute('transform', FIT);
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
</script>
|
|
46
|
+
|
|
7
47
|
<script lang="ts">
|
|
48
|
+
import { untrack } from 'svelte';
|
|
8
49
|
import { Avatar as AvatarPrimitive } from 'bits-ui';
|
|
9
50
|
import { Layer } from '../../utils/index.js';
|
|
51
|
+
import { animatableShapesSmall, shapeMorph } from '../../animation/shapeMorph.svelte.js';
|
|
10
52
|
import { avatar } from './theme.js';
|
|
11
53
|
import { DicebearAvatarBuilder } from './DicebearAvatarBuilder.js';
|
|
12
54
|
import { getMaterialAvatarPalette } from './materialAvatarPalette.js';
|
|
@@ -16,9 +58,11 @@ Avatars can be used to represent people or objects.
|
|
|
16
58
|
src,
|
|
17
59
|
alt = 'User avatar',
|
|
18
60
|
seed = 'ogonek',
|
|
19
|
-
dicebearStyle = '
|
|
61
|
+
dicebearStyle = 'gaze',
|
|
20
62
|
backgroundColor,
|
|
21
|
-
tags,
|
|
63
|
+
tags = ['animation'],
|
|
64
|
+
shape = 'circle',
|
|
65
|
+
spin = 'none',
|
|
22
66
|
size = 'lg',
|
|
23
67
|
loadingStatus = $bindable('loading'),
|
|
24
68
|
class: className,
|
|
@@ -34,20 +78,25 @@ Avatars can be used to represent people or objects.
|
|
|
34
78
|
lg: 96
|
|
35
79
|
} as const;
|
|
36
80
|
|
|
81
|
+
const uid = $props.id();
|
|
82
|
+
const clipId = `m3-avatar-${uid}`;
|
|
83
|
+
// Rendered into the markup so SSR draws the shape; later changes morph from here.
|
|
84
|
+
const initialShape = untrack(() => animatableShapesSmall[shape]);
|
|
85
|
+
|
|
37
86
|
let generatedAvatar = $state('');
|
|
38
87
|
|
|
39
88
|
$effect(() => {
|
|
40
89
|
const sizePx = sizeMap[size ?? 'lg'] ?? 96;
|
|
41
90
|
const currentSeed = seed;
|
|
42
|
-
const currentStyle = dicebearStyle ?? '
|
|
91
|
+
const currentStyle = dicebearStyle ?? 'gaze';
|
|
43
92
|
const palette = backgroundColor ?? getMaterialAvatarPalette();
|
|
44
93
|
const currentTags = tags;
|
|
45
94
|
let cancelled = false;
|
|
46
95
|
|
|
96
|
+
// Square: the clip path gives the avatar its shape.
|
|
47
97
|
const builder = new DicebearAvatarBuilder(currentStyle)
|
|
48
98
|
.seed(currentSeed)
|
|
49
99
|
.size(sizePx)
|
|
50
|
-
.borderRadius(50)
|
|
51
100
|
.background(palette);
|
|
52
101
|
|
|
53
102
|
if (currentTags) builder.tags(currentTags);
|
|
@@ -62,15 +111,33 @@ Avatars can be used to represent people or objects.
|
|
|
62
111
|
};
|
|
63
112
|
});
|
|
64
113
|
|
|
65
|
-
const { root, image, fallback, button } = $derived(avatar({ size }));
|
|
114
|
+
const { root, image, fallback, button, clip } = $derived(avatar({ size }));
|
|
66
115
|
|
|
67
116
|
const rootClasses = $derived(root({ class: className }));
|
|
68
117
|
</script>
|
|
69
118
|
|
|
119
|
+
<svg class={clip()} aria-hidden="true">
|
|
120
|
+
<clipPath id={clipId} clipPathUnits="objectBoundingBox">
|
|
121
|
+
<path
|
|
122
|
+
d={initialShape}
|
|
123
|
+
transform={FIT}
|
|
124
|
+
{@attach shapeMorph(() => animatableShapesSmall[shape])}
|
|
125
|
+
{@attach (path: SVGPathElement) => (spin === 'none' ? undefined : spinClip(path, spin))}
|
|
126
|
+
/>
|
|
127
|
+
</clipPath>
|
|
128
|
+
</svg>
|
|
129
|
+
|
|
70
130
|
{#if onclick}
|
|
71
131
|
<button class={button({ class: rootClasses })} {onclick} type="button" data-cy="m3-avatar">
|
|
72
|
-
|
|
73
|
-
<AvatarPrimitive.Root
|
|
132
|
+
<!-- The state layer sits inside the clipped root, so hover and press take the shape too. -->
|
|
133
|
+
<AvatarPrimitive.Root
|
|
134
|
+
{...rest}
|
|
135
|
+
bind:loadingStatus
|
|
136
|
+
class="relative h-full w-full"
|
|
137
|
+
style="clip-path: url(#{clipId})"
|
|
138
|
+
data-shape={shape}
|
|
139
|
+
>
|
|
140
|
+
<Layer />
|
|
74
141
|
{#if src}
|
|
75
142
|
<AvatarPrimitive.Image {src} {alt} class={image()} />
|
|
76
143
|
{/if}
|
|
@@ -82,7 +149,14 @@ Avatars can be used to represent people or objects.
|
|
|
82
149
|
</AvatarPrimitive.Root>
|
|
83
150
|
</button>
|
|
84
151
|
{:else}
|
|
85
|
-
<AvatarPrimitive.Root
|
|
152
|
+
<AvatarPrimitive.Root
|
|
153
|
+
{...rest}
|
|
154
|
+
bind:loadingStatus
|
|
155
|
+
class={rootClasses}
|
|
156
|
+
style="clip-path: url(#{clipId})"
|
|
157
|
+
data-cy="m3-avatar"
|
|
158
|
+
data-shape={shape}
|
|
159
|
+
>
|
|
86
160
|
{#if src}
|
|
87
161
|
<AvatarPrimitive.Image {src} {alt} class={image()} />
|
|
88
162
|
{/if}
|
|
@@ -2,7 +2,12 @@ import type { AvatarProps } from './types.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Avatars can be used to represent people or objects.
|
|
4
4
|
*
|
|
5
|
+
* The avatar is cut to one of the M3 Expressive shapes (`shape`, a circle by default), morphing on
|
|
6
|
+
* `fastSpatial` when the shape changes. `spin` turns the shape at a constant speed, clockwise or
|
|
7
|
+
* counterclockwise, while the picture inside stays upright.
|
|
8
|
+
*
|
|
5
9
|
* @see https://m3.material.io/components/avatars/overview
|
|
10
|
+
* @see https://m3.material.io/styles/shape
|
|
6
11
|
*/
|
|
7
12
|
declare const Avatar: import("svelte").Component<AvatarProps, {}, "loadingStatus">;
|
|
8
13
|
type Avatar = ReturnType<typeof Avatar>;
|
|
@@ -21,6 +21,8 @@ export declare const avatar: import("tailwind-variants").TVReturnType<{
|
|
|
21
21
|
image: string;
|
|
22
22
|
fallback: string;
|
|
23
23
|
button: string;
|
|
24
|
+
/** The zero-size SVG holding the shape's clip path. */
|
|
25
|
+
clip: string;
|
|
24
26
|
}, undefined, {
|
|
25
27
|
size: {
|
|
26
28
|
xxs: {
|
|
@@ -44,6 +46,8 @@ export declare const avatar: import("tailwind-variants").TVReturnType<{
|
|
|
44
46
|
image: string;
|
|
45
47
|
fallback: string;
|
|
46
48
|
button: string;
|
|
49
|
+
/** The zero-size SVG holding the shape's clip path. */
|
|
50
|
+
clip: string;
|
|
47
51
|
}, import("tailwind-variants").TVReturnTypeLike<{
|
|
48
52
|
size: {
|
|
49
53
|
xxs: {
|
|
@@ -67,4 +71,6 @@ export declare const avatar: import("tailwind-variants").TVReturnType<{
|
|
|
67
71
|
image: string;
|
|
68
72
|
fallback: string;
|
|
69
73
|
button: string;
|
|
74
|
+
/** The zero-size SVG holding the shape's clip path. */
|
|
75
|
+
clip: string;
|
|
70
76
|
}>>;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { tv } from '../../utils/tv.js';
|
|
2
2
|
export const avatar = tv({
|
|
3
3
|
slots: {
|
|
4
|
-
root: 'relative flex aspect-square shrink-0 items-center justify-center overflow-hidden
|
|
4
|
+
root: 'relative flex aspect-square shrink-0 items-center justify-center overflow-hidden',
|
|
5
5
|
image: 'h-full w-full object-cover',
|
|
6
|
-
fallback: 'flex h-full w-full items-center justify-center
|
|
7
|
-
button: 'relative rounded-full focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-md-sys-color-primary'
|
|
6
|
+
fallback: 'flex h-full w-full items-center justify-center bg-md-sys-color-surface-container-highest',
|
|
7
|
+
button: 'relative rounded-full focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-md-sys-color-primary',
|
|
8
|
+
/** The zero-size SVG holding the shape's clip path. */
|
|
9
|
+
clip: 'pointer-events-none absolute size-spacing-0 overflow-hidden'
|
|
8
10
|
},
|
|
9
11
|
variants: {
|
|
10
12
|
size: {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ShapeName } from '../../animation/shapeMorph.svelte.js';
|
|
1
2
|
import type { Avatar as AvatarPrimitive } from 'bits-ui';
|
|
2
3
|
import type { VariantProps } from 'tailwind-variants';
|
|
3
4
|
import type { DicebearStyleName } from './dicebearStyles.js';
|
|
@@ -15,7 +16,7 @@ export type AvatarProps = AvatarPrimitive.RootProps & {
|
|
|
15
16
|
alt?: string;
|
|
16
17
|
/** The seed for the generated avatar if no src is provided. */
|
|
17
18
|
seed?: string;
|
|
18
|
-
/** The Dicebear style used for the generated fallback avatar. @default '
|
|
19
|
+
/** The Dicebear style used for the generated fallback avatar. @default 'gaze' */
|
|
19
20
|
dicebearStyle?: DicebearStyle;
|
|
20
21
|
/**
|
|
21
22
|
* Hex background colors (no `#`) for the generated fallback avatar. Defaults to the
|
|
@@ -29,8 +30,23 @@ export type AvatarProps = AvatarPrimitive.RootProps & {
|
|
|
29
30
|
* supporting style (`gaze`, `shapes`, `blobs`, …) into its animated variants, or
|
|
30
31
|
* `['!animation']` to force the static one. A no-op for styles without that tag.
|
|
31
32
|
* @see https://www.dicebear.com/guides/filter-variants-with-tags
|
|
33
|
+
* @default ['animation']
|
|
32
34
|
*/
|
|
33
35
|
tags?: string[];
|
|
36
|
+
/**
|
|
37
|
+
* The M3 Expressive shape the avatar is cut to (`shapeNames` lists all 35). Changing it morphs
|
|
38
|
+
* the outline on the `fastSpatial` spring; under reduced motion it swaps instantly.
|
|
39
|
+
* @see https://m3.material.io/styles/shape
|
|
40
|
+
* @default 'circle'
|
|
41
|
+
*/
|
|
42
|
+
shape?: ShapeName;
|
|
43
|
+
/**
|
|
44
|
+
* Turns the shape at a constant speed, one turn every 14s. Only the outline turns; the
|
|
45
|
+
* picture stays upright. Decorative, so reduced motion keeps it still. A circle looks the
|
|
46
|
+
* same at every angle, so pair this with another `shape`.
|
|
47
|
+
* @default 'none'
|
|
48
|
+
*/
|
|
49
|
+
spin?: 'none' | 'clockwise' | 'counterclockwise';
|
|
34
50
|
/** The loading status of the avatar image. */
|
|
35
51
|
loadingStatus?: 'loading' | 'loaded' | 'error';
|
|
36
52
|
/** Callback for when the avatar is clicked. If provided, the avatar is wrapped in a button. */
|
|
@@ -17,6 +17,9 @@ Publishes its measured height as `--appbar-height` on the document root, so any
|
|
|
17
17
|
that amount with no props or wiring on either side — see Pane.svelte's doc
|
|
18
18
|
comment.
|
|
19
19
|
|
|
20
|
+
A `ButtonIcon` anywhere inside it defaults to `variant="standard"`, per M3; pass
|
|
21
|
+
`variant` to override.
|
|
22
|
+
|
|
20
23
|
@see https://m3.material.io/components/app-bars/specs
|
|
21
24
|
-->
|
|
22
25
|
<script lang="ts">
|
|
@@ -24,6 +27,7 @@ comment.
|
|
|
24
27
|
import type { AppBarProps } from './types.js';
|
|
25
28
|
import clsx from 'clsx';
|
|
26
29
|
import ButtonIcon from '../../buttons/ButtonIcon.svelte';
|
|
30
|
+
import { setButtonIconVariant } from '../../buttons/context.js';
|
|
27
31
|
|
|
28
32
|
let {
|
|
29
33
|
children,
|
|
@@ -48,6 +52,10 @@ comment.
|
|
|
48
52
|
...rest
|
|
49
53
|
}: AppBarProps = $props();
|
|
50
54
|
|
|
55
|
+
// M3 app bar icon buttons are standard (no container); a filled one over-emphasizes a back or
|
|
56
|
+
// action button. Applies to buttons passed in `leading`/`trailing`/`children` too.
|
|
57
|
+
setButtonIconVariant('standard');
|
|
58
|
+
|
|
51
59
|
let scrolled = $state(false);
|
|
52
60
|
let barHeight = $state(64);
|
|
53
61
|
|
|
@@ -17,6 +17,9 @@ import type { AppBarProps } from './types.js';
|
|
|
17
17
|
* that amount with no props or wiring on either side — see Pane.svelte's doc
|
|
18
18
|
* comment.
|
|
19
19
|
*
|
|
20
|
+
* A `ButtonIcon` anywhere inside it defaults to `variant="standard"`, per M3; pass
|
|
21
|
+
* `variant` to override.
|
|
22
|
+
*
|
|
20
23
|
* @see https://m3.material.io/components/app-bars/specs
|
|
21
24
|
*/
|
|
22
25
|
declare const AppBar: import("svelte").Component<AppBarProps, {}, "query">;
|
|
@@ -121,7 +121,8 @@ export function appbarSize(size, hasSubtitle) {
|
|
|
121
121
|
}
|
|
122
122
|
export const appbar = tv({
|
|
123
123
|
slots: {
|
|
124
|
-
|
|
124
|
+
// Starts beside a viewport-anchored Rail (styles/components.css), which runs full height.
|
|
125
|
+
base: 'fixed top-spacing-0 left-(--md-rail-inset) right-spacing-0 flex flex-col z-layer-bar transition-colors md-sys-motion-effects',
|
|
125
126
|
row: 'grid w-full items-center px-spacing-50',
|
|
126
127
|
childrenRow: 'w-full px-spacing-100 pb-spacing-100',
|
|
127
128
|
ghost: 'w-full shrink-0 pointer-events-none',
|
|
@@ -140,12 +141,23 @@ export const appbar = tv({
|
|
|
140
141
|
// 1fr side columns centre a small bar's title on the bar, not between uneven button groups.
|
|
141
142
|
center: { row: 'grid-cols-[1fr_auto_1fr]', textContainer: 'items-center text-center' }
|
|
142
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
|
+
*/
|
|
143
152
|
scrolled: {
|
|
144
153
|
true: {
|
|
145
154
|
base: 'bg-md-sys-color-surface-container',
|
|
146
155
|
search: 'bg-md-sys-color-surface-container-highest'
|
|
147
156
|
},
|
|
148
|
-
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
|
+
}
|
|
149
161
|
},
|
|
150
162
|
// Search app bar: the search container fills the text column, 8dp from the buttons.
|
|
151
163
|
searchContainer: {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a destination's `href` is the page at `url` or one of its descendants, for marking
|
|
3
|
+
* the current rail/navbar destination.
|
|
4
|
+
*
|
|
5
|
+
* The href resolves against the page, so a relative one works and one on another origin (a
|
|
6
|
+
* Storybook link on its own port, an external site) never matches, even when its path does.
|
|
7
|
+
* An in-page anchor (`#…`) is never a destination. A destination's own path matches its
|
|
8
|
+
* sub-pages too (`/guides` for `/guides/icons`), except the app's root (`/`, or SvelteKit's `base`
|
|
9
|
+
* path when the app is served from a sub-path), which would match everything.
|
|
10
|
+
*/
|
|
11
|
+
export declare function isCurrentRoute(href: string, url: URL, root?: string): boolean;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a destination's `href` is the page at `url` or one of its descendants, for marking
|
|
3
|
+
* the current rail/navbar destination.
|
|
4
|
+
*
|
|
5
|
+
* The href resolves against the page, so a relative one works and one on another origin (a
|
|
6
|
+
* Storybook link on its own port, an external site) never matches, even when its path does.
|
|
7
|
+
* An in-page anchor (`#…`) is never a destination. A destination's own path matches its
|
|
8
|
+
* sub-pages too (`/guides` for `/guides/icons`), except the app's root (`/`, or SvelteKit's `base`
|
|
9
|
+
* path when the app is served from a sub-path), which would match everything.
|
|
10
|
+
*/
|
|
11
|
+
export function isCurrentRoute(href, url, root = '/') {
|
|
12
|
+
if (!href || href.startsWith('#'))
|
|
13
|
+
return false;
|
|
14
|
+
let target;
|
|
15
|
+
try {
|
|
16
|
+
target = new URL(href, url);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
if (target.origin !== url.origin)
|
|
22
|
+
return false;
|
|
23
|
+
const trim = (path) => (path.length > 1 ? path.replace(/\/+$/, '') : path);
|
|
24
|
+
const path = trim(target.pathname);
|
|
25
|
+
const current = trim(url.pathname);
|
|
26
|
+
const isRoot = path === '/' || path === trim(root || '/');
|
|
27
|
+
return current === path || (!isRoot && current.startsWith(path + '/'));
|
|
28
|
+
}
|