@noxlovette/material 0.5.1 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (329) 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 +10 -6
  8. package/dist/animation/TransitionPatterns.stories.svelte +30 -20
  9. package/dist/animation/containerTransform.d.ts +4 -2
  10. package/dist/animation/containerTransform.js +4 -2
  11. package/dist/animation/enterExit.js +5 -2
  12. package/dist/animation/skeleton.d.ts +1 -1
  13. package/dist/animation/skeleton.js +1 -1
  14. package/dist/components/badge/Badge.mdx +70 -0
  15. package/dist/components/badge/Badge.stories.svelte +38 -20
  16. package/dist/components/badge/Badge.svelte +12 -5
  17. package/dist/components/badge/theme.d.ts +30 -7
  18. package/dist/components/badge/theme.js +25 -5
  19. package/dist/components/badge/types.d.ts +18 -4
  20. package/dist/components/buttons/Button.mdx +85 -0
  21. package/dist/components/buttons/Button.stories.svelte +62 -27
  22. package/dist/components/buttons/Button.svelte +29 -25
  23. package/dist/components/buttons/Button.svelte.d.ts +11 -9
  24. package/dist/components/buttons/ButtonIcon.mdx +52 -0
  25. package/dist/components/buttons/ButtonIcon.stories.svelte +72 -48
  26. package/dist/components/buttons/ButtonIcon.svelte +58 -36
  27. package/dist/components/buttons/ButtonIcon.svelte.d.ts +9 -7
  28. package/dist/components/buttons/FAB.mdx +84 -0
  29. package/dist/components/buttons/FAB.stories.svelte +103 -24
  30. package/dist/components/buttons/FAB.stories.svelte.d.ts +2 -17
  31. package/dist/components/buttons/FAB.svelte +353 -29
  32. package/dist/components/buttons/FAB.svelte.d.ts +11 -6
  33. package/dist/components/buttons/FABMenu.svelte +27 -8
  34. package/dist/components/buttons/FABMenu.svelte.d.ts +7 -3
  35. package/dist/components/buttons/FABMenuItem.svelte +20 -16
  36. package/dist/components/buttons/FABMenuItem.svelte.d.ts +3 -3
  37. package/dist/components/buttons/Toggle.stories.svelte +31 -23
  38. package/dist/components/buttons/Toggle.stories.svelte.d.ts +2 -17
  39. package/dist/components/buttons/Toggle.svelte +28 -25
  40. package/dist/components/buttons/Toggle.svelte.d.ts +5 -5
  41. package/dist/components/buttons/button-group/ButtonGroup.mdx +39 -0
  42. package/dist/components/buttons/button-group/ButtonGroup.stories.svelte +62 -43
  43. package/dist/components/buttons/button-group/ButtonGroup.stories.svelte.d.ts +2 -17
  44. package/dist/components/buttons/button-group/ButtonGroup.svelte +102 -8
  45. package/dist/components/buttons/button-group/ButtonGroup.svelte.d.ts +6 -7
  46. package/dist/components/buttons/button-group/theme.d.ts +46 -16
  47. package/dist/components/buttons/button-group/theme.js +14 -9
  48. package/dist/components/buttons/button-group/types.d.ts +13 -7
  49. package/dist/components/buttons/connected/ConnectedButtonGroup.mdx +43 -0
  50. package/dist/components/buttons/connected/ConnectedButtonGroup.stories.svelte +56 -74
  51. package/dist/components/buttons/connected/ConnectedButtonGroup.stories.svelte.d.ts +2 -17
  52. package/dist/components/buttons/connected/ConnectedButtonGroup.svelte +16 -4
  53. package/dist/components/buttons/connected/ConnectedButtonGroup.svelte.d.ts +4 -4
  54. package/dist/components/buttons/connected/ConnectedButtonGroupItem.svelte +35 -8
  55. package/dist/components/buttons/connected/ConnectedButtonGroupItem.svelte.d.ts +2 -3
  56. package/dist/components/buttons/connected/theme.d.ts +34 -19
  57. package/dist/components/buttons/connected/theme.js +33 -10
  58. package/dist/components/buttons/connected/types.d.ts +36 -9
  59. package/dist/components/buttons/shapeMorph.d.ts +26 -0
  60. package/dist/components/buttons/shapeMorph.js +142 -0
  61. package/dist/components/buttons/split-button/SplitButton.mdx +48 -0
  62. package/dist/components/buttons/split-button/SplitButton.stories.svelte +9 -14
  63. package/dist/components/buttons/split-button/SplitButton.svelte +66 -49
  64. package/dist/components/buttons/split-button/SplitButton.svelte.d.ts +6 -11
  65. package/dist/components/buttons/split-button/theme.d.ts +111 -18
  66. package/dist/components/buttons/split-button/theme.js +75 -17
  67. package/dist/components/buttons/split-button/types.d.ts +24 -18
  68. package/dist/components/buttons/theme.d.ts +423 -231
  69. package/dist/components/buttons/theme.js +287 -381
  70. package/dist/components/buttons/types.d.ts +130 -125
  71. package/dist/components/cards/Card.mdx +44 -0
  72. package/dist/components/cards/Card.stories.svelte +11 -11
  73. package/dist/components/cards/theme.js +6 -6
  74. package/dist/components/chips/Chip.mdx +68 -0
  75. package/dist/components/chips/Chip.stories.svelte +4 -4
  76. package/dist/components/chips/theme.d.ts +1 -1
  77. package/dist/components/chips/theme.js +4 -4
  78. package/dist/components/containers/action-rail/ActionRail.stories.svelte +1 -0
  79. package/dist/components/containers/action-rail/theme.d.ts +1 -1
  80. package/dist/components/containers/action-rail/theme.js +2 -2
  81. package/dist/components/containers/app/theme.d.ts +1 -1
  82. package/dist/components/containers/app/theme.js +1 -1
  83. package/dist/components/containers/bottom-sheet/BottomSheet.mdx +64 -0
  84. package/dist/components/containers/bottom-sheet/BottomSheet.stories.svelte +108 -12
  85. package/dist/components/containers/bottom-sheet/BottomSheet.svelte +179 -109
  86. package/dist/components/containers/bottom-sheet/BottomSheet.svelte.d.ts +10 -5
  87. package/dist/components/containers/bottom-sheet/index.d.ts +1 -0
  88. package/dist/components/containers/bottom-sheet/index.js +1 -0
  89. package/dist/components/containers/bottom-sheet/theme.d.ts +45 -0
  90. package/dist/components/containers/bottom-sheet/theme.js +42 -0
  91. package/dist/components/containers/bottom-sheet/types.d.ts +37 -5
  92. package/dist/components/containers/carousel/Carousel.mdx +80 -0
  93. package/dist/components/containers/carousel/Carousel.stories.svelte +131 -0
  94. package/dist/components/containers/carousel/Carousel.stories.svelte.d.ts +4 -0
  95. package/dist/components/containers/carousel/Carousel.svelte +194 -0
  96. package/dist/components/containers/carousel/Carousel.svelte.d.ts +25 -0
  97. package/dist/components/containers/carousel/index.d.ts +3 -0
  98. package/dist/components/containers/{stack → carousel}/index.js +1 -2
  99. package/dist/components/containers/carousel/keylines.d.ts +32 -0
  100. package/dist/components/containers/carousel/keylines.js +88 -0
  101. package/dist/components/containers/carousel/theme.d.ts +162 -0
  102. package/dist/components/containers/carousel/theme.js +67 -0
  103. package/dist/components/containers/carousel/types.d.ts +52 -0
  104. package/dist/components/containers/context-menu/ContextMenu.stories.svelte +1 -0
  105. package/dist/components/containers/context-menu/ContextMenu.svelte +2 -2
  106. package/dist/components/containers/context-menu/ContextMenu.svelte.d.ts +1 -1
  107. package/dist/components/containers/context-menu/theme.d.ts +1 -1
  108. package/dist/components/containers/context-menu/theme.js +11 -11
  109. package/dist/components/containers/dialogue/Dialogue.mdx +65 -0
  110. package/dist/components/containers/dialogue/Dialogue.stories.svelte +4 -4
  111. package/dist/components/containers/dialogue/Dialogue.svelte +2 -2
  112. package/dist/components/containers/dialogue/theme.d.ts +1 -1
  113. package/dist/components/containers/dialogue/theme.js +4 -4
  114. package/dist/components/containers/dialogue/types.d.ts +1 -1
  115. package/dist/components/containers/divider/Divider.stories.svelte +2 -1
  116. package/dist/components/containers/divider/Divider.svelte +2 -2
  117. package/dist/components/containers/divider/theme.d.ts +7 -7
  118. package/dist/components/containers/divider/theme.js +3 -3
  119. package/dist/components/containers/index.d.ts +1 -1
  120. package/dist/components/containers/index.js +1 -1
  121. package/dist/components/containers/link-preview/theme.d.ts +1 -1
  122. package/dist/components/containers/link-preview/theme.js +3 -3
  123. package/dist/components/containers/list/List.svelte +18 -0
  124. package/dist/components/containers/list/List.svelte.d.ts +4 -0
  125. package/dist/components/containers/list/ListItem.mdx +100 -0
  126. package/dist/components/containers/list/ListItem.stories.svelte +177 -42
  127. package/dist/components/containers/list/ListItem.stories.svelte.d.ts +2 -17
  128. package/dist/components/containers/list/ListItem.svelte +92 -37
  129. package/dist/components/containers/list/ListItem.svelte.d.ts +1 -1
  130. package/dist/components/containers/list/context.d.ts +6 -0
  131. package/dist/components/containers/list/context.js +4 -0
  132. package/dist/components/containers/list/index.d.ts +1 -0
  133. package/dist/components/containers/list/index.js +1 -0
  134. package/dist/components/containers/list/theme.d.ts +487 -12
  135. package/dist/components/containers/list/theme.js +141 -14
  136. package/dist/components/containers/list/types.d.ts +39 -5
  137. package/dist/components/containers/menu/Menu.mdx +52 -0
  138. package/dist/components/containers/menu/Menu.stories.svelte +14 -14
  139. package/dist/components/containers/menu/Menu.svelte +2 -2
  140. package/dist/components/containers/menu/MenuGroup.svelte +1 -1
  141. package/dist/components/containers/menu/MenuGroup.svelte.d.ts +1 -1
  142. package/dist/components/containers/menu/MenuSub.svelte +1 -1
  143. package/dist/components/containers/menu/menu-item/MenuItem.svelte +1 -1
  144. package/dist/components/containers/menu/theme.d.ts +1 -1
  145. package/dist/components/containers/menu/theme.js +9 -9
  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.d.ts +1 -1
  158. package/dist/components/containers/pane/theme.js +131 -47
  159. package/dist/components/containers/pane/types.d.ts +3 -2
  160. package/dist/components/containers/popover/Popover.mdx +45 -0
  161. package/dist/components/containers/popover/Popover.stories.svelte +1 -1
  162. package/dist/components/containers/popover/Popover.svelte +1 -1
  163. package/dist/components/containers/popover/theme.d.ts +1 -1
  164. package/dist/components/containers/popover/theme.js +4 -4
  165. package/dist/components/containers/scroll-area/theme.js +3 -3
  166. package/dist/components/containers/side-sheet/SideSheet.mdx +71 -0
  167. package/dist/components/containers/side-sheet/SideSheet.stories.svelte +113 -17
  168. package/dist/components/containers/side-sheet/SideSheet.svelte +113 -66
  169. package/dist/components/containers/side-sheet/SideSheet.svelte.d.ts +8 -7
  170. package/dist/components/containers/side-sheet/index.d.ts +1 -0
  171. package/dist/components/containers/side-sheet/index.js +1 -0
  172. package/dist/components/containers/side-sheet/theme.d.ts +108 -0
  173. package/dist/components/containers/side-sheet/theme.js +72 -0
  174. package/dist/components/containers/side-sheet/types.d.ts +35 -7
  175. package/dist/components/date/DateField.stories.svelte +4 -3
  176. package/dist/components/date/DateField.svelte +11 -2
  177. package/dist/components/date/DateRangeField.stories.svelte +4 -3
  178. package/dist/components/date/DateRangeField.svelte +12 -3
  179. package/dist/components/date/theme.d.ts +8 -8
  180. package/dist/components/date/theme.js +32 -32
  181. package/dist/components/forms/checkbox/Checkbox.mdx +35 -0
  182. package/dist/components/forms/checkbox/Checkbox.stories.svelte +2 -2
  183. package/dist/components/forms/checkbox/Checkbox.svelte +1 -1
  184. package/dist/components/forms/checkbox/theme.d.ts +1 -1
  185. package/dist/components/forms/checkbox/theme.js +4 -4
  186. package/dist/components/forms/command/Command.stories.svelte +1 -0
  187. package/dist/components/forms/command/theme.d.ts +1 -1
  188. package/dist/components/forms/command/theme.js +10 -10
  189. package/dist/components/forms/pin/theme.d.ts +1 -1
  190. package/dist/components/forms/pin/theme.js +5 -5
  191. package/dist/components/forms/radio-group/RadioGroup.svelte +1 -1
  192. package/dist/components/forms/radio-group/theme.d.ts +1 -1
  193. package/dist/components/forms/radio-group/theme.js +6 -6
  194. package/dist/components/forms/search/Search.mdx +38 -0
  195. package/dist/components/forms/search/Search.stories.svelte +19 -9
  196. package/dist/components/forms/search/Search.svelte +40 -17
  197. package/dist/components/forms/search/Search.svelte.d.ts +2 -2
  198. package/dist/components/forms/search/theme.d.ts +46 -13
  199. package/dist/components/forms/search/theme.js +14 -9
  200. package/dist/components/forms/search/types.d.ts +18 -12
  201. package/dist/components/forms/select/Select.mdx +53 -0
  202. package/dist/components/forms/select/Select.stories.svelte +4 -4
  203. package/dist/components/forms/select/Select.svelte +12 -4
  204. package/dist/components/forms/select/SelectItem.svelte +1 -1
  205. package/dist/components/forms/select/theme.d.ts +1 -1
  206. package/dist/components/forms/select/theme.js +14 -14
  207. package/dist/components/forms/slider/Slider.mdx +85 -0
  208. package/dist/components/forms/slider/Slider.stories.svelte +7 -7
  209. package/dist/components/forms/slider/Slider.svelte +46 -28
  210. package/dist/components/forms/slider/theme.d.ts +15 -1
  211. package/dist/components/forms/slider/theme.js +29 -18
  212. package/dist/components/forms/slider/types.d.ts +3 -2
  213. package/dist/components/forms/switch/Switch.mdx +38 -0
  214. package/dist/components/forms/switch/Switch.stories.svelte +3 -3
  215. package/dist/components/forms/switch/theme.d.ts +1 -1
  216. package/dist/components/forms/switch/theme.js +7 -7
  217. package/dist/components/forms/textfield/Textfield.mdx +53 -0
  218. package/dist/components/forms/textfield/Textfield.stories.svelte +6 -6
  219. package/dist/components/forms/textfield/Textfield.svelte +1 -0
  220. package/dist/components/forms/textfield/theme.d.ts +1 -1
  221. package/dist/components/forms/textfield/theme.js +20 -20
  222. package/dist/components/forms/toggle-group/theme.d.ts +1 -1
  223. package/dist/components/forms/toggle-group/theme.js +3 -3
  224. package/dist/components/forms/tooltip/Tooltip.stories.svelte +1 -0
  225. package/dist/components/forms/tooltip/theme.d.ts +1 -1
  226. package/dist/components/forms/tooltip/theme.js +4 -4
  227. package/dist/components/misc/Avatar.stories.svelte +5 -4
  228. package/dist/components/misc/ThemeSettings.svelte +9 -9
  229. package/dist/components/misc/ThemeSwitcher.svelte +3 -3
  230. package/dist/components/misc/theme.js +5 -5
  231. package/dist/components/nav/appbar/AppBar.mdx +71 -0
  232. package/dist/components/nav/appbar/AppBar.stories.svelte +93 -3
  233. package/dist/components/nav/appbar/AppBar.stories.svelte.d.ts +2 -17
  234. package/dist/components/nav/appbar/AppBar.svelte +90 -37
  235. package/dist/components/nav/appbar/AppBar.svelte.d.ts +9 -2
  236. package/dist/components/nav/appbar/theme.d.ts +86 -19
  237. package/dist/components/nav/appbar/theme.js +160 -24
  238. package/dist/components/nav/appbar/types.d.ts +46 -9
  239. package/dist/components/nav/breadcrumb/Breadcrumb.stories.svelte +1 -0
  240. package/dist/components/nav/breadcrumb/theme.d.ts +1 -1
  241. package/dist/components/nav/breadcrumb/theme.js +6 -6
  242. package/dist/components/nav/navbar/Navbar.mdx +38 -0
  243. package/dist/components/nav/navbar/Navbar.svelte +1 -1
  244. package/dist/components/nav/navbar/NavbarItem.svelte +12 -9
  245. package/dist/components/nav/navbar/theme.d.ts +3 -3
  246. package/dist/components/nav/navbar/theme.js +7 -7
  247. package/dist/components/nav/rail/Rail.mdx +73 -0
  248. package/dist/components/nav/rail/Rail.stories.svelte +3 -7
  249. package/dist/components/nav/rail/Rail.svelte +5 -2
  250. package/dist/components/nav/rail/RailItem.svelte +12 -9
  251. package/dist/components/nav/rail/theme.d.ts +1 -1
  252. package/dist/components/nav/rail/theme.js +14 -14
  253. package/dist/components/nav/rail/types.d.ts +8 -1
  254. package/dist/components/nav/tabs/Tabs.mdx +62 -0
  255. package/dist/components/nav/tabs/Tabs.stories.svelte +1 -1
  256. package/dist/components/nav/tabs/theme.d.ts +1 -1
  257. package/dist/components/nav/tabs/theme.js +5 -5
  258. package/dist/components/pill/Pill.mdx +32 -0
  259. package/dist/components/pill/Pill.stories.svelte +1 -1
  260. package/dist/components/pill/theme.d.ts +2 -2
  261. package/dist/components/pill/theme.js +2 -2
  262. package/dist/components/progress/LinearProgress.svelte +2 -2
  263. package/dist/components/progress/Progress.mdx +50 -0
  264. package/dist/components/progress/Progress.stories.svelte +6 -6
  265. package/dist/components/snackbar/Snackbar.mdx +52 -0
  266. package/dist/components/snackbar/Snackbar.stories.svelte +5 -5
  267. package/dist/components/snackbar/Snackbar.svelte +1 -1
  268. package/dist/components/snackbar/theme.d.ts +4 -4
  269. package/dist/components/snackbar/theme.js +5 -5
  270. package/dist/components/table/Table.stories.svelte +1 -0
  271. package/dist/components/table/TableHeader.svelte +1 -1
  272. package/dist/components/table/theme.d.ts +1 -1
  273. package/dist/components/table/theme.js +4 -4
  274. package/dist/components/time/TimeField.stories.svelte +3 -2
  275. package/dist/components/time/TimeField.svelte +2 -2
  276. package/dist/components/time/TimepickerInput.svelte +1 -1
  277. package/dist/components/time/theme.d.ts +1 -1
  278. package/dist/components/time/theme.js +6 -6
  279. package/dist/components/toolbar/Toolbar.stories.svelte +1 -0
  280. package/dist/components/toolbar/Toolbar.svelte +1 -1
  281. package/dist/components/toolbar/Toolbar.svelte.d.ts +1 -1
  282. package/dist/components/toolbar/theme.d.ts +1 -1
  283. package/dist/components/toolbar/theme.js +15 -15
  284. package/dist/components/typography/Typography.mdx +67 -0
  285. package/dist/components/typography/body/Body.stories.svelte +2 -1
  286. package/dist/components/typography/body/theme.d.ts +16 -13
  287. package/dist/components/typography/body/theme.js +20 -5
  288. package/dist/components/typography/body/types.d.ts +2 -1
  289. package/dist/components/typography/display/Display.stories.svelte +2 -1
  290. package/dist/components/typography/display/theme.d.ts +16 -13
  291. package/dist/components/typography/display/theme.js +20 -5
  292. package/dist/components/typography/display/types.d.ts +2 -1
  293. package/dist/components/typography/headline/Headline.stories.svelte +2 -1
  294. package/dist/components/typography/headline/theme.d.ts +16 -13
  295. package/dist/components/typography/headline/theme.js +20 -5
  296. package/dist/components/typography/headline/types.d.ts +2 -1
  297. package/dist/components/typography/kbd/Kbd.stories.svelte +2 -1
  298. package/dist/components/typography/kbd/theme.d.ts +5 -5
  299. package/dist/components/typography/kbd/theme.js +3 -3
  300. package/dist/components/typography/label/Label.stories.svelte +2 -1
  301. package/dist/components/typography/label/theme.d.ts +15 -12
  302. package/dist/components/typography/label/theme.js +20 -5
  303. package/dist/components/typography/label/types.d.ts +2 -1
  304. package/dist/components/typography/title/Title.stories.svelte +2 -1
  305. package/dist/components/typography/title/theme.d.ts +16 -13
  306. package/dist/components/typography/title/theme.js +20 -5
  307. package/dist/components/typography/title/types.d.ts +2 -1
  308. package/dist/index.css +2 -0
  309. package/dist/styles/component.css +78 -175
  310. package/dist/styles/elevation.css +9 -0
  311. package/dist/styles/layers.css +33 -0
  312. package/dist/styles/spacing.css +29 -0
  313. package/dist/styles/typescale.css +635 -20
  314. package/dist/utils/Layer.svelte +1 -1
  315. package/dist/utils/icon/Icon.svelte +8 -6
  316. package/dist/utils/icon/theme.d.ts +1 -1
  317. package/dist/utils/icon/theme.js +1 -1
  318. package/dist/utils/tv.d.ts +6 -0
  319. package/dist/utils/tv.js +12 -0
  320. package/package.json +19 -15
  321. package/dist/components/containers/stack/HStack.svelte +0 -23
  322. package/dist/components/containers/stack/HStack.svelte.d.ts +0 -5
  323. package/dist/components/containers/stack/VStack.svelte +0 -23
  324. package/dist/components/containers/stack/VStack.svelte.d.ts +0 -5
  325. package/dist/components/containers/stack/index.d.ts +0 -4
  326. package/dist/components/containers/stack/theme.d.ts +0 -66
  327. package/dist/components/containers/stack/theme.js +0 -26
  328. package/dist/components/containers/stack/types.d.ts +0 -12
  329. /package/dist/components/containers/{stack → carousel}/types.js +0 -0
