@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
@@ -1,17 +1,19 @@
1
1
  <!--
2
2
  @component
3
- A standard button group — an invisible container that adds spacing between
4
- buttons and lets them mix variants, colors, and widths freely. Pressing a
5
- button also nudges the width of its immediate neighbors, per the M3 spec.
3
+ A standard button group: an invisible container that spaces buttons and adds an interaction
4
+ between neighbours. Pressing a button widens it by 15% while the buttons beside it narrow to
5
+ make room, on the fast spatial spring; a toggle inside also swaps its shape as it's selected.
6
+ Children keep their own style, so filled, tonal and outlined buttons and icon buttons mix freely.
6
7
 
7
- Buttons keep their own identity here; this is a plain layout wrapper around
8
- `Button` / `ButtonIcon` / `Toggle` children, not a selection primitive. For a
9
- single/multi-select segmented control, use `ConnectedButtonGroup` instead.
8
+ For a set of related options where one or more is selected, use `ConnectedButtonGroup`.
10
9
 
11
- @see https://m3.material.io/components/button-groups/overview
10
+ @see https://m3.material.io/components/button-groups/specs
12
11
  -->
13
12
  <script lang="ts">
14
13
  import clsx from 'clsx';
14
+ import { animate } from 'motion';
15
+ import type { Attachment } from 'svelte/attachments';
16
+ import { springTokens, springTransition } from '../../../animation/spring.js';
15
17
  import { buttonGroup } from './theme.js';
16
18
  import type { ButtonGroupProps } from './types.js';
17
19
 
@@ -19,12 +21,104 @@ single/multi-select segmented control, use `ConnectedButtonGroup` instead.
19
21
  orientation = 'horizontal',
20
22
  size = 'sm',
21
23
  children,
24
+ 'aria-label': ariaLabel,
22
25
  class: className
23
26
  }: ButtonGroupProps = $props();
24
27
 
25
28
  const cls = $derived(buttonGroup({ orientation, size }));
29
+
30
+ const GROW = 0.15;
31
+ const spring = springTransition(springTokens.fastSpatial);
32
+ const reducedMotion = () =>
33
+ typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;
34
+
35
+ // The pressed button and its neighbours, with their widths before the press.
36
+ type Press = { items: { el: HTMLElement; width: number; to: number }[] };
37
+
38
+ const squeeze: Attachment<HTMLElement> = (group) => {
39
+ let press: Press | null = null;
40
+ // Widths at rest, kept while a release is still settling so a quick re-press starts from them.
41
+ const rest = new WeakMap<HTMLElement, number>();
42
+
43
+ const isEnabled = (el: Element | null): el is HTMLElement =>
44
+ el instanceof HTMLElement &&
45
+ !el.matches(':disabled, [aria-disabled="true"], [data-disabled]');
46
+
47
+ const itemOf = (target: EventTarget | null) => {
48
+ if (!(target instanceof Node)) return null;
49
+ let el = target instanceof Element ? target : target.parentElement;
50
+ while (el && el.parentElement !== group) el = el.parentElement;
51
+ return el && isEnabled(el) ? el : null;
52
+ };
53
+
54
+ const widthOf = (el: HTMLElement) => {
55
+ if (!rest.has(el)) rest.set(el, el.getBoundingClientRect().width);
56
+ return rest.get(el)!;
57
+ };
58
+
59
+ const start = (el: HTMLElement) => {
60
+ if (orientation !== 'horizontal' || press || reducedMotion()) return;
61
+ const width = widthOf(el);
62
+ const neighbours = [el.previousElementSibling, el.nextElementSibling].filter(isEnabled);
63
+ const share = (width * GROW) / Math.max(neighbours.length, 1);
64
+ press = {
65
+ items: [
66
+ { el, width, to: width * (1 + GROW) },
67
+ ...neighbours.map((n) => {
68
+ const w = widthOf(n);
69
+ return { el: n, width: w, to: Math.max(w - share, 0) };
70
+ })
71
+ ]
72
+ };
73
+ for (const { el: item, to } of press.items) animate(item, { width: `${to}px` }, spring);
74
+ };
75
+
76
+ const end = () => {
77
+ if (!press) return;
78
+ const { items } = press;
79
+ press = null;
80
+ for (const { el, width } of items) {
81
+ animate(el, { width: `${width}px` }, spring).then(() => {
82
+ // Hand the width back to the layout, unless a new press picked this button up.
83
+ if (press?.items.some((i) => i.el === el)) return;
84
+ el.style.width = '';
85
+ rest.delete(el);
86
+ });
87
+ }
88
+ };
89
+
90
+ const onPointerDown = (e: PointerEvent) => {
91
+ if (e.button !== 0) return;
92
+ const el = itemOf(e.target);
93
+ if (el) start(el);
94
+ };
95
+ const onKeyDown = (e: KeyboardEvent) => {
96
+ if (e.repeat || (e.key !== ' ' && e.key !== 'Enter')) return;
97
+ const el = itemOf(e.target);
98
+ if (el) start(el);
99
+ };
100
+
101
+ group.addEventListener('pointerdown', onPointerDown);
102
+ group.addEventListener('keydown', onKeyDown);
103
+ group.addEventListener('keyup', end);
104
+ window.addEventListener('pointerup', end);
105
+ window.addEventListener('pointercancel', end);
106
+ return () => {
107
+ group.removeEventListener('pointerdown', onPointerDown);
108
+ group.removeEventListener('keydown', onKeyDown);
109
+ group.removeEventListener('keyup', end);
110
+ window.removeEventListener('pointerup', end);
111
+ window.removeEventListener('pointercancel', end);
112
+ };
113
+ };
26
114
  </script>
