@noxlovette/material 0.4.8 → 0.5.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/dist/animation/TransitionPatterns.stories.svelte +357 -0
- package/dist/animation/TransitionPatterns.stories.svelte.d.ts +3 -0
- package/dist/animation/containerTransform.d.ts +26 -12
- package/dist/animation/containerTransform.js +22 -138
- package/dist/animation/enterExit.d.ts +21 -3
- package/dist/animation/enterExit.js +69 -22
- package/dist/animation/index.d.ts +1 -1
- package/dist/animation/index.js +1 -1
- package/dist/animation/presence.svelte.d.ts +47 -0
- package/dist/animation/presence.svelte.js +84 -0
- package/dist/animation/sharedAxisTransition.d.ts +35 -37
- package/dist/animation/sharedAxisTransition.js +50 -22
- package/dist/animation/skeleton.d.ts +11 -0
- package/dist/animation/skeleton.js +23 -0
- package/dist/animation/spring.d.ts +61 -0
- package/dist/animation/spring.js +53 -0
- package/dist/animation/springValue.svelte.d.ts +22 -0
- package/dist/animation/springValue.svelte.js +42 -0
- package/dist/animation/transition.d.ts +3 -1
- package/dist/animation/transition.js +3 -1
- package/dist/attachments/clickOutside.d.ts +13 -0
- package/dist/attachments/clickOutside.js +18 -0
- package/dist/attachments/index.d.ts +1 -0
- package/dist/attachments/index.js +1 -0
- package/dist/components/buttons/FAB.svelte +23 -16
- package/dist/components/buttons/FABMenu.svelte +24 -29
- package/dist/components/buttons/FABMenu.svelte.d.ts +2 -1
- package/dist/components/buttons/FABMenuItem.svelte +23 -20
- package/dist/components/buttons/FABMenuItem.svelte.d.ts +5 -1
- package/dist/components/buttons/split-button/SplitButton.svelte +10 -17
- package/dist/components/buttons/split-button/theme.js +1 -1
- package/dist/components/buttons/types.d.ts +3 -3
- package/dist/components/cards/theme.js +1 -1
- package/dist/components/containers/bottom-sheet/BottomSheet.stories.svelte +8 -12
- package/dist/components/containers/bottom-sheet/BottomSheet.svelte +67 -55
- package/dist/components/containers/bottom-sheet/BottomSheet.svelte.d.ts +1 -1
- package/dist/components/containers/bottom-sheet/types.d.ts +8 -2
- package/dist/components/containers/context-menu/ContextMenu.svelte +23 -36
- package/dist/components/containers/context-menu/theme.js +2 -2
- package/dist/components/containers/dialogue/Dialogue.svelte +49 -65
- package/dist/components/containers/link-preview/LinkPreview.svelte +12 -23
- package/dist/components/containers/link-preview/theme.d.ts +3 -3
- package/dist/components/containers/menu/Menu.svelte +14 -21
- package/dist/components/containers/menu/MenuSub.svelte +10 -17
- package/dist/components/containers/pane/DraggablePane.svelte +90 -5
- package/dist/components/containers/pane/DraggablePane.svelte.d.ts +1 -1
- package/dist/components/containers/pane/theme.js +2 -2
- package/dist/components/containers/popover/Popover.svelte +31 -42
- package/dist/components/containers/popover/theme.d.ts +3 -3
- package/dist/components/containers/scroll-area/theme.js +2 -2
- package/dist/components/containers/side-sheet/SideSheet.stories.svelte +10 -15
- package/dist/components/containers/side-sheet/SideSheet.svelte +44 -44
- package/dist/components/containers/side-sheet/SideSheet.svelte.d.ts +4 -3
- package/dist/components/containers/side-sheet/types.d.ts +8 -2
- package/dist/components/date/DateField.svelte +85 -92
- package/dist/components/date/DateField.svelte.d.ts +2 -5
- package/dist/components/date/DateRangeField.svelte +113 -155
- package/dist/components/date/DateRangeField.svelte.d.ts +2 -5
- package/dist/components/date/theme.d.ts +95 -0
- package/dist/components/date/theme.js +99 -14
- package/dist/components/date/types.d.ts +13 -0
- package/dist/components/forms/checkbox/theme.js +2 -2
- package/dist/components/forms/command/theme.d.ts +3 -3
- package/dist/components/forms/command/theme.js +1 -1
- package/dist/components/forms/pin/theme.js +2 -2
- package/dist/components/forms/radio-group/theme.js +2 -2
- package/dist/components/forms/select/Select.svelte +42 -54
- package/dist/components/forms/select/theme.js +4 -4
- package/dist/components/forms/slider/Slider.svelte +144 -126
- package/dist/components/forms/slider/Slider.svelte.d.ts +3 -0
- package/dist/components/forms/slider/theme.d.ts +154 -48
- package/dist/components/forms/slider/theme.js +60 -20
- package/dist/components/forms/slider/types.d.ts +1 -1
- package/dist/components/forms/switch/theme.js +4 -4
- package/dist/components/forms/textfield/Textfield.svelte +1 -1
- package/dist/components/forms/textfield/theme.d.ts +9 -0
- package/dist/components/forms/textfield/theme.js +26 -9
- package/dist/components/forms/toggle-group/theme.js +1 -1
- package/dist/components/forms/tooltip/Tooltip.svelte +24 -35
- package/dist/components/nav/breadcrumb/theme.js +1 -1
- package/dist/components/nav/navbar/theme.js +2 -2
- package/dist/components/nav/rail/Rail.svelte +4 -5
- package/dist/components/nav/rail/theme.js +7 -7
- package/dist/components/nav/tabs/theme.js +1 -1
- package/dist/components/progress/CircularProgress.svelte +78 -4
- package/dist/components/progress/CircularProgress.svelte.d.ts +5 -0
- package/dist/components/progress/LinearProgress.svelte +70 -7
- package/dist/components/progress/LinearProgress.svelte.d.ts +5 -0
- package/dist/components/progress/WavyLinearProgress.svelte +27 -3
- package/dist/components/progress/WavyLinearProgress.svelte.d.ts +3 -0
- package/dist/components/progress/_wavy.d.ts +2 -0
- package/dist/components/progress/_wavy.js +3 -1
- package/dist/components/snackbar/Snackbar.svelte +13 -17
- package/dist/components/table/TableBody.svelte +3 -16
- package/dist/components/table/TableBody.svelte.d.ts +1 -5
- package/dist/components/table/TableHeader.svelte +1 -1
- package/dist/components/table/theme.js +1 -1
- package/dist/components/table/types.d.ts +0 -7
- package/dist/components/time/TimeField.stories.svelte +41 -0
- package/dist/components/time/TimeField.stories.svelte.d.ts +19 -0
- package/dist/components/time/TimeField.svelte +62 -61
- package/dist/components/toolbar/theme.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/styles/component.css +21 -22
- package/dist/styles/motion.css +317 -38
- package/dist/utils/Layer.svelte +16 -29
- package/dist/utils/icon/LoadingIndicator.svelte +61 -22
- package/dist/utils/icon/loadingShapes.d.ts +1 -0
- package/dist/utils/icon/loadingShapes.js +15 -0
- package/package.json +5 -2
- package/dist/actions/clickOutside.d.ts +0 -3
- package/dist/actions/clickOutside.js +0 -13
- package/dist/actions/floating.d.ts +0 -20
- package/dist/actions/floating.js +0 -87
- package/dist/actions/index.d.ts +0 -6
- package/dist/actions/index.js +0 -6
- package/dist/actions/keyboard.d.ts +0 -3
- package/dist/actions/keyboard.js +0 -14
- package/dist/actions/portal.d.ts +0 -3
- package/dist/actions/portal.js +0 -8
- package/dist/actions/positionFloating.d.ts +0 -10
- package/dist/actions/positionFloating.js +0 -59
- package/dist/actions/rovingFocusGroup.d.ts +0 -20
- package/dist/actions/rovingFocusGroup.js +0 -82
- package/dist/animation/easing.d.ts +0 -6
- package/dist/animation/easing.js +0 -78
- package/dist/animation/outroClass.d.ts +0 -3
- package/dist/animation/outroClass.js +0 -20
- package/dist/animation/transitionTypes.d.ts +0 -8
- package/dist/animation/transitionTypes.js +0 -1
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { on } from 'svelte/events';
|
|
2
|
+
/**
|
|
3
|
+
* Calls `handler` when a click lands outside the element. Listens in the capture phase so
|
|
4
|
+
* it still fires when something inside the page stops propagation.
|
|
5
|
+
*
|
|
6
|
+
* Attach it conditionally so the document listener only exists while there is something to
|
|
7
|
+
* dismiss:
|
|
8
|
+
*
|
|
9
|
+
* ```svelte
|
|
10
|
+
* <div {@attach open && clickOutside(() => (open = false))}>...</div>
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export function clickOutside(handler) {
|
|
14
|
+
return (node) => on(document, 'click', (event) => {
|
|
15
|
+
if (!event.composedPath().includes(node))
|
|
16
|
+
handler(event);
|
|
17
|
+
}, { capture: true });
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './clickOutside.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './clickOutside.js';
|
|
@@ -15,8 +15,7 @@ Floating action buttons (FABs) help people take primary actions.
|
|
|
15
15
|
import type { FABProps } from './types.js';
|
|
16
16
|
import FABMenu from './FABMenu.svelte';
|
|
17
17
|
import { Layer, Icon, LoadingIndicator } from '../../utils/index.js';
|
|
18
|
-
import {
|
|
19
|
-
import { Button, type ButtonRootProps } from 'bits-ui';
|
|
18
|
+
import { Button, DropdownMenu, type ButtonRootProps } from 'bits-ui';
|
|
20
19
|
|
|
21
20
|
let {
|
|
22
21
|
children,
|
|
@@ -30,44 +29,43 @@ Floating action buttons (FABs) help people take primary actions.
|
|
|
30
29
|
expanded = false,
|
|
31
30
|
class: className,
|
|
32
31
|
withMenu,
|
|
32
|
+
onclick,
|
|
33
33
|
...restProps
|
|
34
34
|
}: FABProps = $props();
|
|
35
35
|
|
|
36
36
|
const { base, icon, label: labelClass } = $derived(fab({ size, config, expanded }));
|
|
37
37
|
|
|
38
38
|
let showMenu = $state(false);
|
|
39
|
-
let wrapperEl = $state<HTMLElement | null>(null);
|
|
40
39
|
|
|
41
40
|
const btnCls = $derived(
|
|
42
41
|
base({
|
|
43
42
|
class: clsx(
|
|
44
43
|
className,
|
|
45
|
-
'transition-[border-radius]
|
|
44
|
+
'transition-[border-radius] md-sys-motion-fast-spatial',
|
|
46
45
|
withMenu && showMenu && '!rounded-full'
|
|
47
46
|
)
|
|
48
47
|
})
|
|
49
48
|
);
|
|
50
49
|
|
|
51
|
-
function handleClick(e: MouseEvent) {
|
|
50
|
+
function handleClick(e: MouseEvent, triggerClick?: unknown) {
|
|
52
51
|
if (disabled || loading) {
|
|
53
52
|
e.preventDefault();
|
|
54
53
|
return;
|
|
55
54
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
showMenu = !showMenu;
|
|
59
|
-
}
|
|
55
|
+
(onclick as ((e: MouseEvent) => void) | null | undefined)?.(e);
|
|
56
|
+
if (typeof triggerClick === 'function') triggerClick(e);
|
|
60
57
|
}
|
|
61
58
|
</script>
|
|
62
59
|
|
|
63
|
-
|
|
60
|
+
{#snippet fabButton(triggerProps: Record<string, unknown>)}
|
|
64
61
|
<Button.Root
|
|
65
62
|
{disabled}
|
|
66
63
|
{formaction}
|
|
67
|
-
class={btnCls}
|
|
68
|
-
onclick={handleClick}
|
|
69
64
|
data-cy="m3-fab"
|
|
70
65
|
{...restProps as ButtonRootProps}
|
|
66
|
+
{...triggerProps}
|
|
67
|
+
class={btnCls}
|
|
68
|
+
onclick={(e: MouseEvent) => handleClick(e, triggerProps.onclick)}
|
|
71
69
|
>
|
|
72
70
|
{#if loading}
|
|
73
71
|
<LoadingIndicator />
|
|
@@ -81,10 +79,19 @@ Floating action buttons (FABs) help people take primary actions.
|
|
|
81
79
|
</p>
|
|
82
80
|
{/if}
|
|
83
81
|
</Button.Root>
|
|
82
|
+
{/snippet}
|
|
84
83
|
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
{#if withMenu}
|
|
85
|
+
<DropdownMenu.Root bind:open={showMenu}>
|
|
86
|
+
<DropdownMenu.Trigger disabled={disabled || loading}>
|
|
87
|
+
{#snippet child({ props })}
|
|
88
|
+
{@render fabButton(props)}
|
|
89
|
+
{/snippet}
|
|
90
|
+
</DropdownMenu.Trigger>
|
|
91
|
+
<FABMenu>
|
|
87
92
|
{@render children?.()}
|
|
88
93
|
</FABMenu>
|
|
89
|
-
|
|
90
|
-
|
|
94
|
+
</DropdownMenu.Root>
|
|
95
|
+
{:else}
|
|
96
|
+
{@render fabButton({})}
|
|
97
|
+
{/if}
|
|
@@ -1,42 +1,37 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
@component
|
|
3
|
-
|
|
3
|
+
The floating panel of a FAB menu. Rendered by `FAB` when `withMenu` is set — must live inside
|
|
4
|
+
its `DropdownMenu.Root`, which owns open state, positioning, and outside-click/Escape dismissal.
|
|
4
5
|
Should be used with `FABMenuItem` as children.
|
|
5
6
|
-->
|
|
6
7
|
<script lang="ts">
|
|
7
8
|
import clsx from 'clsx';
|
|
8
|
-
import {
|
|
9
|
+
import { DropdownMenu } from 'bits-ui';
|
|
9
10
|
import { fabMenu } from './theme.js';
|
|
10
11
|
import type { FABMenuProps } from './types.js';
|
|
11
|
-
import { enterExit } from '../../animation/
|
|
12
|
-
import { easeEmphasizedAccel, easeEmphasizedDecel } from '../../animation/easing.js';
|
|
13
|
-
import { floating, portal } from '../../actions/index.js';
|
|
12
|
+
import { enterExit, presence } from '../../animation/index.js';
|
|
14
13
|
|
|
15
|
-
let { children, class: className,
|
|
14
|
+
let { children, class: className, ...restProps }: FABMenuProps = $props();
|
|
16
15
|
|
|
17
16
|
const { base } = fabMenu();
|
|
18
17
|
</script>
|
|
19
18
|
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
>
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
.fab-menu {
|
|
40
|
-
transform-origin: bottom right;
|
|
41
|
-
}
|
|
42
|
-
</style>
|
|
19
|
+
<DropdownMenu.Portal>
|
|
20
|
+
<DropdownMenu.Content side="top" align="end" sideOffset={8} collisionPadding={8}>
|
|
21
|
+
{#snippet child({ wrapperProps, props, open })}
|
|
22
|
+
<div {...wrapperProps}>
|
|
23
|
+
<div
|
|
24
|
+
data-cy="m3-fab-menu"
|
|
25
|
+
{...restProps}
|
|
26
|
+
{...props}
|
|
27
|
+
class={base({
|
|
28
|
+
class: clsx(className, 'overflow-y-auto')
|
|
29
|
+
})}
|
|
30
|
+
{@attach presence(() => open, enterExit.scale)}
|
|
31
|
+
>
|
|
32
|
+
{@render children()}
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
{/snippet}
|
|
36
|
+
</DropdownMenu.Content>
|
|
37
|
+
</DropdownMenu.Portal>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { FABMenuProps } from './types.js';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* The floating panel of a FAB menu. Rendered by `FAB` when `withMenu` is set — must live inside
|
|
4
|
+
* its `DropdownMenu.Root`, which owns open state, positioning, and outside-click/Escape dismissal.
|
|
4
5
|
* Should be used with `FABMenuItem` as children.
|
|
5
6
|
*/
|
|
6
7
|
declare const FABMenu: import("svelte").Component<FABMenuProps, {}, "">;
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
@component
|
|
3
|
-
An individual action within a FAB menu.
|
|
3
|
+
An individual action within a FAB menu. Wraps bits-ui's `DropdownMenu.Item`, so it gets the
|
|
4
|
+
`menuitem` role, arrow-key navigation, and closes the menu when selected. Must be rendered
|
|
5
|
+
inside a `FAB` with `withMenu`.
|
|
4
6
|
-->
|
|
5
7
|
<script lang="ts">
|
|
6
8
|
import clsx from 'clsx';
|
|
7
9
|
import { fabMenuItem } from './theme.js';
|
|
8
10
|
import type { FABMenuItemProps } from './types.js';
|
|
9
11
|
import { Icon, LoadingIndicator, Layer } from '../../utils/index.js';
|
|
10
|
-
import { Button, type
|
|
12
|
+
import { Button, DropdownMenu, type DropdownMenuItemProps } from 'bits-ui';
|
|
11
13
|
|
|
12
14
|
let {
|
|
13
15
|
class: className,
|
|
@@ -16,6 +18,7 @@ An individual action within a FAB menu.
|
|
|
16
18
|
children,
|
|
17
19
|
iconProps,
|
|
18
20
|
formaction,
|
|
21
|
+
disabled,
|
|
19
22
|
...restProps
|
|
20
23
|
}: FABMenuItemProps = $props();
|
|
21
24
|
|
|
@@ -23,21 +26,21 @@ An individual action within a FAB menu.
|
|
|
23
26
|
const btnCls = $derived(base({ class: clsx(className) }));
|
|
24
27
|
</script>
|
|
25
28
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
</
|
|
29
|
+
<!-- Spread onto the Item, not the button: bits-ui merges (and chains handlers of) these into `props`. -->
|
|
30
|
+
<DropdownMenu.Item {disabled} {...restProps as DropdownMenuItemProps}>
|
|
31
|
+
{#snippet child({ props })}
|
|
32
|
+
<Button.Root {formaction} {disabled} data-cy="m3-fab-menu-item" {...props} class={btnCls}>
|
|
33
|
+
{#if iconProps}
|
|
34
|
+
{#if loading}
|
|
35
|
+
<LoadingIndicator />
|
|
36
|
+
{:else}
|
|
37
|
+
<Icon class={icon()} {...iconProps} />
|
|
38
|
+
{/if}
|
|
39
|
+
{:else if loading}
|
|
40
|
+
<LoadingIndicator />
|
|
41
|
+
{/if}
|
|
42
|
+
{@render children?.()}
|
|
43
|
+
<Layer />
|
|
44
|
+
</Button.Root>
|
|
45
|
+
{/snippet}
|
|
46
|
+
</DropdownMenu.Item>
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { FABMenuItemProps } from './types.js';
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* An individual action within a FAB menu. Wraps bits-ui's `DropdownMenu.Item`, so it gets the
|
|
4
|
+
* `menuitem` role, arrow-key navigation, and closes the menu when selected. Must be rendered
|
|
5
|
+
* inside a `FAB` with `withMenu`.
|
|
6
|
+
*/
|
|
3
7
|
declare const FABMenuItem: import("svelte").Component<FABMenuItemProps, {}, "">;
|
|
4
8
|
type FABMenuItem = ReturnType<typeof FABMenuItem>;
|
|
5
9
|
export default FABMenuItem;
|
|
@@ -15,10 +15,9 @@ selectable items, no menu).
|
|
|
15
15
|
@see https://m3.material.io/components/buttons/guidelines#834f5f31-e363-4d0f-bc4d-abac128f5f0e
|
|
16
16
|
-->
|
|
17
17
|
<script lang="ts">
|
|
18
|
+
import { enterExit, presence } from '../../../animation/index.js';
|
|
18
19
|
import { DropdownMenu } from 'bits-ui';
|
|
19
20
|
import clsx from 'clsx';
|
|
20
|
-
import { easeEmphasizedDecel } from '../../../animation/easing.js';
|
|
21
|
-
import { enterExit } from '../../../animation/enterExit.js';
|
|
22
21
|
import Button from '../Button.svelte';
|
|
23
22
|
import ButtonIcon from '../ButtonIcon.svelte';
|
|
24
23
|
import { buttonIcon } from '../theme.js';
|
|
@@ -80,23 +79,17 @@ selectable items, no menu).
|
|
|
80
79
|
</DropdownMenu.Trigger>
|
|
81
80
|
|
|
82
81
|
<DropdownMenu.Portal>
|
|
83
|
-
<DropdownMenu.Content
|
|
82
|
+
<DropdownMenu.Content {align} sideOffset={4} class="z-100">
|
|
84
83
|
{#snippet child({ wrapperProps, props, open: isOpen })}
|
|
85
|
-
{
|
|
86
|
-
<div
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
easing: easeEmphasizedDecel,
|
|
93
|
-
mode: 'scale'
|
|
94
|
-
}}
|
|
95
|
-
>
|
|
96
|
-
{@render menuChildren()}
|
|
97
|
-
</div>
|
|
84
|
+
<div {...wrapperProps} class={wrapperProps.class as any}>
|
|
85
|
+
<div
|
|
86
|
+
{...props}
|
|
87
|
+
class="bg-md-sys-color-surface-container-high shadow-elevation-3 ring-md-sys-color-outline/10 max-w-sm min-w-48 gap-1 overflow-y-auto rounded-lg px-2 py-1"
|
|
88
|
+
{@attach presence(() => isOpen, enterExit.scale)}
|
|
89
|
+
>
|
|
90
|
+
{@render menuChildren()}
|
|
98
91
|
</div>
|
|
99
|
-
|
|
92
|
+
</div>
|
|
100
93
|
{/snippet}
|
|
101
94
|
</DropdownMenu.Content>
|
|
102
95
|
</DropdownMenu.Portal>
|
|
@@ -12,7 +12,7 @@ export const SPLIT_BUTTON_INNER_RADIUS = {
|
|
|
12
12
|
export const splitButton = tv({
|
|
13
13
|
slots: {
|
|
14
14
|
root: 'inline-flex items-center gap-0.5',
|
|
15
|
-
icon: 'transition-transform
|
|
15
|
+
icon: 'transition-transform md-sys-motion-fast-spatial'
|
|
16
16
|
},
|
|
17
17
|
variants: {
|
|
18
18
|
open: {
|
|
@@ -146,9 +146,7 @@ export type FABProps = FABVariants & AnchorButtonAttributes & {
|
|
|
146
146
|
*/
|
|
147
147
|
children?: Snippet;
|
|
148
148
|
};
|
|
149
|
-
export type FABMenuProps = HTMLAttributes<
|
|
150
|
-
/** The FAB button element to position the menu relative to. */
|
|
151
|
-
anchorEl: HTMLElement;
|
|
149
|
+
export type FABMenuProps = HTMLAttributes<HTMLDivElement> & {
|
|
152
150
|
/**
|
|
153
151
|
* The menu items. Use `FABMenuItem` components.
|
|
154
152
|
*/
|
|
@@ -159,6 +157,8 @@ export type FABMenuItemProps = FABMenuItemVariants & AnchorButtonAttributes & {
|
|
|
159
157
|
* The visual variant of the menu item.
|
|
160
158
|
*/
|
|
161
159
|
variant?: FABMenuItemVariants['variant'];
|
|
160
|
+
/** Whether the menu item is disabled (skipped by keyboard navigation). */
|
|
161
|
+
disabled?: boolean;
|
|
162
162
|
/** Whether the menu item is in a loading state. */
|
|
163
163
|
loading?: boolean;
|
|
164
164
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { tv } from 'tailwind-variants';
|
|
2
2
|
export const card = tv({
|
|
3
3
|
slots: {
|
|
4
|
-
base: 'relative flex flex-col rounded-lg disabled:opacity-[0.38] disabled:bg-md-sys-color-surface-variant state-layer before:rounded-lg
|
|
4
|
+
base: 'relative flex flex-col rounded-lg disabled:opacity-[0.38] disabled:bg-md-sys-color-surface-variant state-layer before:rounded-lg transition-shadow md-sys-motion-effects',
|
|
5
5
|
icon: 'size-6 text-[24px] text-md-sys-color-primary'
|
|
6
6
|
},
|
|
7
7
|
variants: {
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
BottomSheet
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
backdrop click, or dragging the handle down below a minimum height ('esc' | 'click' | 'low') —
|
|
6
|
-
the consumer is expected to flip its own `open` flag to false in response.
|
|
2
|
+
BottomSheet is controlled with `bind:open`: it dismisses itself on Esc, backdrop click, or
|
|
3
|
+
dragging the handle below a minimum height, and stays mounted until its exit animation
|
|
4
|
+
finishes. The optional `close` callback reports why ('esc' | 'click' | 'low').
|
|
7
5
|
-->
|
|
8
6
|
<script module lang="ts">
|
|
9
7
|
import { defineMeta } from '@storybook/addon-svelte-csf';
|
|
@@ -24,12 +22,10 @@
|
|
|
24
22
|
<Story name="Playground" asChild>
|
|
25
23
|
<div class="p-6">
|
|
26
24
|
<Button onclick={() => (playgroundOpen = true)}>Open bottom sheet</Button>
|
|
27
|
-
{
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
</BottomSheet>
|
|
33
|
-
{/if}
|
|
25
|
+
<BottomSheet bind:open={playgroundOpen}>
|
|
26
|
+
<MenuItem iconProps={{ name: 'share' }}>Share</MenuItem>
|
|
27
|
+
<MenuItem iconProps={{ name: 'link' }}>Copy link</MenuItem>
|
|
28
|
+
<MenuItem iconProps={{ name: 'download' }}>Download</MenuItem>
|
|
29
|
+
</BottomSheet>
|
|
34
30
|
</div>
|
|
35
31
|
</Story>
|
|
@@ -9,31 +9,37 @@ while keeping the main screen content visible.
|
|
|
9
9
|
-->
|
|
10
10
|
<script lang="ts">
|
|
11
11
|
import type { BottomSheetProps } from './types.js';
|
|
12
|
-
import
|
|
13
|
-
import {
|
|
14
|
-
import { outroClass } from '../../../animation/outroClass.js';
|
|
12
|
+
import { untrack } from 'svelte';
|
|
13
|
+
import { enterExit, Presence } from '../../../animation/index.js';
|
|
15
14
|
|
|
16
|
-
|
|
15
|
+
const DEFAULT_HEIGHT = 480;
|
|
16
|
+
const DISMISS_HEIGHT = 48;
|
|
17
17
|
|
|
18
|
-
let
|
|
18
|
+
let { children, open = $bindable(true), close }: BottomSheetProps = $props();
|
|
19
|
+
|
|
20
|
+
let height = $state(DEFAULT_HEIGHT);
|
|
19
21
|
let container: HTMLDivElement | undefined = $state();
|
|
20
22
|
let isDragging = $state(false);
|
|
21
23
|
let startY = $state(0);
|
|
22
24
|
|
|
23
|
-
const
|
|
25
|
+
const sheet = new Presence(() => open);
|
|
26
|
+
|
|
27
|
+
// untrack: re-running this attachment on every drag would call showModal() on an open dialog.
|
|
28
|
+
const showModal = (node: HTMLDialogElement) => {
|
|
29
|
+
untrack(() => node.clientHeight < height && (height = node.clientHeight));
|
|
30
|
+
node.showModal();
|
|
31
|
+
};
|
|
24
32
|
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
): TransitionConfig => {
|
|
29
|
-
if (node.clientHeight < height) height = node.clientHeight;
|
|
30
|
-
return {
|
|
31
|
-
duration: options.duration,
|
|
32
|
-
easing: options.easing,
|
|
33
|
-
css: (t) => `max-height: ${t * height}px`
|
|
34
|
-
};
|
|
33
|
+
const dismiss = (reason: 'esc' | 'click' | 'low') => {
|
|
34
|
+
open = false;
|
|
35
|
+
close?.(reason);
|
|
35
36
|
};
|
|
36
37
|
|
|
38
|
+
// A sheet dragged shut keeps its collapsed height through the exit; restore it on reopen.
|
|
39
|
+
$effect.pre(() => {
|
|
40
|
+
if (open) untrack(() => height < DISMISS_HEIGHT && (height = DEFAULT_HEIGHT));
|
|
41
|
+
});
|
|
42
|
+
|
|
37
43
|
const moveWheel = (e: WheelEvent) => {
|
|
38
44
|
e.preventDefault();
|
|
39
45
|
height += e.deltaY;
|
|
@@ -49,7 +55,7 @@ while keeping the main screen content visible.
|
|
|
49
55
|
};
|
|
50
56
|
|
|
51
57
|
$effect(() => {
|
|
52
|
-
if (height <
|
|
58
|
+
if (height < DISMISS_HEIGHT && untrack(() => open)) dismiss('low');
|
|
53
59
|
});
|
|
54
60
|
</script>
|
|
55
61
|
|
|
@@ -60,51 +66,57 @@ while keeping the main screen content visible.
|
|
|
60
66
|
ontouchend={() => (isDragging = false)}
|
|
61
67
|
/>
|
|
62
68
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
>
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
69
|
+
{#if sheet.mounted}
|
|
70
|
+
<dialog
|
|
71
|
+
class="bg-md-sys-color-surface-container-low text-md-sys-color-on-surface mx-auto mt-auto w-full max-w-2xl overflow-hidden rounded-t-md"
|
|
72
|
+
style:max-height="{height}px"
|
|
73
|
+
data-state={open ? 'open' : 'closed'}
|
|
74
|
+
{@attach showModal}
|
|
75
|
+
{@attach sheet.attach(enterExit.bottomSheet)}
|
|
76
|
+
oncancel={(e) => {
|
|
77
|
+
e.preventDefault();
|
|
78
|
+
dismiss('esc');
|
|
79
|
+
}}
|
|
80
|
+
onmousedown={(e) => {
|
|
81
|
+
if (e.target != e.currentTarget) return;
|
|
82
|
+
dismiss('click');
|
|
83
|
+
}}
|
|
84
|
+
onwheel={moveWheel}
|
|
85
|
+
>
|
|
86
|
+
<div class="px-4" bind:this={container}>
|
|
87
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
88
|
+
<div
|
|
89
|
+
class="flex h-12 w-full cursor-grab items-center justify-center"
|
|
90
|
+
onmousedown={(e) => {
|
|
91
|
+
e.preventDefault();
|
|
92
|
+
isDragging = true;
|
|
93
|
+
startY = e.clientY;
|
|
94
|
+
}}
|
|
95
|
+
ontouchstart={(e) => {
|
|
96
|
+
isDragging = true;
|
|
97
|
+
startY = e.touches[0].clientY;
|
|
98
|
+
}}
|
|
99
|
+
>
|
|
100
|
+
<div class="bg-md-sys-color-on-surface-variant/40 h-1 w-8 rounded-full"></div>
|
|
101
|
+
</div>
|
|
102
|
+
{@render children()}
|
|
95
103
|
</div>
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
</dialog>
|
|
104
|
+
</dialog>
|
|
105
|
+
{/if}
|
|
99
106
|
|
|
100
107
|
<style>
|
|
101
108
|
dialog::backdrop {
|
|
102
109
|
background-color: rgba(0, 0, 0, 0.5);
|
|
103
|
-
animation: backdrop
|
|
110
|
+
animation: backdrop var(--md-sys-motion-duration-effects-spring)
|
|
111
|
+
var(--md-sys-motion-timing-function-effects-spring);
|
|
112
|
+
}
|
|
113
|
+
dialog[data-state='closed'] {
|
|
114
|
+
pointer-events: none;
|
|
104
115
|
}
|
|
105
|
-
dialog
|
|
116
|
+
dialog[data-state='closed']::backdrop {
|
|
106
117
|
background-color: transparent;
|
|
107
|
-
animation: backdropReverse
|
|
118
|
+
animation: backdropReverse var(--md-sys-motion-duration-fast-effects-spring)
|
|
119
|
+
var(--md-sys-motion-timing-function-fast-effects-spring);
|
|
108
120
|
}
|
|
109
121
|
@keyframes backdrop {
|
|
110
122
|
from {
|
|
@@ -7,6 +7,6 @@ import type { BottomSheetProps } from './types.js';
|
|
|
7
7
|
*
|
|
8
8
|
* @see https://m3.material.io/components/bottom-sheets/overview
|
|
9
9
|
*/
|
|
10
|
-
declare const BottomSheet: import("svelte").Component<BottomSheetProps, {}, "">;
|
|
10
|
+
declare const BottomSheet: import("svelte").Component<BottomSheetProps, {}, "open">;
|
|
11
11
|
type BottomSheet = ReturnType<typeof BottomSheet>;
|
|
12
12
|
export default BottomSheet;
|
|
@@ -9,8 +9,14 @@ export interface BottomSheetProps {
|
|
|
9
9
|
*/
|
|
10
10
|
children: Snippet;
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
12
|
+
* Whether the sheet is shown. Bind it (`bind:open`) so the sheet can play its exit animation
|
|
13
|
+
* before unmounting.
|
|
14
|
+
* @default true
|
|
15
|
+
*/
|
|
16
|
+
open?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Called when the user dismisses the sheet.
|
|
13
19
|
* @param reason The reason for closing ('esc' key, 'click' outside, or 'low' drag height).
|
|
14
20
|
*/
|
|
15
|
-
close
|
|
21
|
+
close?: (reason: 'esc' | 'click' | 'low') => void;
|
|
16
22
|
}
|