@@ -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;
@@ -1,33 +1,52 @@
1
1
  <!--
2
2
  @component
3
- The floating panel of a FAB menu. Rendered by `FAB` when `withMenu` is set — must live inside
4
- its `DropdownMenu.Root`, which owns open state, positioning, and outside-click/Escape dismissal.
5
- Should be used with `FABMenuItem` as children.
3
+ The item stack of a FAB menu, rendered by `FAB` when `withMenu` is set. It lives inside the
4
+ FAB's `DropdownMenu.Root`, which owns open state, positioning, and outside-click/Escape
5
+ dismissal. Items are end-aligned, 4dp apart and 8dp above the close button. Use up to six
6
+ `FABMenuItem`s.
7
+
8
+ Motion: the items grow out of the close button one after another, nearest first, on the fast
9
+ spatial spring; the stack fades out on close.
6
10
  -->
7
11
  <script lang="ts">
8
12
  import clsx from 'clsx';
9
13
  import { DropdownMenu } from 'bits-ui';
14
+ import { animate, stagger } from 'motion';
15
+ import type { Attachment } from 'svelte/attachments';
10
16
  import { fabMenu } from './theme.js';
11
17
  import type { FABMenuProps } from './types.js';
12
18
  import { enterExit, presence } from '../../animation/index.js';
