@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.
Files changed (316) hide show
  1. package/bin/material-claude-skill.js +31 -0
  2. package/claude-skill/material-design/SKILL.md +68 -0
  3. package/claude-skill/material-design/references/accessibility-checklist.md +29 -0
  4. package/claude-skill/material-design/references/component-patterns.md +55 -0
  5. package/claude-skill/material-design/references/motion-guide.md +163 -0
  6. package/claude-skill/material-design/references/tokens-and-styles.md +68 -0
  7. package/dist/animation/Shapes.stories.svelte +60 -0
  8. package/dist/animation/Shapes.stories.svelte.d.ts +3 -0
  9. package/dist/animation/TransitionPatterns.stories.svelte +32 -20
  10. package/dist/animation/containerTransform.d.ts +11 -0
  11. package/dist/animation/containerTransform.js +11 -0
  12. package/dist/animation/enterExit.d.ts +9 -0
  13. package/dist/animation/enterExit.js +6 -3
  14. package/dist/animation/index.d.ts +1 -0
  15. package/dist/animation/index.js +1 -0
  16. package/dist/animation/shapeMorph.svelte.d.ts +52 -0
  17. package/dist/animation/shapeMorph.svelte.js +112 -0
  18. package/dist/animation/shapesAnimatableSmall.d.ts +28 -22
  19. package/dist/animation/shapesAnimatableSmall.js +32 -23
  20. package/dist/animation/sharedAxisTransition.d.ts +22 -0
  21. package/dist/animation/sharedAxisTransition.js +25 -1
  22. package/dist/animation/skeleton.d.ts +6 -1
  23. package/dist/animation/skeleton.js +6 -1
  24. package/dist/components/badge/Badge.mdx +70 -0
  25. package/dist/components/badge/Badge.stories.svelte +38 -20
  26. package/dist/components/badge/Badge.svelte +12 -5
  27. package/dist/components/badge/theme.d.ts +30 -7
  28. package/dist/components/badge/theme.js +24 -4
  29. package/dist/components/badge/types.d.ts +18 -4
  30. package/dist/components/buttons/Button.mdx +85 -0
  31. package/dist/components/buttons/Button.stories.svelte +62 -27
  32. package/dist/components/buttons/Button.svelte +29 -25
  33. package/dist/components/buttons/Button.svelte.d.ts +11 -9
  34. package/dist/components/buttons/ButtonIcon.mdx +52 -0
  35. package/dist/components/buttons/ButtonIcon.stories.svelte +72 -48
  36. package/dist/components/buttons/ButtonIcon.svelte +58 -36
  37. package/dist/components/buttons/ButtonIcon.svelte.d.ts +9 -7
  38. package/dist/components/buttons/FAB.mdx +84 -0
  39. package/dist/components/buttons/FAB.stories.svelte +103 -24
  40. package/dist/components/buttons/FAB.stories.svelte.d.ts +2 -17
  41. package/dist/components/buttons/FAB.svelte +353 -29
  42. package/dist/components/buttons/FAB.svelte.d.ts +11 -6
  43. package/dist/components/buttons/FABMenu.svelte +27 -8
  44. package/dist/components/buttons/FABMenu.svelte.d.ts +7 -3
  45. package/dist/components/buttons/FABMenuItem.svelte +20 -16
  46. package/dist/components/buttons/FABMenuItem.svelte.d.ts +3 -3
  47. package/dist/components/buttons/Toggle.stories.svelte +31 -23
  48. package/dist/components/buttons/Toggle.stories.svelte.d.ts +2 -17
  49. package/dist/components/buttons/Toggle.svelte +28 -25
  50. package/dist/components/buttons/Toggle.svelte.d.ts +5 -5
  51. package/dist/components/buttons/button-group/ButtonGroup.mdx +39 -0
  52. package/dist/components/buttons/button-group/ButtonGroup.stories.svelte +62 -43
  53. package/dist/components/buttons/button-group/ButtonGroup.stories.svelte.d.ts +2 -17
  54. package/dist/components/buttons/button-group/ButtonGroup.svelte +102 -8
  55. package/dist/components/buttons/button-group/ButtonGroup.svelte.d.ts +6 -7
  56. package/dist/components/buttons/button-group/theme.d.ts +45 -15
  57. package/dist/components/buttons/button-group/theme.js +13 -8
  58. package/dist/components/buttons/button-group/types.d.ts +13 -7
  59. package/dist/components/buttons/connected/ConnectedButtonGroup.mdx +43 -0
  60. package/dist/components/buttons/connected/ConnectedButtonGroup.stories.svelte +56 -74
  61. package/dist/components/buttons/connected/ConnectedButtonGroup.stories.svelte.d.ts +2 -17
  62. package/dist/components/buttons/connected/ConnectedButtonGroup.svelte +16 -4
  63. package/dist/components/buttons/connected/ConnectedButtonGroup.svelte.d.ts +4 -4
  64. package/dist/components/buttons/connected/ConnectedButtonGroupItem.svelte +35 -8
  65. package/dist/components/buttons/connected/ConnectedButtonGroupItem.svelte.d.ts +2 -3
  66. package/dist/components/buttons/connected/theme.d.ts +33 -18
  67. package/dist/components/buttons/connected/theme.js +32 -9
  68. package/dist/components/buttons/connected/types.d.ts +36 -9
  69. package/dist/components/buttons/shapeMorph.d.ts +26 -0
  70. package/dist/components/buttons/shapeMorph.js +142 -0
  71. package/dist/components/buttons/split-button/SplitButton.mdx +48 -0
  72. package/dist/components/buttons/split-button/SplitButton.stories.svelte +9 -14
  73. package/dist/components/buttons/split-button/SplitButton.svelte +65 -48
  74. package/dist/components/buttons/split-button/SplitButton.svelte.d.ts +6 -11
  75. package/dist/components/buttons/split-button/theme.d.ts +110 -17
  76. package/dist/components/buttons/split-button/theme.js +74 -16
  77. package/dist/components/buttons/split-button/types.d.ts +24 -18
  78. package/dist/components/buttons/theme.d.ts +422 -230
  79. package/dist/components/buttons/theme.js +286 -380
  80. package/dist/components/buttons/types.d.ts +130 -125
  81. package/dist/components/cards/Card.mdx +44 -0
  82. package/dist/components/cards/Card.stories.svelte +11 -11
  83. package/dist/components/cards/theme.js +5 -5
  84. package/dist/components/chips/Chip.mdx +68 -0
  85. package/dist/components/chips/Chip.stories.svelte +4 -4
  86. package/dist/components/chips/theme.js +3 -3
  87. package/dist/components/containers/action-rail/ActionRail.stories.svelte +1 -0
  88. package/dist/components/containers/action-rail/theme.js +1 -1
  89. package/dist/components/containers/bottom-sheet/BottomSheet.mdx +64 -0
  90. package/dist/components/containers/bottom-sheet/BottomSheet.stories.svelte +108 -12
  91. package/dist/components/containers/bottom-sheet/BottomSheet.svelte +179 -109
  92. package/dist/components/containers/bottom-sheet/BottomSheet.svelte.d.ts +10 -5
  93. package/dist/components/containers/bottom-sheet/index.d.ts +1 -0
  94. package/dist/components/containers/bottom-sheet/index.js +1 -0
  95. package/dist/components/containers/bottom-sheet/theme.d.ts +45 -0
  96. package/dist/components/containers/bottom-sheet/theme.js +42 -0
  97. package/dist/components/containers/bottom-sheet/types.d.ts +37 -5
  98. package/dist/components/containers/carousel/Carousel.mdx +80 -0
  99. package/dist/components/containers/carousel/Carousel.stories.svelte +131 -0
  100. package/dist/components/containers/carousel/Carousel.stories.svelte.d.ts +4 -0
  101. package/dist/components/containers/carousel/Carousel.svelte +194 -0
  102. package/dist/components/containers/carousel/Carousel.svelte.d.ts +25 -0
  103. package/dist/components/containers/carousel/index.d.ts +3 -0
  104. package/dist/components/containers/{stack → carousel}/index.js +1 -2
  105. package/dist/components/containers/carousel/keylines.d.ts +32 -0
  106. package/dist/components/containers/carousel/keylines.js +88 -0
  107. package/dist/components/containers/carousel/theme.d.ts +162 -0
  108. package/dist/components/containers/carousel/theme.js +67 -0
  109. package/dist/components/containers/carousel/types.d.ts +52 -0
  110. package/dist/components/containers/context-menu/ContextMenu.stories.svelte +1 -0
  111. package/dist/components/containers/context-menu/ContextMenu.svelte +2 -2
  112. package/dist/components/containers/context-menu/ContextMenu.svelte.d.ts +1 -1
  113. package/dist/components/containers/context-menu/theme.js +10 -10
  114. package/dist/components/containers/dialogue/Dialogue.mdx +65 -0
  115. package/dist/components/containers/dialogue/Dialogue.stories.svelte +4 -4
  116. package/dist/components/containers/dialogue/Dialogue.svelte +1 -1
  117. package/dist/components/containers/dialogue/theme.js +3 -3
  118. package/dist/components/containers/divider/Divider.stories.svelte +2 -1
  119. package/dist/components/containers/divider/Divider.svelte +2 -2
  120. package/dist/components/containers/divider/theme.d.ts +6 -6
  121. package/dist/components/containers/divider/theme.js +2 -2
  122. package/dist/components/containers/index.d.ts +1 -1
  123. package/dist/components/containers/index.js +1 -1
  124. package/dist/components/containers/link-preview/theme.js +2 -2
  125. package/dist/components/containers/list/List.svelte +18 -0
  126. package/dist/components/containers/list/List.svelte.d.ts +4 -0
  127. package/dist/components/containers/list/ListItem.mdx +100 -0
  128. package/dist/components/containers/list/ListItem.stories.svelte +177 -42
  129. package/dist/components/containers/list/ListItem.stories.svelte.d.ts +2 -17
  130. package/dist/components/containers/list/ListItem.svelte +92 -37
  131. package/dist/components/containers/list/ListItem.svelte.d.ts +1 -1
  132. package/dist/components/containers/list/context.d.ts +6 -0
  133. package/dist/components/containers/list/context.js +4 -0
  134. package/dist/components/containers/list/index.d.ts +1 -0
  135. package/dist/components/containers/list/index.js +1 -0
  136. package/dist/components/containers/list/theme.d.ts +487 -12
  137. package/dist/components/containers/list/theme.js +140 -13
  138. package/dist/components/containers/list/types.d.ts +39 -5
  139. package/dist/components/containers/menu/Menu.mdx +52 -0
  140. package/dist/components/containers/menu/Menu.stories.svelte +14 -14
  141. package/dist/components/containers/menu/Menu.svelte +1 -1
  142. package/dist/components/containers/menu/MenuGroup.svelte +1 -1
  143. package/dist/components/containers/menu/MenuGroup.svelte.d.ts +1 -1
  144. package/dist/components/containers/menu/menu-item/MenuItem.svelte +1 -1
  145. package/dist/components/containers/menu/theme.js +8 -8
  146. package/dist/components/containers/pane/DraggablePane.stories.svelte +4 -3
  147. package/dist/components/containers/pane/DraggablePane.svelte +2 -2
  148. package/dist/components/containers/pane/Pane.stories.svelte +11 -10
  149. package/dist/components/containers/pane/Pane.svelte +24 -2
  150. package/dist/components/containers/pane/PaneGrid.stories.svelte +19 -18
  151. package/dist/components/containers/pane/PaneGrid.svelte +3 -2
  152. package/dist/components/containers/pane/PaneGrid.svelte.d.ts +2 -1
  153. package/dist/components/containers/pane/PaneHandle.svelte +48 -2
  154. package/dist/components/containers/pane/PaneHandle.svelte.d.ts +4 -0
  155. package/dist/components/containers/pane/resizeStore.svelte.d.ts +10 -1
  156. package/dist/components/containers/pane/resizeStore.svelte.js +13 -2
  157. package/dist/components/containers/pane/theme.js +129 -45
  158. package/dist/components/containers/pane/types.d.ts +3 -2
  159. package/dist/components/containers/popover/Popover.mdx +45 -0
  160. package/dist/components/containers/popover/Popover.stories.svelte +1 -1
  161. package/dist/components/containers/popover/Popover.svelte +1 -1
  162. package/dist/components/containers/popover/theme.js +3 -3
  163. package/dist/components/containers/scroll-area/theme.js +2 -2
  164. package/dist/components/containers/side-sheet/SideSheet.mdx +71 -0
  165. package/dist/components/containers/side-sheet/SideSheet.stories.svelte +113 -17
  166. package/dist/components/containers/side-sheet/SideSheet.svelte +113 -66
  167. package/dist/components/containers/side-sheet/SideSheet.svelte.d.ts +8 -7
  168. package/dist/components/containers/side-sheet/index.d.ts +1 -0
  169. package/dist/components/containers/side-sheet/index.js +1 -0
  170. package/dist/components/containers/side-sheet/theme.d.ts +108 -0
  171. package/dist/components/containers/side-sheet/theme.js +72 -0
  172. package/dist/components/containers/side-sheet/types.d.ts +35 -7
  173. package/dist/components/date/DateField.stories.svelte +4 -3
  174. package/dist/components/date/DateField.svelte +49 -26
  175. package/dist/components/date/DateField.svelte.d.ts +2 -0
  176. package/dist/components/date/DateRangeField.stories.svelte +4 -3
  177. package/dist/components/date/DateRangeField.svelte +54 -31
  178. package/dist/components/date/DateRangeField.svelte.d.ts +2 -0
  179. package/dist/components/date/calendarMotion.d.ts +9 -0
  180. package/dist/components/date/calendarMotion.js +23 -0
  181. package/dist/components/date/theme.d.ts +7 -7
  182. package/dist/components/date/theme.js +34 -30
  183. package/dist/components/forms/checkbox/Checkbox.mdx +35 -0
  184. package/dist/components/forms/checkbox/Checkbox.stories.svelte +2 -2
  185. package/dist/components/forms/checkbox/Checkbox.svelte +1 -1
  186. package/dist/components/forms/checkbox/theme.js +3 -3
  187. package/dist/components/forms/command/Command.stories.svelte +1 -0
  188. package/dist/components/forms/command/theme.js +9 -9
  189. package/dist/components/forms/pin/theme.js +4 -4
  190. package/dist/components/forms/radio-group/RadioGroup.svelte +1 -1
  191. package/dist/components/forms/radio-group/theme.js +5 -5
  192. package/dist/components/forms/search/Search.mdx +38 -0
  193. package/dist/components/forms/search/Search.stories.svelte +19 -9
  194. package/dist/components/forms/search/Search.svelte +40 -17
  195. package/dist/components/forms/search/Search.svelte.d.ts +2 -2
  196. package/dist/components/forms/search/theme.d.ts +45 -12
  197. package/dist/components/forms/search/theme.js +13 -8
  198. package/dist/components/forms/search/types.d.ts +18 -12
  199. package/dist/components/forms/select/Select.mdx +53 -0
  200. package/dist/components/forms/select/Select.stories.svelte +4 -4
  201. package/dist/components/forms/select/Select.svelte +10 -2
  202. package/dist/components/forms/select/SelectItem.svelte +1 -1
  203. package/dist/components/forms/select/theme.js +12 -12
  204. package/dist/components/forms/slider/Slider.mdx +85 -0
  205. package/dist/components/forms/slider/Slider.stories.svelte +7 -7
  206. package/dist/components/forms/slider/Slider.svelte +46 -28
  207. package/dist/components/forms/slider/theme.d.ts +14 -0
  208. package/dist/components/forms/slider/theme.js +28 -17
  209. package/dist/components/forms/slider/types.d.ts +3 -2
  210. package/dist/components/forms/switch/Switch.mdx +38 -0
  211. package/dist/components/forms/switch/Switch.stories.svelte +3 -3
  212. package/dist/components/forms/switch/theme.js +6 -6
  213. package/dist/components/forms/textfield/Textfield.mdx +53 -0
  214. package/dist/components/forms/textfield/Textfield.stories.svelte +6 -6
  215. package/dist/components/forms/textfield/Textfield.svelte +1 -0
  216. package/dist/components/forms/textfield/theme.d.ts +3 -0
  217. package/dist/components/forms/textfield/theme.js +22 -19
  218. package/dist/components/forms/toggle-group/theme.js +2 -2
  219. package/dist/components/forms/tooltip/Tooltip.stories.svelte +1 -0
  220. package/dist/components/forms/tooltip/theme.js +3 -3
  221. package/dist/components/misc/Avatar.stories.svelte +5 -4
  222. package/dist/components/misc/ThemeSettings.svelte +9 -9
  223. package/dist/components/misc/ThemeSwitcher.svelte +3 -3
  224. package/dist/components/misc/theme.js +4 -4
  225. package/dist/components/nav/appbar/AppBar.mdx +71 -0
  226. package/dist/components/nav/appbar/AppBar.stories.svelte +93 -3
  227. package/dist/components/nav/appbar/AppBar.stories.svelte.d.ts +2 -17
  228. package/dist/components/nav/appbar/AppBar.svelte +90 -37
  229. package/dist/components/nav/appbar/AppBar.svelte.d.ts +9 -2
  230. package/dist/components/nav/appbar/theme.d.ts +85 -18
  231. package/dist/components/nav/appbar/theme.js +159 -23
  232. package/dist/components/nav/appbar/types.d.ts +46 -9
  233. package/dist/components/nav/breadcrumb/Breadcrumb.stories.svelte +1 -0
  234. package/dist/components/nav/breadcrumb/theme.js +5 -5
  235. package/dist/components/nav/navbar/Navbar.mdx +38 -0
  236. package/dist/components/nav/navbar/Navbar.svelte +1 -1
  237. package/dist/components/nav/navbar/NavbarItem.svelte +12 -9
  238. package/dist/components/nav/navbar/theme.js +6 -6
  239. package/dist/components/nav/rail/Rail.mdx +73 -0
  240. package/dist/components/nav/rail/Rail.stories.svelte +3 -7
  241. package/dist/components/nav/rail/Rail.svelte +1 -0
  242. package/dist/components/nav/rail/RailItem.svelte +12 -9
  243. package/dist/components/nav/rail/theme.js +13 -13
  244. package/dist/components/nav/rail/types.d.ts +4 -1
  245. package/dist/components/nav/tabs/TabHolder.svelte +29 -3
  246. package/dist/components/nav/tabs/TabHolder.svelte.d.ts +3 -1
  247. package/dist/components/nav/tabs/Tabs.mdx +62 -0
  248. package/dist/components/nav/tabs/Tabs.stories.svelte +1 -1
  249. package/dist/components/nav/tabs/theme.js +4 -4
  250. package/dist/components/pill/Pill.mdx +32 -0
  251. package/dist/components/pill/Pill.stories.svelte +1 -1
  252. package/dist/components/pill/theme.d.ts +1 -1
  253. package/dist/components/pill/theme.js +1 -1
  254. package/dist/components/progress/LinearProgress.svelte +1 -1
  255. package/dist/components/progress/Progress.mdx +50 -0
  256. package/dist/components/progress/Progress.stories.svelte +6 -6
  257. package/dist/components/snackbar/Snackbar.mdx +52 -0
  258. package/dist/components/snackbar/Snackbar.stories.svelte +5 -5
  259. package/dist/components/snackbar/Snackbar.svelte +1 -1
  260. package/dist/components/snackbar/theme.d.ts +3 -3
  261. package/dist/components/snackbar/theme.js +4 -4
  262. package/dist/components/table/Table.stories.svelte +1 -0
  263. package/dist/components/table/TableHeader.svelte +1 -1
  264. package/dist/components/table/theme.js +3 -3
  265. package/dist/components/time/TimeField.stories.svelte +3 -2
  266. package/dist/components/time/TimeField.svelte +14 -8
  267. package/dist/components/time/TimeField.svelte.d.ts +6 -0
  268. package/dist/components/time/TimepickerInput.svelte +9 -5
  269. package/dist/components/time/theme.js +5 -5
  270. package/dist/components/time/types.d.ts +11 -1
  271. package/dist/components/toolbar/Toolbar.stories.svelte +1 -0
  272. package/dist/components/toolbar/Toolbar.svelte +1 -1
  273. package/dist/components/toolbar/Toolbar.svelte.d.ts +1 -1
  274. package/dist/components/toolbar/theme.js +14 -14
  275. package/dist/components/typography/Typography.mdx +67 -0
  276. package/dist/components/typography/body/Body.stories.svelte +2 -1
  277. package/dist/components/typography/body/theme.d.ts +15 -12
  278. package/dist/components/typography/body/theme.js +19 -4
  279. package/dist/components/typography/body/types.d.ts +2 -1
  280. package/dist/components/typography/display/Display.stories.svelte +2 -1
  281. package/dist/components/typography/display/theme.d.ts +15 -12
  282. package/dist/components/typography/display/theme.js +19 -4
  283. package/dist/components/typography/display/types.d.ts +2 -1
  284. package/dist/components/typography/headline/Headline.stories.svelte +2 -1
  285. package/dist/components/typography/headline/theme.d.ts +15 -12
  286. package/dist/components/typography/headline/theme.js +19 -4
  287. package/dist/components/typography/headline/types.d.ts +2 -1
  288. package/dist/components/typography/kbd/Kbd.stories.svelte +2 -1
  289. package/dist/components/typography/kbd/theme.d.ts +4 -4
  290. package/dist/components/typography/kbd/theme.js +2 -2
  291. package/dist/components/typography/label/Label.stories.svelte +2 -1
  292. package/dist/components/typography/label/theme.d.ts +15 -12
  293. package/dist/components/typography/label/theme.js +19 -4
  294. package/dist/components/typography/label/types.d.ts +2 -1
  295. package/dist/components/typography/title/Title.stories.svelte +2 -1
  296. package/dist/components/typography/title/theme.d.ts +15 -12
  297. package/dist/components/typography/title/theme.js +19 -4
  298. package/dist/components/typography/title/types.d.ts +2 -1
  299. package/dist/index.css +1 -0
  300. package/dist/styles/component.css +78 -175
  301. package/dist/styles/elevation.css +9 -0
  302. package/dist/styles/spacing.css +29 -0
  303. package/dist/styles/typescale.css +635 -20
  304. package/dist/utils/Layer.svelte +1 -1
  305. package/dist/utils/icon/Icon.svelte +8 -6
  306. package/dist/utils/icon/loadingShapes.js +11 -10
  307. package/package.json +19 -15
  308. package/dist/components/containers/stack/HStack.svelte +0 -23
  309. package/dist/components/containers/stack/HStack.svelte.d.ts +0 -5
  310. package/dist/components/containers/stack/VStack.svelte +0 -23
  311. package/dist/components/containers/stack/VStack.svelte.d.ts +0 -5
  312. package/dist/components/containers/stack/index.d.ts +0 -4
  313. package/dist/components/containers/stack/theme.d.ts +0 -66
  314. package/dist/components/containers/stack/theme.js +0 -26
  315. package/dist/components/containers/stack/types.d.ts +0 -12
  316. /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