27
115
 
28
- <div class={cls.root({ class: clsx(className) })} data-size={size} data-orientation={orientation}>
116
+ <div
117
+ role="group"
118
+ aria-label={ariaLabel}
119
+ data-orientation={orientation}
120
+ class={cls.root({ class: clsx(className) })}
121
+ {@attach squeeze}
122
+ >
29
123
  {@render children()}
30
124
  </div>
@@ -1,14 +1,13 @@
1
1
  import type { ButtonGroupProps } from './types.js';
2
2
  /**
3
- * A standard button group — an invisible container that adds spacing between
4
- * buttons and lets them mix variants, colors, and widths freely. Pressing a
5
- * button also nudges the width of its immediate neighbors, per the M3 spec.
3
+ * A standard button group: an invisible container that spaces buttons and adds an interaction
4
+ * between neighbours. Pressing a button widens it by 15% while the buttons beside it narrow to
5
+ * make room, on the fast spatial spring; a toggle inside also swaps its shape as it's selected.
6
+ * Children keep their own style, so filled, tonal and outlined buttons and icon buttons mix freely.
6
7
  *
7
- * Buttons keep their own identity here; this is a plain layout wrapper around
8
- * `Button` / `ButtonIcon` / `Toggle` children, not a selection primitive. For a
9
- * single/multi-select segmented control, use `ConnectedButtonGroup` instead.
8
+ * For a set of related options where one or more is selected, use `ConnectedButtonGroup`.
10
9
  *
11
- * @see https://m3.material.io/components/button-groups/overview
10
+ * @see https://m3.material.io/components/button-groups/specs
12
11
  */
13
12
  declare const ButtonGroup: import("svelte").Component<ButtonGroupProps, {}, "">;
14
13
  type ButtonGroup = ReturnType<typeof ButtonGroup>;
@@ -10,11 +10,21 @@ export declare const buttonGroup: import("tailwind-variants").TVReturnType<{
10
10
  };
11
11
  };
12
12
  size: {
13
- xs: {};
14
- sm: {};
15
- md: {};
16
- lg: {};
17
- xl: {};
13
+ xs: {
14
+ root: string;
15
+ };
16
+ sm: {
17
+ root: string;
18
+ };
19
+ md: {
20
+ root: string;
21
+ };
22
+ lg: {
23
+ root: string;
24
+ };
25
+ xl: {
26
+ root: string;
27
+ };
18
28
  };
19
29
  }, {
20
30
  root: string;
@@ -28,11 +38,21 @@ export declare const buttonGroup: import("tailwind-variants").TVReturnType<{
28
38
  };
29
39
  };
