@noxlovette/material 0.6.0 → 0.7.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/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/badge/theme.js +1 -1
- package/dist/components/buttons/button-group/theme.d.ts +1 -1
- package/dist/components/buttons/button-group/theme.js +1 -1
- package/dist/components/buttons/connected/theme.d.ts +1 -1
- package/dist/components/buttons/connected/theme.js +1 -1
- package/dist/components/buttons/split-button/SplitButton.svelte +1 -1
- package/dist/components/buttons/split-button/theme.d.ts +1 -1
- package/dist/components/buttons/split-button/theme.js +1 -1
- package/dist/components/buttons/theme.d.ts +1 -1
- package/dist/components/buttons/theme.js +3 -3
- package/dist/components/cards/theme.js +1 -1
- 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 +183 -22
- package/dist/components/chips/theme.js +117 -26
- package/dist/components/chips/types.d.ts +36 -2
- package/dist/components/containers/action-rail/theme.d.ts +1 -1
- package/dist/components/containers/action-rail/theme.js +2 -2
- package/dist/components/containers/app/theme.d.ts +2 -2
- package/dist/components/containers/app/theme.js +2 -2
- package/dist/components/containers/bottom-sheet/BottomSheet.stories.svelte +1 -1
- package/dist/components/containers/bottom-sheet/theme.d.ts +1 -1
- package/dist/components/containers/bottom-sheet/theme.js +2 -2
- package/dist/components/containers/carousel/theme.d.ts +1 -1
- package/dist/components/containers/carousel/theme.js +1 -1
- package/dist/components/containers/context-menu/theme.d.ts +1 -1
- package/dist/components/containers/context-menu/theme.js +2 -2
- package/dist/components/containers/dialogue/Dialogue.svelte +1 -1
- package/dist/components/containers/dialogue/theme.d.ts +1 -1
- package/dist/components/containers/dialogue/theme.js +3 -3
- package/dist/components/containers/dialogue/types.d.ts +1 -1
- package/dist/components/containers/divider/theme.d.ts +1 -1
- package/dist/components/containers/divider/theme.js +1 -1
- package/dist/components/containers/link-preview/theme.d.ts +1 -1
- package/dist/components/containers/link-preview/theme.js +2 -2
- 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 +7 -1
- package/dist/components/containers/list/theme.js +11 -3
- package/dist/components/containers/menu/Menu.svelte +1 -1
- package/dist/components/containers/menu/MenuSub.svelte +1 -1
- package/dist/components/containers/menu/theme.d.ts +1 -1
- package/dist/components/containers/menu/theme.js +1 -1
- package/dist/components/containers/pane/DraggablePane.svelte +25 -63
- package/dist/components/containers/pane/Pane.svelte +1 -1
- package/dist/components/containers/pane/theme.d.ts +1 -1
- package/dist/components/containers/pane/theme.js +35 -9
- package/dist/components/containers/popover/theme.d.ts +4 -4
- package/dist/components/containers/popover/theme.js +2 -2
- package/dist/components/containers/scroll-area/theme.js +1 -1
- package/dist/components/containers/side-sheet/theme.d.ts +1 -1
- package/dist/components/containers/side-sheet/theme.js +1 -1
- package/dist/components/date/theme.d.ts +7 -7
- package/dist/components/date/theme.js +1 -1
- package/dist/components/forms/checkbox/theme.d.ts +1 -1
- package/dist/components/forms/checkbox/theme.js +2 -2
- package/dist/components/forms/command/theme.d.ts +4 -4
- package/dist/components/forms/command/theme.js +1 -1
- package/dist/components/forms/pin/theme.d.ts +1 -1
- package/dist/components/forms/pin/theme.js +1 -1
- package/dist/components/forms/radio-group/theme.d.ts +1 -1
- package/dist/components/forms/radio-group/theme.js +1 -1
- package/dist/components/forms/search/theme.d.ts +1 -1
- package/dist/components/forms/search/theme.js +1 -1
- package/dist/components/forms/select/Select.svelte +2 -2
- package/dist/components/forms/select/theme.d.ts +1 -1
- package/dist/components/forms/select/theme.js +2 -2
- package/dist/components/forms/slider/theme.d.ts +1 -1
- package/dist/components/forms/slider/theme.js +1 -1
- package/dist/components/forms/switch/theme.d.ts +1 -1
- package/dist/components/forms/switch/theme.js +1 -1
- package/dist/components/forms/textfield/theme.d.ts +1 -1
- package/dist/components/forms/textfield/theme.js +1 -1
- package/dist/components/forms/toggle-group/theme.d.ts +1 -1
- package/dist/components/forms/toggle-group/theme.js +1 -1
- package/dist/components/forms/tooltip/theme.d.ts +1 -1
- package/dist/components/forms/tooltip/theme.js +2 -2
- 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/ThemeSwitcher.svelte +1 -1
- package/dist/components/misc/theme.d.ts +6 -0
- package/dist/components/misc/theme.js +6 -4
- package/dist/components/misc/types.d.ts +17 -1
- package/dist/components/nav/appbar/theme.d.ts +1 -1
- package/dist/components/nav/appbar/theme.js +3 -2
- package/dist/components/nav/breadcrumb/theme.d.ts +1 -1
- package/dist/components/nav/breadcrumb/theme.js +1 -1
- 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/navbar/theme.js +2 -2
- 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 +165 -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 +70 -85
- package/dist/components/nav/rail/theme.js +80 -67
- package/dist/components/nav/rail/types.d.ts +4 -0
- package/dist/components/nav/tabs/Tabs.stories.svelte +2 -1
- package/dist/components/nav/tabs/theme.d.ts +1 -1
- package/dist/components/nav/tabs/theme.js +1 -1
- package/dist/components/pill/theme.d.ts +1 -1
- package/dist/components/pill/theme.js +1 -1
- package/dist/components/progress/LinearProgress.svelte +1 -1
- package/dist/components/snackbar/theme.d.ts +1 -1
- package/dist/components/snackbar/theme.js +2 -2
- package/dist/components/table/theme.d.ts +1 -1
- package/dist/components/table/theme.js +1 -1
- package/dist/components/time/TimeField.svelte +1 -1
- package/dist/components/time/theme.d.ts +1 -1
- package/dist/components/time/theme.js +1 -1
- package/dist/components/toolbar/theme.d.ts +1 -1
- package/dist/components/toolbar/theme.js +2 -2
- package/dist/components/typography/body/theme.d.ts +1 -1
- package/dist/components/typography/body/theme.js +1 -1
- package/dist/components/typography/display/theme.d.ts +1 -1
- package/dist/components/typography/display/theme.js +1 -1
- package/dist/components/typography/headline/theme.d.ts +1 -1
- package/dist/components/typography/headline/theme.js +1 -1
- package/dist/components/typography/kbd/theme.d.ts +1 -1
- package/dist/components/typography/kbd/theme.js +1 -1
- package/dist/components/typography/label/theme.js +1 -1
- package/dist/components/typography/title/theme.d.ts +1 -1
- package/dist/components/typography/title/theme.js +1 -1
- package/dist/index.css +3 -0
- package/dist/styles/components.css +50 -0
- package/dist/styles/icon.css +13 -0
- package/dist/styles/layers.css +33 -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/theme.d.ts +1 -1
- package/dist/utils/icon/theme.js +1 -1
- package/dist/utils/icon/types.d.ts +59 -7
- package/dist/utils/tv.d.ts +5 -0
- package/dist/utils/tv.js +20 -0
- package/package.json +2 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { tv } from '
|
|
1
|
+
import { tv } from '../../../utils/tv.js';
|
|
2
2
|
export const tooltip = tv({
|
|
3
3
|
slots: {
|
|
4
|
-
base: 'min-w-60 py-spacing-150 px-spacing-200 shadow-elevation-2 rounded-md relative z-
|
|
4
|
+
base: 'min-w-60 py-spacing-150 px-spacing-200 shadow-elevation-2 rounded-md relative z-layer-popup pointer-events-auto',
|
|
5
5
|
subhead: '',
|
|
6
6
|
supportingText: '',
|
|
7
7
|
textContainer: '',
|
|
@@ -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>;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
let open = $state(false);
|
|
7
7
|
</script>
|
|
8
8
|
|
|
9
|
-
<div class="right-spacing-200 bottom-spacing-200
|
|
9
|
+
<div class="right-spacing-200 bottom-spacing-200 z-layer-fab fixed hidden md:block">
|
|
10
10
|
<Popover bind:open title="Theme Settings" side="top" align="end" sideOffset={16}>
|
|
11
11
|
{#snippet trigger(triggerProps)}
|
|
12
12
|
<FAB iconProps={{ name: 'palette' }} {...triggerProps} />
|
|
@@ -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
|
-
import { tv } from '
|
|
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. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type VariantProps } from '
|
|
1
|
+
import { type VariantProps } from '../../../utils/tv.js';
|
|
2
2
|
import type { Breakpoint } from '../../containers/pane/theme.js';
|
|
3
3
|
export type AppbarVariants = VariantProps<typeof appbar>;
|
|
4
4
|
export type AppBarSize = 'small' | 'medium' | 'large';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { tv } from '
|
|
1
|
+
import { tv } from '../../../utils/tv.js';
|
|
2
2
|
/*
|
|
3
3
|
M3 Expressive app bar (https://m3.material.io/components/app-bars/specs), laid out on one
|
|
4
4
|
3-column grid (leading · text · trailing) so the size can change per breakpoint in CSS alone:
|
|
@@ -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',
|
|
@@ -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
|
+
}
|
|
@@ -6,12 +6,14 @@ A single destination within a navigation bar.
|
|
|
6
6
|
-->
|
|
7
7
|
<script lang="ts">
|
|
8
8
|
import { page } from '$app/state';
|
|
9
|
+
import { base as appBase } from '$app/paths';
|
|
9
10
|
import type { NavItemProps } from './types.js';
|
|
10
11
|
import { navitem } from './theme.js';
|
|
11
12
|
import { Icon, Layer } from '../../../utils/index.js';
|
|
12
13
|
import Badge from '../../badge/Badge.svelte';
|
|
13
14
|
import { NavigationMenu } from 'bits-ui';
|
|
14
15
|
import clsx from 'clsx';
|
|
16
|
+
import { isCurrentRoute } from '../currentRoute.js';
|
|
15
17
|
|
|
16
18
|
let {
|
|
17
19
|
href = '/',
|
|
@@ -23,12 +25,7 @@ A single destination within a navigation bar.
|
|
|
23
25
|
...rest
|
|
24
26
|
}: NavItemProps = $props();
|
|
25
27
|
|
|
26
|
-
const isActive = $derived(
|
|
27
|
-
selected ||
|
|
28
|
-
page.url.pathname === href ||
|
|
29
|
-
(href !== '/' && page.url.href.startsWith(href)) ||
|
|
30
|
-
page.url.href == href
|
|
31
|
-
);
|
|
28
|
+
const isActive = $derived(selected || isCurrentRoute(href, page.url, appBase));
|
|
32
29
|
|
|
33
30
|
const {
|
|
34
31
|
base,
|
|
@@ -2,18 +2,18 @@ export declare const navbar: import("tailwind-variants").TVReturnType<{
|
|
|
2
2
|
[key: string]: {
|
|
3
3
|
[key: string]: import("tailwind-variants").ClassValue | {
|
|
4
4
|
base?: import("tailwind-variants").ClassValue;
|
|
5
|
+
fab?: import("tailwind-variants").ClassValue;
|
|
5
6
|
items?: import("tailwind-variants").ClassValue;
|
|
6
7
|
ghost?: import("tailwind-variants").ClassValue;
|
|
7
|
-
fab?: import("tailwind-variants").ClassValue;
|
|
8
8
|
};
|
|
9
9
|
};
|
|
10
10
|
} | {
|
|
11
11
|
[x: string]: {
|
|
12
12
|
[x: string]: import("tailwind-variants").ClassValue | {
|
|
13
13
|
base?: import("tailwind-variants").ClassValue;
|
|
14
|
+
fab?: import("tailwind-variants").ClassValue;
|
|
14
15
|
items?: import("tailwind-variants").ClassValue;
|
|
15
16
|
ghost?: import("tailwind-variants").ClassValue;
|
|
16
|
-
fab?: import("tailwind-variants").ClassValue;
|
|
17
17
|
};
|
|
18
18
|
};
|
|
19
19
|
} | {}, {
|
|
@@ -25,9 +25,9 @@ export declare const navbar: import("tailwind-variants").TVReturnType<{
|
|
|
25
25
|
[key: string]: {
|
|
26
26
|
[key: string]: import("tailwind-variants").ClassValue | {
|
|
27
27
|
base?: import("tailwind-variants").ClassValue;
|
|
28
|
+
fab?: import("tailwind-variants").ClassValue;
|
|
28
29
|
items?: import("tailwind-variants").ClassValue;
|
|
29
30
|
ghost?: import("tailwind-variants").ClassValue;
|
|
30
|
-
fab?: import("tailwind-variants").ClassValue;
|
|
31
31
|
};
|
|
32
32
|
};
|
|
33
33
|
} | {}, {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { tv } from '
|
|
1
|
+
import { tv } from '../../../utils/tv.js';
|
|
2
2
|
export const navbar = tv({
|
|
3
3
|
slots: {
|
|
4
|
-
base: 'shadow-elevation-2 bg-md-sys-color-surface-container
|
|
4
|
+
base: 'shadow-elevation-2 bg-md-sys-color-surface-container z-layer-bar flex h-20 fixed bottom-spacing-0 w-full left-spacing-0 justify-around py-spacing-150 md:hidden',
|
|
5
5
|
items: 'flex justify-around w-full',
|
|
6
6
|
fab: 'bottom-24 right-spacing-200 absolute',
|
|
7
7
|
ghost: 'h-20 w-full shrink-0 md:hidden pointer-events-none'
|