19
+ import { springTokens, springTransition } from '../../animation/spring.js';
13
20
 
14
21
  let { children, class: className, ...restProps }: FABMenuProps = $props();
15
22
 
16
23
  const { base } = fabMenu();
24
+
25
+ const STAGGER_S = 0.03;
26
+ const itemsIn: Attachment<HTMLElement> = (node) => {
27
+ if (matchMedia('(prefers-reduced-motion: reduce)').matches) return;
28
+ const items = [...node.children] as HTMLElement[];
29
+ for (const item of items) item.style.transformOrigin = 'right center';
30
+ const controls = animate(
31
+ items,
32
+ { y: [8, 0], scale: [0.6, 1] },
33
+ { ...springTransition(springTokens.fastSpatial), delay: stagger(STAGGER_S, { from: 'last' }) }
34
+ );
35
+ return () => controls.stop();
36
+ };
17
37
  </script>
18
38
 
19
39
  <DropdownMenu.Portal>
20
- <DropdownMenu.Content side="top" align="end" sideOffset={8} collisionPadding={8}>
40
+ <DropdownMenu.Content side="top" align="end" sideOffset={3} collisionPadding={8}>
21
41
  {#snippet child({ wrapperProps, props, open })}
22
42
  <div {...wrapperProps}>
23
43
  <div
24
44
  data-cy="m3-fab-menu"
25
45
  {...restProps}
26
46
  {...props}
27
- class={base({
28
- class: clsx(className, 'overflow-y-auto')
29
- })}
30
- {@attach presence(() => open, enterExit.scale)}
47
+ class={base({ class: clsx(className) })}
48
+ {@attach presence(() => open, enterExit.fade)}
49
+ {@attach itemsIn}
31
50
  >
32
51
  {@render children()}
33
52
  </div>
@@ -1,8 +1,12 @@
1
1
  import type { FABMenuProps } from './types.js';
2
2
  /**
3
- * The floating panel of a FAB menu. Rendered by `FAB` when `withMenu` is set — must live inside
4
- * its `DropdownMenu.Root`, which owns open state, positioning, and outside-click/Escape dismissal.
5
- * Should be used with `FABMenuItem` as children.
3
+ * The item stack of a FAB menu, rendered by `FAB` when `withMenu` is set. It lives inside the
4
+ * FAB's `DropdownMenu.Root`, which owns open state, positioning, and outside-click/Escape
5
+ * dismissal. Items are end-aligned, 4dp apart and 8dp above the close button. Use up to six
6
+ * `FABMenuItem`s.
7
+ *
8
+ * Motion: the items grow out of the close button one after another, nearest first, on the fast
9
+ * spatial spring; the stack fades out on close.
6
10
  */
7
11
  declare const FABMenu: import("svelte").Component<FABMenuProps, {}, "">;
8
12
  type FABMenu = ReturnType<typeof FABMenu>;
@@ -1,11 +1,12 @@
1
1
  <!--
2
2
  @component
3
- An individual action within a FAB menu. Wraps bits-ui's `DropdownMenu.Item`, so it gets the
4
- `menuitem` role, arrow-key navigation, and closes the menu when selected. Must be rendered
5
- inside a `FAB` with `withMenu`.
3
+ One action in a FAB menu: a 56dp pill in the FAB's colour set (primary, secondary or tertiary
4
+ container). Wraps bits-ui's `DropdownMenu.Item`, so it gets the `menuitem` role, arrow-key
5
+ navigation, and closes the menu when chosen. Render it inside a `FAB` with `withMenu`.
6
6
  -->