30
40
  size: {
31
- xs: {};
32
- sm: {};
33
- md: {};
34
- lg: {};
35
- xl: {};
41
+ xs: {
42
+ root: string;
43
+ };
44
+ sm: {
45
+ root: string;
46
+ };
47
+ md: {
48
+ root: string;
49
+ };
50
+ lg: {
51
+ root: string;
52
+ };
53
+ xl: {
54
+ root: string;
55
+ };
36
56
  };
37
57
  }, {
38
58
  root: string;
@@ -46,11 +66,21 @@ export declare const buttonGroup: import("tailwind-variants").TVReturnType<{
46
66
  };
47
67
  };
48
68
  size: {
49
- xs: {};
50
- sm: {};
51
- md: {};
52
- lg: {};
53
- xl: {};
69
+ xs: {
70
+ root: string;
71
+ };
72
+ sm: {
73
+ root: string;
74
+ };
75
+ md: {
76
+ root: string;
77
+ };
78
+ lg: {
79
+ root: string;
80
+ };
81
+ xl: {
82
+ root: string;
83
+ };
54
84
  };
55
85
  }, {
56
86
  root: string;
@@ -1,19 +1,24 @@
1
1
  import { tv } from 'tailwind-variants';
2
+ /*
3
+ * Standard button group, per https://m3.material.io/components/button-groups/specs: an invisible
4
+ * container that spaces its buttons so each keeps a 48dp target (18/12/8/8/8dp for XS–XL).
5
+ * Pressing a button widens it by 15% and its neighbours give that width back (ButtonGroup.svelte).
6
+ */
2
7
  export const buttonGroup = tv({
3
8
  slots: {
4
- root: 'md-btn-group-standard'
9
+ root: 'inline-flex w-max flex-nowrap'
5
10
  },
6
11
  variants: {
7
12
  orientation: {
8
- horizontal: { root: 'flex-row' },
9
- vertical: { root: 'flex-col' }
13
+ horizontal: { root: 'flex-row items-center' },
14
+ vertical: { root: 'w-auto flex-col items-stretch' }
10
15
  },
11
16
  size: {
12
- xs: {},
13
- sm: {},
14
- md: {},
15
- lg: {},
16
- xl: {}
17
+ xs: { root: 'gap-[1.125rem]' },
18
+ sm: { root: 'gap-spacing-150' },
19
+ md: { root: 'gap-spacing-100' },
20
+ lg: { root: 'gap-spacing-100' },
21
+ xl: { root: 'gap-spacing-100' }
17
22
  }
18
23
  },
19
24
  defaultVariants: {
@@ -1,14 +1,20 @@
1
1
  import type { Snippet } from 'svelte';
2
- import type { ButtonGroupVariants } from './theme.js';
3
- export type ButtonGroupProps = ButtonGroupVariants & {
4
- /** Layout direction of the buttons. */
2
+ import type { ButtonSize } from '../theme.js';
3
+ export type ButtonGroupProps = {
4
+ /** @default 'horizontal' */
5
5
  orientation?: 'horizontal' | 'vertical';
6
6
  /**
7
- * Drives the gap between buttons and the press-squeeze amount applied to a
8
- * pressed button's immediate neighbors. Match the size of the buttons inside.
7
+ * The size of the buttons inside. Sets the space between them: 18, 12, 8, 8 and 8dp for XS to
8
+ * XL, so each keeps a 48dp target.
9
+ * @default 'sm'
10
+ */
11
+ size?: ButtonSize;
12
+ /**
13
+ * `Button`s, `Toggle`s and `ButtonIcon`s, mixed freely. Avoid `text` buttons and `standard`
14
+ * icon buttons, which have no container to change shape.
9
15
  */
10
- size?: ButtonGroupVariants['size'];
11
- /** The buttons in the group — `Button`, `ButtonIcon`, or `Toggle` components, mixed freely. */
12
16
  children: Snippet;
17
+ /** Accessible name, when the group needs one (it gets `role="group"`). */
18
+ 'aria-label'?: string;
13
19
  class?: string;
14
20
  };
@@ -0,0 +1,43 @@
1
+ import { Meta, Title, Subtitle, Canvas, Controls } from '@storybook/addon-docs/blocks';
2
+ import * as ConnectedStories from './ConnectedButtonGroup.stories.svelte';
3
+
4
+ <Meta of={ConnectedStories} />
5
+
6
+ <Title />
7
+
8
+ <Subtitle>Connected button groups show related options, one or several selected.</Subtitle>
9
+
10
+ [M3 spec](https://m3.material.io/components/button-groups/specs) ·
11
+ `import { ConnectedButtonGroup, ConnectedButtonGroupItem } from '@noxlovette/material';`
12
+
13
+ <Canvas of={ConnectedStories.Playground} />
14
+
15
+ <Controls of={ConnectedStories.Playground} />
16
+
17
+ ## When to use
18
+
19
+ For a small set of related options that change what the page shows, such as Walk, Bike, Drive.
20
+ It replaces M3's segmented button. For separate actions, use `ButtonGroup`.
21
+ `type="single"` keeps one item selected; `type="multiple"` allows several.
22
+
23
+ ## Colour and shape
24
+
25
+ Items are toggle buttons in the group's `variant`: filled (default), tonal, outlined or
26
+ elevated, with the same unselected and selected colours as `Toggle`. Selection shows through
27
+ colour and shape, not a check mark:
28
+
29
+ - Items sit 2dp apart. The outer corners are fully round, or the inner size with
30
+ `shape="square"`.
31
+ - Inner corners are 8dp (16dp at L, 20dp at XL), tighten while pressed, and round out fully on
32
+ the selected item. Neighbours never change.
33
+
34
+ <Canvas of={ConnectedStories.ColourStyles} />
35
+
36
+ <Canvas of={ConnectedStories.SizesAndShapes} />
37
+
38
+ ## Accessibility
39
+
40
+ Name the group with `aria-label`, and give icon-only items an `aria-label`. Arrow keys move
41
+ between items; Space and Enter select. XS and S items are at least 48dp wide.
42
+
43
+ <Canvas of={ConnectedStories.MultiSelectIcons} />
@@ -3,29 +3,22 @@
3
3
  import ConnectedButtonGroup from './ConnectedButtonGroup.svelte';
4
4
  import ConnectedButtonGroupItem from './ConnectedButtonGroupItem.svelte';
5
5
 
6
+ const sizes = ['xs', 'sm', 'md', 'lg', 'xl'] as const;
7
+ const variants = ['filled', 'tonal', 'outlined', 'elevated'] as const;
8
+
6
9
  const { Story } = defineMeta({
7
10
  title: 'Buttons/Connected Button Group',
8
11
  component: ConnectedButtonGroup,
9
12
  argTypes: {
10
- type: {
11
- control: 'select',
12
- options: ['single', 'multiple']
13
- },
14
- orientation: {
15
- control: 'select',
16
- options: ['horizontal', 'vertical']
17
- },
18
- shape: {
19
- control: 'select',
20
- options: ['round', 'square']
21
- },
22
- size: {
23
- control: 'select',
24
- options: ['xs', 'sm', 'md', 'lg', 'xl']
25
- }
13
+ type: { control: 'inline-radio', options: ['single', 'multiple'] },
14
+ variant: { control: 'select', options: variants },
15
+ orientation: { control: 'inline-radio', options: ['horizontal', 'vertical'] },
16
+ shape: { control: 'inline-radio', options: ['round', 'square'] },
17
+ size: { control: 'select', options: sizes }
26
18
  },
27
19
  args: {
28
20
  type: 'single',
21
+ variant: 'filled',
29
22
  orientation: 'horizontal',
30
23
  shape: 'round',
31
24
  size: 'sm'
@@ -33,86 +26,75 @@
33
26
  });
34
27
  </script>
35
28
 
29
+ <script lang="ts">
30
+ let mode = $state('bike');
31
+ let format = $state<string[]>(['bold']);
32
+ </script>
33
+
36
34
  <Story name="Playground">
37
35
  {#snippet template(args)}
38
36
  <ConnectedButtonGroup
39
- type={args.type}
40
- orientation={args.orientation}
41
- shape={args.shape}
42
- size={args.size}
43
- value="left"
37
+ {...args}
38
+ value={args.type === 'multiple' ? [] : 'walk'}
39
+ aria-label="Travel mode"
44
40
  >
41
+ <ConnectedButtonGroupItem value="walk" label="Walk" iconProps={{ name: 'directions_walk' }} />
42
+ <ConnectedButtonGroupItem value="bike" label="Bike" iconProps={{ name: 'directions_bike' }} />
45
43
  <ConnectedButtonGroupItem
46
- value="left"
47
- label="Left"
48
- iconProps={{ name: 'format_align_left' }}
49
- />
50
- <ConnectedButtonGroupItem
51
- value="center"
52
- label="Center"
53
- iconProps={{ name: 'format_align_center' }}
54
- />
55
- <ConnectedButtonGroupItem
56
- value="right"
57
- label="Right"
58
- iconProps={{ name: 'format_align_right' }}
44
+ value="drive"
45
+ label="Drive"
46
+ iconProps={{ name: 'directions_car' }}
59
47
  />
60
48
  </ConnectedButtonGroup>
61
49
  {/snippet}
62
50
  </Story>
63
51
 
64
- <Story name="Multi-select" asChild>
65
- <div class="p-6">
66
- <ConnectedButtonGroup type="multiple" value={['bold']}>
67
- <ConnectedButtonGroupItem value="bold" label="Bold" iconProps={{ name: 'format_bold' }} />
52
+ <Story name="Colour styles" asChild>
53
+ <div class="gap-spacing-200 p-spacing-300 flex max-w-md flex-col">
54
+ {#each variants as variant (variant)}
55
+ <ConnectedButtonGroup type="single" {variant} bind:value={mode} aria-label="Travel mode">
56
+ <ConnectedButtonGroupItem value="walk" label="Walk" />
57
+ <ConnectedButtonGroupItem value="bike" label="Bike" />
58
+ <ConnectedButtonGroupItem value="drive" label="Drive" />
59
+ </ConnectedButtonGroup>
60
+ {/each}
61
+ </div>
62
+ </Story>
63
+
64
+ <Story name="Multi-select icons" asChild>
65
+ <div class="gap-spacing-200 p-spacing-300 flex flex-col items-start">
66
+ <ConnectedButtonGroup type="multiple" bind:value={format} class="w-auto" aria-label="Format">
67
+ <ConnectedButtonGroupItem
68
+ value="bold"
69
+ aria-label="Bold"
70
+ iconProps={{ name: 'format_bold' }}
71
+ />
68
72
  <ConnectedButtonGroupItem
69
73
  value="italic"
70
- label="Italic"
74
+ aria-label="Italic"
71
75
  iconProps={{ name: 'format_italic' }}
72
76
  />
73
77
  <ConnectedButtonGroupItem
74
78
  value="underline"
75
- label="Underline"
79
+ aria-label="Underline"
76
80
  iconProps={{ name: 'format_underlined' }}
77
81
  />
78
82
  </ConnectedButtonGroup>
79
83
  </div>
80
84
  </Story>
81
85
 
82
- <Story name="Icon Only" asChild>
83
- <div class="p-6">
84
- <ConnectedButtonGroup type="single" value="list">
85
- <ConnectedButtonGroupItem value="list" iconProps={{ name: 'view_list' }} />
86
- <ConnectedButtonGroupItem value="grid" iconProps={{ name: 'grid_view' }} />
87
- </ConnectedButtonGroup>
88
- </div>
89
- </Story>
90
-
91
- <Story name="Square Shape" asChild>
92
- <div class="p-6">
93
- <ConnectedButtonGroup type="single" shape="square" value="list">
94
- <ConnectedButtonGroupItem value="list" iconProps={{ name: 'view_list' }} />
95
- <ConnectedButtonGroupItem value="grid" iconProps={{ name: 'grid_view' }} />
96
- </ConnectedButtonGroup>
97
- </div>
98
- </Story>
99
-
100
- <Story name="Sizes" asChild>
101
- <div class="flex flex-col gap-6 p-6">
102
- {#each ['xs', 'sm', 'md', 'lg', 'xl'] as const as size (size)}
103
- <ConnectedButtonGroup type="single" {size} value="list">
104
- <ConnectedButtonGroupItem value="list" iconProps={{ name: 'view_list' }} />
105
- <ConnectedButtonGroupItem value="grid" iconProps={{ name: 'grid_view' }} />
106
- </ConnectedButtonGroup>
86
+ <Story name="Sizes and shapes" asChild>
87
+ <div class="gap-spacing-200 p-spacing-300 flex max-w-xl flex-col">
88
+ {#each sizes as size (size)}
89
+ <div class="gap-spacing-200 flex">
90
+ {#each ['round', 'square'] as const as shape (shape)}
91
+ <ConnectedButtonGroup type="single" {size} {shape} value="b">
92
+ <ConnectedButtonGroupItem value="a" label="A" />
93
+ <ConnectedButtonGroupItem value="b" label="B" />
94
+ <ConnectedButtonGroupItem value="c" label="C" />
95
+ </ConnectedButtonGroup>
96
+ {/each}
97
+ </div>
107
98
  {/each}
108
99
  </div>
109
100
  </Story>
110
-
111
- <Story name="Vertical" asChild>
112
- <div class="p-6">
113
- <ConnectedButtonGroup type="single" orientation="vertical" value="list">
114
- <ConnectedButtonGroupItem value="list" label="List" iconProps={{ name: 'view_list' }} />
115
- <ConnectedButtonGroupItem value="grid" label="Grid" iconProps={{ name: 'grid_view' }} />
116
- </ConnectedButtonGroup>
117
- </div>
118
- </Story>
@@ -1,19 +1,4 @@
1
1
  import ConnectedButtonGroup from './ConnectedButtonGroup.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 ConnectedButtonGroup: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
16
- [evt: string]: CustomEvent<any>;
17
- }, {}, {}, string>;
18
- type ConnectedButtonGroup = InstanceType<typeof ConnectedButtonGroup>;
2
+ declare const ConnectedButtonGroup: import("svelte").Component<Record<string, never>, {}, "">;
3
+ type ConnectedButtonGroup = ReturnType<typeof ConnectedButtonGroup>;
19
4
  export default ConnectedButtonGroup;
@@ -1,9 +1,9 @@
1
1
  <!--
2
2
  @component
3
- A connected button group — the M3 replacement for the deprecated segmented
4
- button. Items sit in a shared pill (or square) shape with a small gap between
5
- them; selecting or pressing an item changes only that item's own shape and
6
- color, never its neighbors'.
3
+ A connected button group, M3's replacement for the segmented button: toggle buttons 2dp apart
4
+ in one shared shape, for related options such as Walk, Bike, Drive. Selecting an item rounds it
5
+ out and gives it the selected colours; pressing tightens its inner corners. Neighbours never
6
+ change.
7
7
 
8
8
  @see https://m3.material.io/components/button-groups/overview
9
9
  -->
@@ -21,9 +21,11 @@ color, never its neighbors'.
21
21
  disabled = false,
22
22
  loop = true,
23
23
  orientation = 'horizontal',
24
+ variant = 'filled',
24
25
  shape = 'round',
25
26
  size = 'sm',
26
27
  children,
28
+ 'aria-label': ariaLabel,
27
29
  class: className
28
30
  }: ConnectedButtonGroupProps = $props();
29
31
 
@@ -35,6 +37,15 @@ color, never its neighbors'.
35
37
  },
36
38
  get shape() {
37
39
  return shape;
40
+ },
41
+ get variant() {
42
+ return variant;
43
+ },
44
+ get type() {
45
+ return type;
46
+ },
47
+ get value() {
48
+ return value;
38
49
  }
39
50
  });
40
51
  </script>
@@ -46,6 +57,7 @@ color, never its neighbors'.
46
57
  {disabled}
47
58
  {loop}
48
59
  {orientation}
60
+ aria-label={ariaLabel}
49
61
  class={cls.root({ class: clsx(className) })}
50
62
  >
51
63
  {@render children?.()}
@@ -1,9 +1,9 @@
1
1
  import type { ConnectedButtonGroupProps } from './types.js';
2
2
  /**
3
- * A connected button group — the M3 replacement for the deprecated segmented
4
- * button. Items sit in a shared pill (or square) shape with a small gap between
5
- * them; selecting or pressing an item changes only that item's own shape and
6
- * color, never its neighbors'.
3
+ * A connected button group, M3's replacement for the segmented button: toggle buttons 2dp apart
4
+ * in one shared shape, for related options such as Walk, Bike, Drive. Selecting an item rounds it
5
+ * out and gives it the selected colours; pressing tightens its inner corners. Neighbours never
6
+ * change.
7
7
  *
8
8
  * @see https://m3.material.io/components/button-groups/overview
9
9
  */