@noxlovette/material 0.5.0 → 0.6.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/bin/material-claude-skill.js +31 -0
- package/claude-skill/material-design/SKILL.md +68 -0
- package/claude-skill/material-design/references/accessibility-checklist.md +29 -0
- package/claude-skill/material-design/references/component-patterns.md +55 -0
- package/claude-skill/material-design/references/motion-guide.md +163 -0
- package/claude-skill/material-design/references/tokens-and-styles.md +68 -0
- package/dist/animation/Shapes.stories.svelte +60 -0
- package/dist/animation/Shapes.stories.svelte.d.ts +3 -0
- package/dist/animation/TransitionPatterns.stories.svelte +32 -20
- package/dist/animation/containerTransform.d.ts +11 -0
- package/dist/animation/containerTransform.js +11 -0
- package/dist/animation/enterExit.d.ts +9 -0
- package/dist/animation/enterExit.js +6 -3
- package/dist/animation/index.d.ts +1 -0
- package/dist/animation/index.js +1 -0
- package/dist/animation/shapeMorph.svelte.d.ts +52 -0
- package/dist/animation/shapeMorph.svelte.js +112 -0
- package/dist/animation/shapesAnimatableSmall.d.ts +28 -22
- package/dist/animation/shapesAnimatableSmall.js +32 -23
- package/dist/animation/sharedAxisTransition.d.ts +22 -0
- package/dist/animation/sharedAxisTransition.js +25 -1
- package/dist/animation/skeleton.d.ts +6 -1
- package/dist/animation/skeleton.js +6 -1
- package/dist/components/badge/Badge.mdx +70 -0
- package/dist/components/badge/Badge.stories.svelte +38 -20
- package/dist/components/badge/Badge.svelte +12 -5
- package/dist/components/badge/theme.d.ts +30 -7
- package/dist/components/badge/theme.js +24 -4
- package/dist/components/badge/types.d.ts +18 -4
- package/dist/components/buttons/Button.mdx +85 -0
- package/dist/components/buttons/Button.stories.svelte +62 -27
- package/dist/components/buttons/Button.svelte +29 -25
- package/dist/components/buttons/Button.svelte.d.ts +11 -9
- package/dist/components/buttons/ButtonIcon.mdx +52 -0
- package/dist/components/buttons/ButtonIcon.stories.svelte +72 -48
- package/dist/components/buttons/ButtonIcon.svelte +58 -36
- package/dist/components/buttons/ButtonIcon.svelte.d.ts +9 -7
- package/dist/components/buttons/FAB.mdx +84 -0
- package/dist/components/buttons/FAB.stories.svelte +103 -24
- package/dist/components/buttons/FAB.stories.svelte.d.ts +2 -17
- package/dist/components/buttons/FAB.svelte +353 -29
- package/dist/components/buttons/FAB.svelte.d.ts +11 -6
- package/dist/components/buttons/FABMenu.svelte +27 -8
- package/dist/components/buttons/FABMenu.svelte.d.ts +7 -3
- package/dist/components/buttons/FABMenuItem.svelte +20 -16
- package/dist/components/buttons/FABMenuItem.svelte.d.ts +3 -3
- package/dist/components/buttons/Toggle.stories.svelte +31 -23
- package/dist/components/buttons/Toggle.stories.svelte.d.ts +2 -17
- package/dist/components/buttons/Toggle.svelte +28 -25
- package/dist/components/buttons/Toggle.svelte.d.ts +5 -5
- package/dist/components/buttons/button-group/ButtonGroup.mdx +39 -0
- package/dist/components/buttons/button-group/ButtonGroup.stories.svelte +62 -43
- package/dist/components/buttons/button-group/ButtonGroup.stories.svelte.d.ts +2 -17
- package/dist/components/buttons/button-group/ButtonGroup.svelte +102 -8
- package/dist/components/buttons/button-group/ButtonGroup.svelte.d.ts +6 -7
- package/dist/components/buttons/button-group/theme.d.ts +45 -15
- package/dist/components/buttons/button-group/theme.js +13 -8
- package/dist/components/buttons/button-group/types.d.ts +13 -7
- package/dist/components/buttons/connected/ConnectedButtonGroup.mdx +43 -0
- package/dist/components/buttons/connected/ConnectedButtonGroup.stories.svelte +56 -74
- package/dist/components/buttons/connected/ConnectedButtonGroup.stories.svelte.d.ts +2 -17
- package/dist/components/buttons/connected/ConnectedButtonGroup.svelte +16 -4
- package/dist/components/buttons/connected/ConnectedButtonGroup.svelte.d.ts +4 -4
- package/dist/components/buttons/connected/ConnectedButtonGroupItem.svelte +35 -8
- package/dist/components/buttons/connected/ConnectedButtonGroupItem.svelte.d.ts +2 -3
- package/dist/components/buttons/connected/theme.d.ts +33 -18
- package/dist/components/buttons/connected/theme.js +32 -9
- package/dist/components/buttons/connected/types.d.ts +36 -9
- package/dist/components/buttons/shapeMorph.d.ts +26 -0
- package/dist/components/buttons/shapeMorph.js +142 -0
- package/dist/components/buttons/split-button/SplitButton.mdx +48 -0
- package/dist/components/buttons/split-button/SplitButton.stories.svelte +9 -14
- package/dist/components/buttons/split-button/SplitButton.svelte +65 -48
- package/dist/components/buttons/split-button/SplitButton.svelte.d.ts +6 -11
- package/dist/components/buttons/split-button/theme.d.ts +110 -17
- package/dist/components/buttons/split-button/theme.js +74 -16
- package/dist/components/buttons/split-button/types.d.ts +24 -18
- package/dist/components/buttons/theme.d.ts +422 -230
- package/dist/components/buttons/theme.js +286 -380
- package/dist/components/buttons/types.d.ts +130 -125
- package/dist/components/cards/Card.mdx +44 -0
- package/dist/components/cards/Card.stories.svelte +11 -11
- package/dist/components/cards/theme.js +5 -5
- package/dist/components/chips/Chip.mdx +68 -0
- package/dist/components/chips/Chip.stories.svelte +4 -4
- package/dist/components/chips/theme.js +3 -3
- package/dist/components/containers/action-rail/ActionRail.stories.svelte +1 -0
- package/dist/components/containers/action-rail/theme.js +1 -1
- package/dist/components/containers/bottom-sheet/BottomSheet.mdx +64 -0
- package/dist/components/containers/bottom-sheet/BottomSheet.stories.svelte +108 -12
- package/dist/components/containers/bottom-sheet/BottomSheet.svelte +179 -109
- package/dist/components/containers/bottom-sheet/BottomSheet.svelte.d.ts +10 -5
- package/dist/components/containers/bottom-sheet/index.d.ts +1 -0
- package/dist/components/containers/bottom-sheet/index.js +1 -0
- package/dist/components/containers/bottom-sheet/theme.d.ts +45 -0
- package/dist/components/containers/bottom-sheet/theme.js +42 -0
- package/dist/components/containers/bottom-sheet/types.d.ts +37 -5
- package/dist/components/containers/carousel/Carousel.mdx +80 -0
- package/dist/components/containers/carousel/Carousel.stories.svelte +131 -0
- package/dist/components/containers/carousel/Carousel.stories.svelte.d.ts +4 -0
- package/dist/components/containers/carousel/Carousel.svelte +194 -0
- package/dist/components/containers/carousel/Carousel.svelte.d.ts +25 -0
- package/dist/components/containers/carousel/index.d.ts +3 -0
- package/dist/components/containers/{stack → carousel}/index.js +1 -2
- package/dist/components/containers/carousel/keylines.d.ts +32 -0
- package/dist/components/containers/carousel/keylines.js +88 -0
- package/dist/components/containers/carousel/theme.d.ts +162 -0
- package/dist/components/containers/carousel/theme.js +67 -0
- package/dist/components/containers/carousel/types.d.ts +52 -0
- package/dist/components/containers/context-menu/ContextMenu.stories.svelte +1 -0
- package/dist/components/containers/context-menu/ContextMenu.svelte +2 -2
- package/dist/components/containers/context-menu/ContextMenu.svelte.d.ts +1 -1
- package/dist/components/containers/context-menu/theme.js +10 -10
- package/dist/components/containers/dialogue/Dialogue.mdx +65 -0
- package/dist/components/containers/dialogue/Dialogue.stories.svelte +4 -4
- package/dist/components/containers/dialogue/Dialogue.svelte +1 -1
- package/dist/components/containers/dialogue/theme.js +3 -3
- package/dist/components/containers/divider/Divider.stories.svelte +2 -1
- package/dist/components/containers/divider/Divider.svelte +2 -2
- package/dist/components/containers/divider/theme.d.ts +6 -6
- package/dist/components/containers/divider/theme.js +2 -2
- package/dist/components/containers/index.d.ts +1 -1
- package/dist/components/containers/index.js +1 -1
- package/dist/components/containers/link-preview/theme.js +2 -2
- package/dist/components/containers/list/List.svelte +18 -0
- package/dist/components/containers/list/List.svelte.d.ts +4 -0
- package/dist/components/containers/list/ListItem.mdx +100 -0
- package/dist/components/containers/list/ListItem.stories.svelte +177 -42
- package/dist/components/containers/list/ListItem.stories.svelte.d.ts +2 -17
- package/dist/components/containers/list/ListItem.svelte +92 -37
- package/dist/components/containers/list/ListItem.svelte.d.ts +1 -1
- package/dist/components/containers/list/context.d.ts +6 -0
- package/dist/components/containers/list/context.js +4 -0
- package/dist/components/containers/list/index.d.ts +1 -0
- package/dist/components/containers/list/index.js +1 -0
- package/dist/components/containers/list/theme.d.ts +487 -12
- package/dist/components/containers/list/theme.js +140 -13
- package/dist/components/containers/list/types.d.ts +39 -5
- package/dist/components/containers/menu/Menu.mdx +52 -0
- package/dist/components/containers/menu/Menu.stories.svelte +14 -14
- package/dist/components/containers/menu/Menu.svelte +1 -1
- package/dist/components/containers/menu/MenuGroup.svelte +1 -1
- package/dist/components/containers/menu/MenuGroup.svelte.d.ts +1 -1
- package/dist/components/containers/menu/menu-item/MenuItem.svelte +1 -1
- package/dist/components/containers/menu/theme.js +8 -8
- package/dist/components/containers/pane/DraggablePane.stories.svelte +4 -3
- package/dist/components/containers/pane/DraggablePane.svelte +2 -2
- package/dist/components/containers/pane/Pane.stories.svelte +11 -10
- package/dist/components/containers/pane/Pane.svelte +24 -2
- package/dist/components/containers/pane/PaneGrid.stories.svelte +19 -18
- package/dist/components/containers/pane/PaneGrid.svelte +3 -2
- package/dist/components/containers/pane/PaneGrid.svelte.d.ts +2 -1
- package/dist/components/containers/pane/PaneHandle.svelte +48 -2
- package/dist/components/containers/pane/PaneHandle.svelte.d.ts +4 -0
- package/dist/components/containers/pane/resizeStore.svelte.d.ts +10 -1
- package/dist/components/containers/pane/resizeStore.svelte.js +13 -2
- package/dist/components/containers/pane/theme.js +129 -45
- package/dist/components/containers/pane/types.d.ts +3 -2
- package/dist/components/containers/popover/Popover.mdx +45 -0
- package/dist/components/containers/popover/Popover.stories.svelte +1 -1
- package/dist/components/containers/popover/Popover.svelte +1 -1
- package/dist/components/containers/popover/theme.js +3 -3
- package/dist/components/containers/scroll-area/theme.js +2 -2
- package/dist/components/containers/side-sheet/SideSheet.mdx +71 -0
- package/dist/components/containers/side-sheet/SideSheet.stories.svelte +113 -17
- package/dist/components/containers/side-sheet/SideSheet.svelte +113 -66
- package/dist/components/containers/side-sheet/SideSheet.svelte.d.ts +8 -7
- package/dist/components/containers/side-sheet/index.d.ts +1 -0
- package/dist/components/containers/side-sheet/index.js +1 -0
- package/dist/components/containers/side-sheet/theme.d.ts +108 -0
- package/dist/components/containers/side-sheet/theme.js +72 -0
- package/dist/components/containers/side-sheet/types.d.ts +35 -7
- package/dist/components/date/DateField.stories.svelte +4 -3
- package/dist/components/date/DateField.svelte +49 -26
- package/dist/components/date/DateField.svelte.d.ts +2 -0
- package/dist/components/date/DateRangeField.stories.svelte +4 -3
- package/dist/components/date/DateRangeField.svelte +54 -31
- package/dist/components/date/DateRangeField.svelte.d.ts +2 -0
- package/dist/components/date/calendarMotion.d.ts +9 -0
- package/dist/components/date/calendarMotion.js +23 -0
- package/dist/components/date/theme.d.ts +7 -7
- package/dist/components/date/theme.js +34 -30
- package/dist/components/forms/checkbox/Checkbox.mdx +35 -0
- package/dist/components/forms/checkbox/Checkbox.stories.svelte +2 -2
- package/dist/components/forms/checkbox/Checkbox.svelte +1 -1
- package/dist/components/forms/checkbox/theme.js +3 -3
- package/dist/components/forms/command/Command.stories.svelte +1 -0
- package/dist/components/forms/command/theme.js +9 -9
- package/dist/components/forms/pin/theme.js +4 -4
- package/dist/components/forms/radio-group/RadioGroup.svelte +1 -1
- package/dist/components/forms/radio-group/theme.js +5 -5
- package/dist/components/forms/search/Search.mdx +38 -0
- package/dist/components/forms/search/Search.stories.svelte +19 -9
- package/dist/components/forms/search/Search.svelte +40 -17
- package/dist/components/forms/search/Search.svelte.d.ts +2 -2
- package/dist/components/forms/search/theme.d.ts +45 -12
- package/dist/components/forms/search/theme.js +13 -8
- package/dist/components/forms/search/types.d.ts +18 -12
- package/dist/components/forms/select/Select.mdx +53 -0
- package/dist/components/forms/select/Select.stories.svelte +4 -4
- package/dist/components/forms/select/Select.svelte +10 -2
- package/dist/components/forms/select/SelectItem.svelte +1 -1
- package/dist/components/forms/select/theme.js +12 -12
- package/dist/components/forms/slider/Slider.mdx +85 -0
- package/dist/components/forms/slider/Slider.stories.svelte +7 -7
- package/dist/components/forms/slider/Slider.svelte +46 -28
- package/dist/components/forms/slider/theme.d.ts +14 -0
- package/dist/components/forms/slider/theme.js +28 -17
- package/dist/components/forms/slider/types.d.ts +3 -2
- package/dist/components/forms/switch/Switch.mdx +38 -0
- package/dist/components/forms/switch/Switch.stories.svelte +3 -3
- package/dist/components/forms/switch/theme.js +6 -6
- package/dist/components/forms/textfield/Textfield.mdx +53 -0
- package/dist/components/forms/textfield/Textfield.stories.svelte +6 -6
- package/dist/components/forms/textfield/Textfield.svelte +1 -0
- package/dist/components/forms/textfield/theme.d.ts +3 -0
- package/dist/components/forms/textfield/theme.js +22 -19
- package/dist/components/forms/toggle-group/theme.js +2 -2
- package/dist/components/forms/tooltip/Tooltip.stories.svelte +1 -0
- package/dist/components/forms/tooltip/theme.js +3 -3
- package/dist/components/misc/Avatar.stories.svelte +5 -4
- package/dist/components/misc/ThemeSettings.svelte +9 -9
- package/dist/components/misc/ThemeSwitcher.svelte +3 -3
- package/dist/components/misc/theme.js +4 -4
- package/dist/components/nav/appbar/AppBar.mdx +71 -0
- package/dist/components/nav/appbar/AppBar.stories.svelte +93 -3
- package/dist/components/nav/appbar/AppBar.stories.svelte.d.ts +2 -17
- package/dist/components/nav/appbar/AppBar.svelte +90 -37
- package/dist/components/nav/appbar/AppBar.svelte.d.ts +9 -2
- package/dist/components/nav/appbar/theme.d.ts +85 -18
- package/dist/components/nav/appbar/theme.js +159 -23
- package/dist/components/nav/appbar/types.d.ts +46 -9
- package/dist/components/nav/breadcrumb/Breadcrumb.stories.svelte +1 -0
- package/dist/components/nav/breadcrumb/theme.js +5 -5
- package/dist/components/nav/navbar/Navbar.mdx +38 -0
- package/dist/components/nav/navbar/Navbar.svelte +1 -1
- package/dist/components/nav/navbar/NavbarItem.svelte +12 -9
- package/dist/components/nav/navbar/theme.js +6 -6
- package/dist/components/nav/rail/Rail.mdx +73 -0
- package/dist/components/nav/rail/Rail.stories.svelte +3 -7
- package/dist/components/nav/rail/Rail.svelte +1 -0
- package/dist/components/nav/rail/RailItem.svelte +12 -9
- package/dist/components/nav/rail/theme.js +13 -13
- package/dist/components/nav/rail/types.d.ts +4 -1
- package/dist/components/nav/tabs/TabHolder.svelte +29 -3
- package/dist/components/nav/tabs/TabHolder.svelte.d.ts +3 -1
- package/dist/components/nav/tabs/Tabs.mdx +62 -0
- package/dist/components/nav/tabs/Tabs.stories.svelte +1 -1
- package/dist/components/nav/tabs/theme.js +4 -4
- package/dist/components/pill/Pill.mdx +32 -0
- package/dist/components/pill/Pill.stories.svelte +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/progress/Progress.mdx +50 -0
- package/dist/components/progress/Progress.stories.svelte +6 -6
- package/dist/components/snackbar/Snackbar.mdx +52 -0
- package/dist/components/snackbar/Snackbar.stories.svelte +5 -5
- package/dist/components/snackbar/Snackbar.svelte +1 -1
- package/dist/components/snackbar/theme.d.ts +3 -3
- package/dist/components/snackbar/theme.js +4 -4
- package/dist/components/table/Table.stories.svelte +1 -0
- package/dist/components/table/TableHeader.svelte +1 -1
- package/dist/components/table/theme.js +3 -3
- package/dist/components/time/TimeField.stories.svelte +3 -2
- package/dist/components/time/TimeField.svelte +14 -8
- package/dist/components/time/TimeField.svelte.d.ts +6 -0
- package/dist/components/time/TimepickerInput.svelte +9 -5
- package/dist/components/time/theme.js +5 -5
- package/dist/components/time/types.d.ts +11 -1
- package/dist/components/toolbar/Toolbar.stories.svelte +1 -0
- package/dist/components/toolbar/Toolbar.svelte +1 -1
- package/dist/components/toolbar/Toolbar.svelte.d.ts +1 -1
- package/dist/components/toolbar/theme.js +14 -14
- package/dist/components/typography/Typography.mdx +67 -0
- package/dist/components/typography/body/Body.stories.svelte +2 -1
- package/dist/components/typography/body/theme.d.ts +15 -12
- package/dist/components/typography/body/theme.js +19 -4
- package/dist/components/typography/body/types.d.ts +2 -1
- package/dist/components/typography/display/Display.stories.svelte +2 -1
- package/dist/components/typography/display/theme.d.ts +15 -12
- package/dist/components/typography/display/theme.js +19 -4
- package/dist/components/typography/display/types.d.ts +2 -1
- package/dist/components/typography/headline/Headline.stories.svelte +2 -1
- package/dist/components/typography/headline/theme.d.ts +15 -12
- package/dist/components/typography/headline/theme.js +19 -4
- package/dist/components/typography/headline/types.d.ts +2 -1
- package/dist/components/typography/kbd/Kbd.stories.svelte +2 -1
- package/dist/components/typography/kbd/theme.d.ts +4 -4
- package/dist/components/typography/kbd/theme.js +2 -2
- package/dist/components/typography/label/Label.stories.svelte +2 -1
- package/dist/components/typography/label/theme.d.ts +15 -12
- package/dist/components/typography/label/theme.js +19 -4
- package/dist/components/typography/label/types.d.ts +2 -1
- package/dist/components/typography/title/Title.stories.svelte +2 -1
- package/dist/components/typography/title/theme.d.ts +15 -12
- package/dist/components/typography/title/theme.js +19 -4
- package/dist/components/typography/title/types.d.ts +2 -1
- package/dist/index.css +1 -0
- package/dist/styles/component.css +78 -175
- package/dist/styles/elevation.css +9 -0
- package/dist/styles/spacing.css +29 -0
- package/dist/styles/typescale.css +635 -20
- package/dist/utils/Layer.svelte +1 -1
- package/dist/utils/icon/Icon.svelte +8 -6
- package/dist/utils/icon/loadingShapes.js +11 -10
- package/package.json +19 -15
- package/dist/components/containers/stack/HStack.svelte +0 -23
- package/dist/components/containers/stack/HStack.svelte.d.ts +0 -5
- package/dist/components/containers/stack/VStack.svelte +0 -23
- package/dist/components/containers/stack/VStack.svelte.d.ts +0 -5
- package/dist/components/containers/stack/index.d.ts +0 -4
- package/dist/components/containers/stack/theme.d.ts +0 -66
- package/dist/components/containers/stack/theme.js +0 -26
- package/dist/components/containers/stack/types.d.ts +0 -12
- /package/dist/components/containers/{stack → carousel}/types.js +0 -0
|
@@ -2,26 +2,31 @@
|
|
|
2
2
|
import { defineMeta } from '@storybook/addon-svelte-csf';
|
|
3
3
|
import FAB from './FAB.svelte';
|
|
4
4
|
import FABMenuItem from './FABMenuItem.svelte';
|
|
5
|
+
import ListItem from '../containers/list/ListItem.svelte';
|
|
6
|
+
import List from '../containers/list/List.svelte';
|
|
7
|
+
import Icon from '../../utils/icon/Icon.svelte';
|
|
8
|
+
|
|
9
|
+
const colors = [
|
|
10
|
+
'primary-container',
|
|
11
|
+
'secondary-container',
|
|
12
|
+
'tertiary-container',
|
|
13
|
+
'primary',
|
|
14
|
+
'secondary',
|
|
15
|
+
'tertiary'
|
|
16
|
+
] as const;
|
|
5
17
|
|
|
6
18
|
const { Story } = defineMeta({
|
|
7
19
|
title: 'Buttons/FAB',
|
|
8
20
|
component: FAB,
|
|
9
21
|
argTypes: {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
options: ['primary', 'secondary', 'tertiary']
|
|
13
|
-
},
|
|
14
|
-
size: {
|
|
15
|
-
control: 'select',
|
|
16
|
-
options: ['small', 'regular', 'large']
|
|
17
|
-
},
|
|
22
|
+
color: { control: 'select', options: colors },
|
|
23
|
+
size: { control: 'select', options: ['small', 'regular', 'medium', 'large'] },
|
|
18
24
|
expanded: { control: 'boolean' },
|
|
19
|
-
label: { control: 'text' },
|
|
20
25
|
disabled: { control: 'boolean' },
|
|
21
26
|
loading: { control: 'boolean' }
|
|
22
27
|
},
|
|
23
28
|
args: {
|
|
24
|
-
|
|
29
|
+
color: 'primary-container',
|
|
25
30
|
size: 'regular',
|
|
26
31
|
expanded: false,
|
|
27
32
|
label: 'Compose',
|
|
@@ -32,28 +37,102 @@
|
|
|
32
37
|
});
|
|
33
38
|
</script>
|
|
34
39
|
|
|
35
|
-
<
|
|
40
|
+
<script lang="ts">
|
|
41
|
+
let extended = $state(true);
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<Story name="Playground">
|
|
45
|
+
{#snippet template(args)}
|
|
46
|
+
<FAB {...args} iconProps={{ name: 'edit' }} />
|
|
47
|
+
{/snippet}
|
|
48
|
+
</Story>
|
|
36
49
|
|
|
37
50
|
<Story name="Sizes" asChild>
|
|
38
|
-
<div class="
|
|
39
|
-
<FAB size="
|
|
40
|
-
<FAB size="
|
|
41
|
-
<FAB size="large"
|
|
51
|
+
<div class="gap-spacing-300 p-spacing-300 flex items-end">
|
|
52
|
+
<FAB size="regular" aria-label="Compose" iconProps={{ name: 'edit' }} />
|
|
53
|
+
<FAB size="medium" aria-label="Compose" iconProps={{ name: 'edit' }} />
|
|
54
|
+
<FAB size="large" aria-label="Compose" iconProps={{ name: 'edit' }} />
|
|
55
|
+
<FAB size="small" aria-label="Compose (baseline small)" iconProps={{ name: 'edit' }} />
|
|
56
|
+
</div>
|
|
57
|
+
</Story>
|
|
58
|
+
|
|
59
|
+
<Story name="Colours" asChild>
|
|
60
|
+
<div class="gap-spacing-300 p-spacing-300 flex flex-wrap">
|
|
61
|
+
{#each colors as color (color)}
|
|
62
|
+
<FAB {color} aria-label={color} iconProps={{ name: 'edit' }} />
|
|
63
|
+
{/each}
|
|
42
64
|
</div>
|
|
43
65
|
</Story>
|
|
44
66
|
|
|
45
67
|
<Story name="Extended" asChild>
|
|
46
|
-
<div class="flex flex-
|
|
47
|
-
<FAB
|
|
68
|
+
<div class="gap-spacing-300 p-spacing-300 flex flex-col items-start">
|
|
69
|
+
<FAB expanded label="Compose" iconProps={{ name: 'edit' }} />
|
|
70
|
+
<FAB size="medium" expanded label="Compose" iconProps={{ name: 'edit' }} />
|
|
71
|
+
<FAB size="large" expanded label="Compose" iconProps={{ name: 'edit' }} />
|
|
48
72
|
</div>
|
|
49
73
|
</Story>
|
|
50
74
|
|
|
51
|
-
<Story name="
|
|
52
|
-
<div class="
|
|
53
|
-
<
|
|
54
|
-
<
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
75
|
+
<Story name="Responsive" asChild>
|
|
76
|
+
<div class="gap-spacing-300 p-spacing-300 flex flex-col items-start">
|
|
77
|
+
<label class="md-sys-typescale-body-medium gap-spacing-100 flex items-center">
|
|
78
|
+
<input type="checkbox" bind:checked={extended} /> Extended
|
|
79
|
+
</label>
|
|
80
|
+
<FAB expanded={extended} label="Create" iconProps={{ name: 'add' }} />
|
|
81
|
+
<p class="md-sys-typescale-body-medium text-md-sys-color-on-surface-variant max-w-md">
|
|
82
|
+
By default a labelled FAB is a FAB on compact windows and extended from medium windows up;
|
|
83
|
+
resize the preview to see it transform.
|
|
84
|
+
</p>
|
|
85
|
+
<FAB label="Create" iconProps={{ name: 'add' }} />
|
|
58
86
|
</div>
|
|
59
87
|
</Story>
|
|
88
|
+
|
|
89
|
+
<Story
|
|
90
|
+
name="FAB menu"
|
|
91
|
+
exportName="FabMenu"
|
|
92
|
+
parameters={{ docs: { story: { inline: false, height: '28rem' } } }}
|
|
93
|
+
>
|
|
94
|
+
{#snippet template()}
|
|
95
|
+
<div class="end-spacing-200 bottom-spacing-200 gap-spacing-200 fixed flex">
|
|
96
|
+
<FAB
|
|
97
|
+
withMenu
|
|
98
|
+
size="medium"
|
|
99
|
+
color="tertiary-container"
|
|
100
|
+
aria-label="New"
|
|
101
|
+
iconProps={{ name: 'add' }}
|
|
102
|
+
>
|
|
103
|
+
<FABMenuItem iconProps={{ name: 'chat' }}>Message</FABMenuItem>
|
|
104
|
+
<FABMenuItem iconProps={{ name: 'folder' }}>Folder</FABMenuItem>
|
|
105
|
+
<FABMenuItem iconProps={{ name: 'upload' }}>Upload</FABMenuItem>
|
|
106
|
+
</FAB>
|
|
107
|
+
<FAB withMenu aria-label="New" iconProps={{ name: 'add' }}>
|
|
108
|
+
<FABMenuItem iconProps={{ name: 'chat' }}>Message</FABMenuItem>
|
|
109
|
+
<FABMenuItem iconProps={{ name: 'folder' }}>Folder</FABMenuItem>
|
|
110
|
+
<FABMenuItem iconProps={{ name: 'upload' }}>Upload</FABMenuItem>
|
|
111
|
+
</FAB>
|
|
112
|
+
</div>
|
|
113
|
+
{/snippet}
|
|
114
|
+
</Story>
|
|
115
|
+
|
|
116
|
+
<Story
|
|
117
|
+
name="Container transform"
|
|
118
|
+
parameters={{ docs: { story: { inline: false, height: '32rem' } } }}
|
|
119
|
+
>
|
|
120
|
+
{#snippet template()}
|
|
121
|
+
<div class="end-spacing-200 bottom-spacing-200 fixed">
|
|
122
|
+
<FAB label="Compose" iconProps={{ name: 'edit' }} expanded={false}>
|
|
123
|
+
{#snippet surface({ close })}
|
|
124
|
+
<List>
|
|
125
|
+
{#each ['Lily MacDonald', 'So Duri', 'Ziad Aouad'] as name (name)}
|
|
126
|
+
<ListItem headline={name} onclick={close}>
|
|
127
|
+
{#snippet leading()}<Icon name="person" />{/snippet}
|
|
128
|
+
</ListItem>
|
|
129
|
+
{/each}
|
|
130
|
+
<ListItem headline="Compose new" onclick={close}>
|
|
131
|
+
{#snippet leading()}<Icon name="edit" />{/snippet}
|
|
132
|
+
</ListItem>
|
|
133
|
+
</List>
|
|
134
|
+
{/snippet}
|
|
135
|
+
</FAB>
|
|
136
|
+
</div>
|
|
137
|
+
{/snippet}
|
|
138
|
+
</Story>
|
|
@@ -1,19 +1,4 @@
|
|
|
1
1
|
import FAB from './FAB.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 FAB: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
16
|
-
[evt: string]: CustomEvent<any>;
|
|
17
|
-
}, {}, {}, string>;
|
|
18
|
-
type FAB = InstanceType<typeof FAB>;
|
|
2
|
+
declare const FAB: import("svelte").Component<Record<string, never>, {}, "">;
|
|
3
|
+
type FAB = ReturnType<typeof FAB>;
|
|
19
4
|
export default FAB;
|
|
@@ -2,16 +2,27 @@
|
|
|
2
2
|
@component
|
|
3
3
|
Floating action buttons (FABs) help people take primary actions.
|
|
4
4
|
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
- Sizes: regular (56dp, default), medium (80dp), large (96dp); small (40dp) is baseline only
|
|
6
|
+
- `expanded` + `label`: the extended FAB, whose label slides open beside the icon. Responsive:
|
|
7
|
+
a FAB on compact windows and an extended FAB from medium up by default, and inside a `Rail`
|
|
8
|
+
it follows the rail's collapsed state
|
|
9
|
+
- `withMenu`: opens a FAB menu of `FABMenuItem`s; the FAB becomes its 56dp close button
|
|
10
|
+
- `surface`: opens into a surface with a container transform (FAB → sheet); the FAB's
|
|
11
|
+
container grows into it and changes colour on the way
|
|
9
12
|
|
|
10
|
-
@see https://m3.material.io/components/floating-action-button/
|
|
13
|
+
@see https://m3.material.io/components/floating-action-button/specs
|
|
14
|
+
@see https://m3.material.io/components/extended-fab/specs
|
|
15
|
+
@see https://m3.material.io/components/fab-menu/specs
|
|
11
16
|
-->
|
|
12
17
|
<script lang="ts">
|
|
13
18
|
import clsx from 'clsx';
|
|
14
|
-
import {
|
|
19
|
+
import { getContext, setContext, tick, untrack } from 'svelte';
|
|
20
|
+
import type { Attachment } from 'svelte/attachments';
|
|
21
|
+
import { animate, type AnimationPlaybackControls } from 'motion';
|
|
22
|
+
import { springTokens, springTransition } from '../../animation/spring.js';
|
|
23
|
+
import { clickOutside } from '../../attachments/index.js';
|
|
24
|
+
import { fab, fabExpandedClasses, fabMenuSet, fabSurface } from './theme.js';
|
|
25
|
+
import type { Breakpoint } from '../containers/pane/theme.js';
|
|
15
26
|
import type { FABProps } from './types.js';
|
|
16
27
|
import FABMenu from './FABMenu.svelte';
|
|
17
28
|
import { Layer, Icon, LoadingIndicator } from '../../utils/index.js';
|
|
@@ -22,30 +33,286 @@ Floating action buttons (FABs) help people take primary actions.
|
|
|
22
33
|
disabled,
|
|
23
34
|
formaction,
|
|
24
35
|
size = 'regular',
|
|
25
|
-
|
|
36
|
+
color = 'primary-container',
|
|
26
37
|
iconProps,
|
|
27
38
|
label,
|
|
28
39
|
loading,
|
|
29
|
-
expanded
|
|
40
|
+
expanded,
|
|
30
41
|
class: className,
|
|
31
42
|
withMenu,
|
|
43
|
+
surface,
|
|
44
|
+
surfaceOpen = $bindable(false),
|
|
32
45
|
onclick,
|
|
33
46
|
...restProps
|
|
34
47
|
}: FABProps = $props();
|
|
35
48
|
|
|
36
|
-
|
|
49
|
+
let menuOpen = $state(false);
|
|
50
|
+
const menuSet = $derived(fabMenuSet(color));
|
|
51
|
+
setContext('fabMenuSet', () => menuSet);
|
|
37
52
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
className,
|
|
44
|
-
'transition-[border-radius] md-sys-motion-fast-spatial',
|
|
45
|
-
withMenu && showMenu && '!rounded-full'
|
|
46
|
-
)
|
|
47
|
-
})
|
|
53
|
+
// M3: in a collapsed rail use a FAB, in an expanded one an extended FAB; elsewhere a FAB when
|
|
54
|
+
// space is limited (compact windows) and an extended FAB from medium windows up.
|
|
55
|
+
const rail = getContext<{ collapsed: boolean } | undefined>('rail');
|
|
56
|
+
const resolvedExpanded = $derived(
|
|
57
|
+
expanded ?? (rail ? !rail.collapsed : { small: false, medium: true })
|
|
48
58
|
);
|
|
59
|
+
const hasLabel = $derived(!!label && size !== 'small');
|
|
60
|
+
// Always a FAB: the menu keeps its footprint only then (an extended FAB's width is its label's).
|
|
61
|
+
const neverExpanded = $derived(
|
|
62
|
+
!hasLabel ||
|
|
63
|
+
(typeof resolvedExpanded === 'object'
|
|
64
|
+
? Object.values(resolvedExpanded).every((v) => !v)
|
|
65
|
+
: !resolvedExpanded)
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const open = $derived(!!withMenu && menuOpen);
|
|
69
|
+
const cls = $derived(fab({ size, color, menuOpen: open, menuSet }));
|
|
70
|
+
const expandedCls = $derived.by(() => {
|
|
71
|
+
const tiers: [Breakpoint, boolean][] =
|
|
72
|
+
typeof resolvedExpanded === 'object'
|
|
73
|
+
? (Object.entries(resolvedExpanded) as [Breakpoint, boolean][])
|
|
74
|
+
: [['small', resolvedExpanded]];
|
|
75
|
+
const entries = tiers.map(([bp, v]) => fabExpandedClasses[bp][v ? 'true' : 'false']);
|
|
76
|
+
return {
|
|
77
|
+
track: entries.map((e) => e.track).join(' '),
|
|
78
|
+
base: size === 'large' && !open ? entries.map((e) => e.large).join(' ') : ''
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// The close button shares the FAB's top trailing corner, so the FAB keeps its footprint while
|
|
83
|
+
// it shrinks into it.
|
|
84
|
+
const footprint: Record<NonNullable<FABProps['size']>, string> = {
|
|
85
|
+
small: 'size-spacing-500',
|
|
86
|
+
regular: 'size-spacing-700',
|
|
87
|
+
medium: 'size-20',
|
|
88
|
+
large: 'size-24'
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const reducedMotion = () =>
|
|
92
|
+
typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
93
|
+
const spatial = springTransition(springTokens.spatial);
|
|
94
|
+
const colorAndShape = { ...spatial, backgroundColor: springTransition(springTokens.effects) };
|
|
95
|
+
|
|
96
|
+
let fabEl = $state<HTMLElement | null>(null);
|
|
97
|
+
|
|
98
|
+
/*
|
|
99
|
+
* FAB menu: the FAB springs into the 56dp round close button (M3: always 56dp, whatever the
|
|
100
|
+
* FAB's size) and back. Measured before and after the DOM update, then sprung between the two
|
|
101
|
+
* in px, so the corners stay a real radius instead of racing a 9999px `rounded-full`.
|
|
102
|
+
*/
|
|
103
|
+
const CLOSE_BUTTON = { w: 56, h: 56, r: 28 };
|
|
104
|
+
type Box = { w: number; h: number; r: number };
|
|
105
|
+
const measure = (el: HTMLElement): Box => ({
|
|
106
|
+
w: el.offsetWidth,
|
|
107
|
+
h: el.offsetHeight,
|
|
108
|
+
r: parseFloat(getComputedStyle(el).borderTopLeftRadius)
|
|
109
|
+
});
|
|
110
|
+
const clearBox = (el: HTMLElement) => {
|
|
111
|
+
el.style.width = '';
|
|
112
|
+
el.style.height = '';
|
|
113
|
+
el.style.borderRadius = '';
|
|
114
|
+
};
|
|
115
|
+
let menuMorph: AnimationPlaybackControls | undefined;
|
|
116
|
+
let beforeMenu: Box | null = null;
|
|
117
|
+
$effect.pre(() => {
|
|
118
|
+
void open;
|
|
119
|
+
untrack(() => (beforeMenu = fabEl ? measure(fabEl) : null));
|
|
120
|
+
});
|
|
121
|
+
$effect(() => {
|
|
122
|
+
const isOpen = open;
|
|
123
|
+
untrack(() => {
|
|
124
|
+
const el = fabEl;
|
|
125
|
+
const from = beforeMenu;
|
|
126
|
+
beforeMenu = null;
|
|
127
|
+
if (!el || !from || !withMenu) return;
|
|
128
|
+
menuMorph?.stop();
|
|
129
|
+
clearBox(el);
|
|
130
|
+
const to = isOpen ? CLOSE_BUTTON : measure(el);
|
|
131
|
+
const keyframes = {
|
|
132
|
+
width: [`${from.w}px`, `${to.w}px`],
|
|
133
|
+
height: [`${from.h}px`, `${to.h}px`],
|
|
134
|
+
borderRadius: [`${from.r}px`, `${to.r}px`]
|
|
135
|
+
};
|
|
136
|
+
if (reducedMotion()) {
|
|
137
|
+
if (isOpen)
|
|
138
|
+
Object.assign(el.style, { width: '56px', height: '56px', borderRadius: '28px' });
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const controls = animate(el, keyframes, spatial);
|
|
142
|
+
menuMorph = controls;
|
|
143
|
+
// Closed: hand size and shape back to the classes once the spring settles.
|
|
144
|
+
if (!isOpen) controls.then(() => !open && clearBox(el));
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
/*
|
|
149
|
+
* FAB ↔ extended FAB. The responsive classes decide which one shows; when that flips (the
|
|
150
|
+
* prop, the rail, or the window crossing a tier), the FAB's width and padding spring from what
|
|
151
|
+
* was on screen to the new layout, with the label kept laid out and clipped by the moving edge.
|
|
152
|
+
*/
|
|
153
|
+
const tierOrder: Breakpoint[] = ['small', 'medium', 'large', 'extraLarge'];
|
|
154
|
+
let tier = $state<Breakpoint>('small');
|
|
155
|
+
$effect(() => {
|
|
156
|
+
const root = getComputedStyle(document.documentElement);
|
|
157
|
+
const min = (name: string, fallback: string) =>
|
|
158
|
+
`(min-width: ${root.getPropertyValue(`--breakpoint-${name}`).trim() || fallback})`;
|
|
159
|
+
const queries: [Breakpoint, MediaQueryList][] = [
|
|
160
|
+
['medium', matchMedia(min('md', '48rem'))],
|
|
161
|
+
['large', matchMedia(min('lg', '64rem'))],
|
|
162
|
+
['extraLarge', matchMedia(min('xl', '80rem'))]
|
|
163
|
+
];
|
|
164
|
+
const update = () =>
|
|
165
|
+
(tier = queries.reduce<Breakpoint>((t, [bp, q]) => (q.matches ? bp : t), 'small'));
|
|
166
|
+
update();
|
|
167
|
+
queries.forEach(([, q]) => q.addEventListener('change', update));
|
|
168
|
+
return () => queries.forEach(([, q]) => q.removeEventListener('change', update));
|
|
169
|
+
});
|
|
170
|
+
// Missing tiers inherit upward, as in the classes.
|
|
171
|
+
const expandedNow = $derived.by(() => {
|
|
172
|
+
if (!hasLabel) return false;
|
|
173
|
+
if (typeof resolvedExpanded !== 'object') return resolvedExpanded;
|
|
174
|
+
let value = false;
|
|
175
|
+
for (const bp of tierOrder.slice(0, tierOrder.indexOf(tier) + 1))
|
|
176
|
+
value = resolvedExpanded[bp] ?? value;
|
|
177
|
+
return value;
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
let trackEl = $state<HTMLElement | null>(null);
|
|
181
|
+
// What was last on screen, kept by a ResizeObserver so a flip can start from it.
|
|
182
|
+
let shown = { width: 0, padding: 0 };
|
|
183
|
+
$effect(() => {
|
|
184
|
+
const el = fabEl;
|
|
185
|
+
if (!el) return;
|
|
186
|
+
const observer = new ResizeObserver(() => {
|
|
187
|
+
shown = { width: el.offsetWidth, padding: parseFloat(getComputedStyle(el).paddingLeft) };
|
|
188
|
+
});
|
|
189
|
+
observer.observe(el);
|
|
190
|
+
return () => observer.disconnect();
|
|
191
|
+
});
|
|
192
|
+
let labelMorph: AnimationPlaybackControls | undefined;
|
|
193
|
+
let labelMorphStarted = false;
|
|
194
|
+
$effect(() => {
|
|
195
|
+
void expandedNow;
|
|
196
|
+
untrack(() => {
|
|
197
|
+
const el = fabEl;
|
|
198
|
+
const track = trackEl;
|
|
199
|
+
if (!labelMorphStarted) {
|
|
200
|
+
labelMorphStarted = true;
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (!el || !track || open || !shown.width) return;
|
|
204
|
+
labelMorph?.stop();
|
|
205
|
+
// The new layout, with nothing inline.
|
|
206
|
+
el.style.width = el.style.paddingInline = el.style.overflow = '';
|
|
207
|
+
track.style.gridTemplateColumns = '';
|
|
208
|
+
const to = { width: el.offsetWidth, padding: parseFloat(getComputedStyle(el).paddingLeft) };
|
|
209
|
+
if (reducedMotion() || (to.width === shown.width && to.padding === shown.padding)) return;
|
|
210
|
+
Object.assign(el.style, { overflow: 'hidden', width: `${shown.width}px` });
|
|
211
|
+
track.style.gridTemplateColumns = '1fr';
|
|
212
|
+
const controls = animate(
|
|
213
|
+
el,
|
|
214
|
+
{
|
|
215
|
+
width: [`${shown.width}px`, `${to.width}px`],
|
|
216
|
+
paddingInline: [`${shown.padding}px`, `${to.padding}px`]
|
|
217
|
+
},
|
|
218
|
+
spatial
|
|
219
|
+
);
|
|
220
|
+
labelMorph = controls;
|
|
221
|
+
controls.then(() => {
|
|
222
|
+
if (labelMorph !== controls) return;
|
|
223
|
+
el.style.width = el.style.paddingInline = el.style.overflow = '';
|
|
224
|
+
track.style.gridTemplateColumns = '';
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
/*
|
|
230
|
+
* FAB → surface, M3's container transform with a persistent container: the FAB's container
|
|
231
|
+
* grows from its corner into the surface (a clip-path), turning from the FAB's colour into the
|
|
232
|
+
* surface's, and the content fades in once it has room. Closing plays it backwards and hands
|
|
233
|
+
* focus back to the FAB, which stays in the layout, invisible, while the surface is open.
|
|
234
|
+
*/
|
|
235
|
+
const surfaceCls = fabSurface();
|
|
236
|
+
const SURFACE_RADIUS = 24;
|
|
237
|
+
let surfaceEl: HTMLElement | null = null;
|
|
238
|
+
let closing = false;
|
|
239
|
+
const fabClip = (surfaceBox: DOMRect) => {
|
|
240
|
+
const f = fabEl!.getBoundingClientRect();
|
|
241
|
+
const r = parseFloat(getComputedStyle(fabEl!).borderTopLeftRadius);
|
|
242
|
+
return `inset(${f.top - surfaceBox.top}px ${surfaceBox.right - f.right}px ${surfaceBox.bottom - f.bottom}px ${f.left - surfaceBox.left}px round ${r}px)`;
|
|
243
|
+
};
|
|
244
|
+
const focusFirst = (node: HTMLElement) => {
|
|
245
|
+
const target = node.querySelector<HTMLElement>(
|
|
246
|
+
'a[href], button:not([disabled]), input:not([disabled]), select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
247
|
+
);
|
|
248
|
+
(target ?? node).focus();
|
|
249
|
+
};
|
|
250
|
+
// The content grows out of the FAB with the container: scaled from the FAB's centre.
|
|
251
|
+
const CONTENT_START_SCALE = 0.85;
|
|
252
|
+
const fromFabCentre = (content: HTMLElement) => {
|
|
253
|
+
const f = fabEl!.getBoundingClientRect();
|
|
254
|
+
const c = content.getBoundingClientRect();
|
|
255
|
+
content.style.transformOrigin = `${f.left + f.width / 2 - c.left}px ${f.top + f.height / 2 - c.top}px`;
|
|
256
|
+
};
|
|
257
|
+
const growFromFab: Attachment<HTMLElement> = (node) => {
|
|
258
|
+
surfaceEl = node;
|
|
259
|
+
const content = node.querySelector<HTMLElement>('[data-fab-surface-content]');
|
|
260
|
+
if (fabEl && !reducedMotion()) {
|
|
261
|
+
animate(
|
|
262
|
+
node,
|
|
263
|
+
{
|
|
264
|
+
clipPath: [
|
|
265
|
+
fabClip(node.getBoundingClientRect()),
|
|
266
|
+
`inset(0px 0px 0px 0px round ${SURFACE_RADIUS}px)`
|
|
267
|
+
],
|
|
268
|
+
backgroundColor: [
|
|
269
|
+
getComputedStyle(fabEl).backgroundColor,
|
|
270
|
+
getComputedStyle(node).backgroundColor
|
|
271
|
+
]
|
|
272
|
+
},
|
|
273
|
+
colorAndShape
|
|
274
|
+
);
|
|
275
|
+
if (content) {
|
|
276
|
+
fromFabCentre(content);
|
|
277
|
+
animate(
|
|
278
|
+
content,
|
|
279
|
+
{ scale: [CONTENT_START_SCALE, 1], opacity: [0, 1] },
|
|
280
|
+
{ ...spatial, opacity: { ...springTransition(springTokens.effects), delay: 0.06 } }
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
focusFirst(node);
|
|
285
|
+
return () => (surfaceEl = null);
|
|
286
|
+
};
|
|
287
|
+
const closeSurface = async () => {
|
|
288
|
+
if (!surfaceOpen || closing) return;
|
|
289
|
+
closing = true;
|
|
290
|
+
const node = surfaceEl;
|
|
291
|
+
if (node && fabEl && !reducedMotion()) {
|
|
292
|
+
const content = node.querySelector<HTMLElement>('[data-fab-surface-content]');
|
|
293
|
+
if (content) fromFabCentre(content);
|
|
294
|
+
await Promise.all([
|
|
295
|
+
content &&
|
|
296
|
+
animate(
|
|
297
|
+
content,
|
|
298
|
+
{ scale: CONTENT_START_SCALE, opacity: 0 },
|
|
299
|
+
{ ...spatial, opacity: springTransition(springTokens.fastEffects) }
|
|
300
|
+
),
|
|
301
|
+
animate(
|
|
302
|
+
node,
|
|
303
|
+
{
|
|
304
|
+
clipPath: fabClip(node.getBoundingClientRect()),
|
|
305
|
+
backgroundColor: getComputedStyle(fabEl).backgroundColor
|
|
306
|
+
},
|
|
307
|
+
colorAndShape
|
|
308
|
+
)
|
|
309
|
+
]);
|
|
310
|
+
}
|
|
311
|
+
surfaceOpen = false;
|
|
312
|
+
closing = false;
|
|
313
|
+
await tick();
|
|
314
|
+
fabEl?.focus();
|
|
315
|
+
};
|
|
49
316
|
|
|
50
317
|
function handleClick(e: MouseEvent, triggerClick?: unknown) {
|
|
51
318
|
if (disabled || loading) {
|
|
@@ -54,6 +321,7 @@ Floating action buttons (FABs) help people take primary actions.
|
|
|
54
321
|
}
|
|
55
322
|
(onclick as ((e: MouseEvent) => void) | null | undefined)?.(e);
|
|
56
323
|
if (typeof triggerClick === 'function') triggerClick(e);
|
|
324
|
+
if (surface && !e.defaultPrevented) surfaceOpen = true;
|
|
57
325
|
}
|
|
58
326
|
</script>
|
|
59
327
|
|
|
@@ -61,37 +329,93 @@ Floating action buttons (FABs) help people take primary actions.
|
|
|
61
329
|
<Button.Root
|
|
62
330
|
{disabled}
|
|
63
331
|
{formaction}
|
|
332
|
+
aria-label={label}
|
|
333
|
+
aria-busy={loading || undefined}
|
|
64
334
|
data-cy="m3-fab"
|
|
335
|
+
aria-haspopup={surface ? 'dialog' : undefined}
|
|
336
|
+
aria-expanded={surface ? surfaceOpen : undefined}
|
|
337
|
+
bind:ref={fabEl}
|
|
65
338
|
{...restProps as ButtonRootProps}
|
|
66
339
|
{...triggerProps}
|
|
67
|
-
class={
|
|
340
|
+
class={cls.base({
|
|
341
|
+
class: clsx(
|
|
342
|
+
expandedCls.base,
|
|
343
|
+
surfaceOpen && 'invisible',
|
|
344
|
+
// In its footprint, pinned to the top trailing corner the close button shares.
|
|
345
|
+
withMenu &&
|
|
346
|
+
neverExpanded && ['col-start-1 row-start-1 self-start justify-self-end', footprint[size]],
|
|
347
|
+
className
|
|
348
|
+
)
|
|
349
|
+
})}
|
|
68
350
|
onclick={(e: MouseEvent) => handleClick(e, triggerProps.onclick)}
|
|
69
351
|
>
|
|
70
352
|
{#if loading}
|
|
71
|
-
<LoadingIndicator />
|
|
353
|
+
<LoadingIndicator center={false} class={cls.icon({ class: 'text-current' })} />
|
|
72
354
|
{:else}
|
|
73
|
-
<Icon
|
|
74
|
-
|
|
355
|
+
<Icon
|
|
356
|
+
{...open ? { name: 'close' } : iconProps}
|
|
357
|
+
class={cls.icon({ class: clsx(!open && iconProps.class) })}
|
|
358
|
+
/>
|
|
75
359
|
{/if}
|
|
76
|
-
{#if
|
|
77
|
-
<
|
|
78
|
-
{
|
|
79
|
-
|
|
360
|
+
{#if hasLabel}
|
|
361
|
+
<span
|
|
362
|
+
bind:this={trackEl}
|
|
363
|
+
class={cls.labelTrack({ class: expandedCls.track })}
|
|
364
|
+
aria-hidden="true"
|
|
365
|
+
>
|
|
366
|
+
<span class={cls.labelClip()}>
|
|
367
|
+
<span class={cls.label()}>{label}</span>
|
|
368
|
+
</span>
|
|
369
|
+
</span>
|
|
80
370
|
{/if}
|
|
371
|
+
<Layer />
|
|
81
372
|
</Button.Root>
|
|
82
373
|
{/snippet}
|
|
83
374
|
|
|
84
375
|
{#if withMenu}
|
|
85
|
-
<DropdownMenu.Root bind:open={
|
|
376
|
+
<DropdownMenu.Root bind:open={menuOpen}>
|
|
86
377
|
<DropdownMenu.Trigger disabled={disabled || loading}>
|
|
87
378
|
{#snippet child({ props })}
|
|
88
|
-
{
|
|
379
|
+
{#if !neverExpanded}
|
|
380
|
+
{@render fabButton(props)}
|
|
381
|
+
{:else}
|
|
382
|
+
<span class="inline-grid">
|
|
383
|
+
<span class="invisible col-start-1 row-start-1 {footprint[size]}"></span>
|
|
384
|
+
{@render fabButton(props)}
|
|
385
|
+
</span>
|
|
386
|
+
{/if}
|
|
89
387
|
{/snippet}
|
|
90
388
|
</DropdownMenu.Trigger>
|
|
91
389
|
<FABMenu>
|
|
92
390
|
{@render children?.()}
|
|
93
391
|
</FABMenu>
|
|
94
392
|
</DropdownMenu.Root>
|
|
393
|
+
{:else if surface}
|
|
394
|
+
<span class={surfaceCls.anchor()}>
|
|
395
|
+
{@render fabButton({})}
|
|
396
|
+
{#if surfaceOpen}
|
|
397
|
+
<div class={surfaceCls.wrapper()}>
|
|
398
|
+
<div
|
|
399
|
+
role="dialog"
|
|
400
|
+
aria-label={label ?? restProps['aria-label']}
|
|
401
|
+
tabindex="-1"
|
|
402
|
+
class={surfaceCls.surface()}
|
|
403
|
+
onkeydown={(e) => {
|
|
404
|
+
if (e.key === 'Escape') {
|
|
405
|
+
e.stopPropagation();
|
|
406
|
+
closeSurface();
|
|
407
|
+
}
|
|
408
|
+
}}
|
|
409
|
+
{@attach growFromFab}
|
|
410
|
+
{@attach clickOutside(closeSurface)}
|
|
411
|
+
>
|
|
412
|
+
<div data-fab-surface-content>
|
|
413
|
+
{@render surface({ close: closeSurface })}
|
|
414
|
+
</div>
|
|
415
|
+
</div>
|
|
416
|
+
</div>
|
|
417
|
+
{/if}
|
|
418
|
+
</span>
|
|
95
419
|
{:else}
|
|
96
420
|
{@render fabButton({})}
|
|
97
421
|
{/if}
|
|
@@ -2,13 +2,18 @@ import type { FABProps } from './types.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Floating action buttons (FABs) help people take primary actions.
|
|
4
4
|
*
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* - Sizes: regular (56dp, default), medium (80dp), large (96dp); small (40dp) is baseline only
|
|
6
|
+
* - `expanded` + `label`: the extended FAB, whose label slides open beside the icon. Responsive:
|
|
7
|
+
* a FAB on compact windows and an extended FAB from medium up by default, and inside a `Rail`
|
|
8
|
+
* it follows the rail's collapsed state
|
|
9
|
+
* - `withMenu`: opens a FAB menu of `FABMenuItem`s; the FAB becomes its 56dp close button
|
|
10
|
+
* - `surface`: opens into a surface with a container transform (FAB → sheet); the FAB's
|
|
11
|
+
* container grows into it and changes colour on the way
|
|
9
12
|
*
|
|
10
|
-
* @see https://m3.material.io/components/floating-action-button/
|
|
13
|
+
* @see https://m3.material.io/components/floating-action-button/specs
|
|
14
|
+
* @see https://m3.material.io/components/extended-fab/specs
|
|
15
|
+
* @see https://m3.material.io/components/fab-menu/specs
|
|
11
16
|
*/
|
|
12
|
-
declare const FAB: import("svelte").Component<FABProps, {}, "">;
|
|
17
|
+
declare const FAB: import("svelte").Component<FABProps, {}, "surfaceOpen">;
|
|
13
18
|
type FAB = ReturnType<typeof FAB>;
|
|
14
19
|
export default FAB;
|