7
7
  <script lang="ts">
8
8
  import clsx from 'clsx';
9
+ import { getContext } from 'svelte';
9
10
  import { fabMenuItem } from './theme.js';
10
11
  import type { FABMenuItemProps } from './types.js';
11
12
  import { Icon, LoadingIndicator, Layer } from '../../utils/index.js';
@@ -13,7 +14,6 @@ inside a `FAB` with `withMenu`.
13
14
 
14
15
  let {
15
16
  class: className,
16
- variant = 'secondary',
17
17
  loading,
18
18
  children,
19
19
  iconProps,
@@ -22,24 +22,28 @@ inside a `FAB` with `withMenu`.
22
22
  ...restProps
23
23
  }: FABMenuItemProps = $props();
24
24
 
25
- const { base, icon } = $derived(fabMenuItem({ variant }));
26
- const btnCls = $derived(base({ class: clsx(className) }));
25
+ const menuSet = getContext<(() => 'primary' | 'secondary' | 'tertiary') | undefined>(
26
+ 'fabMenuSet'
27
+ );
28
+ const cls = $derived(fabMenuItem({ set: menuSet?.() ?? 'primary' }));
27
29
  </script>
28
30
 
29
31
  <!-- Spread onto the Item, not the button: bits-ui merges (and chains handlers of) these into `props`. -->