- config: {
11
- control: 'select',
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
- config: 'primary',
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
- <Story name="Playground" />
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="flex flex-wrap items-end gap-4 p-6">
39
- <FAB size="small" config="primary" iconProps={{ name: 'edit' }} />
40
- <FAB size="regular" config="primary" iconProps={{ name: 'edit' }} />
41
- <FAB size="large" config="primary" iconProps={{ name: 'edit' }} />
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-wrap items-center gap-4 p-6">
47
- <FAB config="primary" expanded label="Compose" iconProps={{ name: 'edit' }} />
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="With Menu" asChild>
52
- <div class="relative flex h-40 items-end gap-4 p-6">
53
- <FAB config="primary" withMenu iconProps={{ name: 'add' }}>
54
- <FABMenuItem variant="primary" iconProps={{ name: 'draw' }}>Draw</FABMenuItem>
55
- <FABMenuItem variant="secondary" iconProps={{ name: 'image' }}>Insert Image</FABMenuItem>
56
- <FABMenuItem variant="tertiary" iconProps={{ name: 'mic' }}>Record</FABMenuItem>
57
- </FAB>
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
- interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
3
- new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
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
- - Regular: Standard size
6
- - Small: For secondary actions
7
- - Large: For prominent actions
8
- - Extended: Includes a text label
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/overview
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 { fab } from './theme.js';
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
- config = 'primary',
36
+ color = 'primary-container',
26
37
  iconProps,
27
38
  label,
28
39
  loading,
29
- expanded = false,
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
- const { base, icon, label: labelClass } = $derived(fab({ size, config, expanded }));
49
+ let menuOpen = $state(false);
50
+ const menuSet = $derived(fabMenuSet(color));
51
+ setContext('fabMenuSet', () => menuSet);
37
52
 
38
- let showMenu = $state(false);
39
-
40
- const btnCls = $derived(
41
- base({
42
- class: clsx(
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={btnCls}
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 {...withMenu && showMenu ? { name: 'close' } : iconProps} class={icon()} />
74
- <Layer />
355
+ <Icon
356
+ {...open ? { name: 'close' } : iconProps}
357
+ class={cls.icon({ class: clsx(!open && iconProps.class) })}
358
+ />
75
359
  {/if}
76
- {#if expanded && !(withMenu && showMenu)}
77
- <p class={labelClass()}>
78
- {label}
79
- </p>
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={showMenu}>
376
+ <DropdownMenu.Root bind:open={menuOpen}>
86
377
  <DropdownMenu.Trigger disabled={disabled || loading}>
87
378
  {#snippet child({ props })}
88
- {@render fabButton(props)}
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
- * - Regular: Standard size
6
- * - Small: For secondary actions
7
- * - Large: For prominent actions
8
- * - Extended: Includes a text label
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/overview
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;