30
32
  <DropdownMenu.Item {disabled} {...restProps as DropdownMenuItemProps}>
31
33
  {#snippet child({ props })}
32
- <Button.Root {formaction} {disabled} data-cy="m3-fab-menu-item" {...props} class={btnCls}>
33
- {#if iconProps}
34
- {#if loading}
35
- <LoadingIndicator />
36
- {:else}
37
- <Icon class={icon()} {...iconProps} />
38
- {/if}
39
- {:else if loading}
40
- <LoadingIndicator />
34
+ <Button.Root
35
+ {formaction}
36
+ {disabled}
37
+ data-cy="m3-fab-menu-item"
38
+ {...props}
39
+ class={cls.base({ class: clsx(className) })}
40
+ >
41
+ {#if loading}
42
+ <LoadingIndicator center={false} class={cls.icon({ class: 'text-current' })} />
43
+ {:else}
44
+ <Icon {...iconProps} class={cls.icon({ class: clsx(iconProps.class) })} />
41
45
  {/if}
42
- {@render children?.()}
46
+ {@render children()}
43
47
  <Layer />
44
48
  </Button.Root>
45
49
  {/snippet}
@@ -1,8 +1,8 @@
1
1
  import type { FABMenuItemProps } from './types.js';
2
2
  /**
3
- * An individual action within a FAB menu. Wraps bits-ui's `DropdownMenu.Item`, so it gets the
4
- * `menuitem` role, arrow-key navigation, and closes the menu when selected. Must be rendered
5
- * inside a `FAB` with `withMenu`.
3
+ * One action in a FAB menu: a 56dp pill in the FAB's colour set (primary, secondary or tertiary
4
+ * container). Wraps bits-ui's `DropdownMenu.Item`, so it gets the `menuitem` role, arrow-key
5
+ * navigation, and closes the menu when chosen. Render it inside a `FAB` with `withMenu`.
6
6
  */
7
7
  declare const FABMenuItem: import("svelte").Component<FABMenuItemProps, {}, "">;
8
8
  type FABMenuItem = ReturnType<typeof FABMenuItem>;