@noxlovette/material 0.5.1 → 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 (296) 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 +24 -4
  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 +45 -15
  47. package/dist/components/buttons/button-group/theme.js +13 -8
  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 +33 -18
  57. package/dist/components/buttons/connected/theme.js +32 -9
  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 +65 -48
  64. package/dist/components/buttons/split-button/SplitButton.svelte.d.ts +6 -11
  65. package/dist/components/buttons/split-button/theme.d.ts +110 -17
  66. package/dist/components/buttons/split-button/theme.js +74 -16
  67. package/dist/components/buttons/split-button/types.d.ts +24 -18
  68. package/dist/components/buttons/theme.d.ts +422 -230
  69. package/dist/components/buttons/theme.js +286 -380
  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 +5 -5
  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.js +3 -3
  77. package/dist/components/containers/action-rail/ActionRail.stories.svelte +1 -0
  78. package/dist/components/containers/action-rail/theme.js +1 -1
  79. package/dist/components/containers/bottom-sheet/BottomSheet.mdx +64 -0
  80. package/dist/components/containers/bottom-sheet/BottomSheet.stories.svelte +108 -12
  81. package/dist/components/containers/bottom-sheet/BottomSheet.svelte +179 -109
  82. package/dist/components/containers/bottom-sheet/BottomSheet.svelte.d.ts +10 -5
  83. package/dist/components/containers/bottom-sheet/index.d.ts +1 -0
  84. package/dist/components/containers/bottom-sheet/index.js +1 -0
  85. package/dist/components/containers/bottom-sheet/theme.d.ts +45 -0
  86. package/dist/components/containers/bottom-sheet/theme.js +42 -0
  87. package/dist/components/containers/bottom-sheet/types.d.ts +37 -5
  88. package/dist/components/containers/carousel/Carousel.mdx +80 -0
  89. package/dist/components/containers/carousel/Carousel.stories.svelte +131 -0
  90. package/dist/components/containers/carousel/Carousel.stories.svelte.d.ts +4 -0
  91. package/dist/components/containers/carousel/Carousel.svelte +194 -0
  92. package/dist/components/containers/carousel/Carousel.svelte.d.ts +25 -0
  93. package/dist/components/containers/carousel/index.d.ts +3 -0
  94. package/dist/components/containers/{stack → carousel}/index.js +1 -2
  95. package/dist/components/containers/carousel/keylines.d.ts +32 -0
  96. package/dist/components/containers/carousel/keylines.js +88 -0
  97. package/dist/components/containers/carousel/theme.d.ts +162 -0
  98. package/dist/components/containers/carousel/theme.js +67 -0
  99. package/dist/components/containers/carousel/types.d.ts +52 -0
  100. package/dist/components/containers/context-menu/ContextMenu.stories.svelte +1 -0
  101. package/dist/components/containers/context-menu/ContextMenu.svelte +2 -2
  102. package/dist/components/containers/context-menu/ContextMenu.svelte.d.ts +1 -1
  103. package/dist/components/containers/context-menu/theme.js +10 -10
  104. package/dist/components/containers/dialogue/Dialogue.mdx +65 -0
  105. package/dist/components/containers/dialogue/Dialogue.stories.svelte +4 -4
  106. package/dist/components/containers/dialogue/Dialogue.svelte +1 -1
  107. package/dist/components/containers/dialogue/theme.js +3 -3
  108. package/dist/components/containers/divider/Divider.stories.svelte +2 -1
  109. package/dist/components/containers/divider/Divider.svelte +2 -2
  110. package/dist/components/containers/divider/theme.d.ts +6 -6
  111. package/dist/components/containers/divider/theme.js +2 -2
  112. package/dist/components/containers/index.d.ts +1 -1
  113. package/dist/components/containers/index.js +1 -1
  114. package/dist/components/containers/link-preview/theme.js +2 -2
  115. package/dist/components/containers/list/List.svelte +18 -0
  116. package/dist/components/containers/list/List.svelte.d.ts +4 -0
  117. package/dist/components/containers/list/ListItem.mdx +100 -0
  118. package/dist/components/containers/list/ListItem.stories.svelte +177 -42
  119. package/dist/components/containers/list/ListItem.stories.svelte.d.ts +2 -17
  120. package/dist/components/containers/list/ListItem.svelte +92 -37
  121. package/dist/components/containers/list/ListItem.svelte.d.ts +1 -1
  122. package/dist/components/containers/list/context.d.ts +6 -0
  123. package/dist/components/containers/list/context.js +4 -0
  124. package/dist/components/containers/list/index.d.ts +1 -0
  125. package/dist/components/containers/list/index.js +1 -0
  126. package/dist/components/containers/list/theme.d.ts +487 -12
  127. package/dist/components/containers/list/theme.js +140 -13
  128. package/dist/components/containers/list/types.d.ts +39 -5
  129. package/dist/components/containers/menu/Menu.mdx +52 -0
  130. package/dist/components/containers/menu/Menu.stories.svelte +14 -14
  131. package/dist/components/containers/menu/Menu.svelte +1 -1
  132. package/dist/components/containers/menu/MenuGroup.svelte +1 -1
  133. package/dist/components/containers/menu/MenuGroup.svelte.d.ts +1 -1
  134. package/dist/components/containers/menu/menu-item/MenuItem.svelte +1 -1
  135. package/dist/components/containers/menu/theme.js +8 -8
  136. package/dist/components/containers/pane/DraggablePane.stories.svelte +4 -3
  137. package/dist/components/containers/pane/DraggablePane.svelte +2 -2
  138. package/dist/components/containers/pane/Pane.stories.svelte +11 -10
  139. package/dist/components/containers/pane/Pane.svelte +24 -2
  140. package/dist/components/containers/pane/PaneGrid.stories.svelte +19 -18
  141. package/dist/components/containers/pane/PaneGrid.svelte +3 -2
  142. package/dist/components/containers/pane/PaneGrid.svelte.d.ts +2 -1
  143. package/dist/components/containers/pane/PaneHandle.svelte +48 -2
  144. package/dist/components/containers/pane/PaneHandle.svelte.d.ts +4 -0
  145. package/dist/components/containers/pane/resizeStore.svelte.d.ts +10 -1
  146. package/dist/components/containers/pane/resizeStore.svelte.js +13 -2
  147. package/dist/components/containers/pane/theme.js +129 -45
  148. package/dist/components/containers/pane/types.d.ts +3 -2
  149. package/dist/components/containers/popover/Popover.mdx +45 -0
  150. package/dist/components/containers/popover/Popover.stories.svelte +1 -1
  151. package/dist/components/containers/popover/Popover.svelte +1 -1
  152. package/dist/components/containers/popover/theme.js +3 -3
  153. package/dist/components/containers/scroll-area/theme.js +2 -2
  154. package/dist/components/containers/side-sheet/SideSheet.mdx +71 -0
  155. package/dist/components/containers/side-sheet/SideSheet.stories.svelte +113 -17
  156. package/dist/components/containers/side-sheet/SideSheet.svelte +113 -66
  157. package/dist/components/containers/side-sheet/SideSheet.svelte.d.ts +8 -7
  158. package/dist/components/containers/side-sheet/index.d.ts +1 -0
  159. package/dist/components/containers/side-sheet/index.js +1 -0
  160. package/dist/components/containers/side-sheet/theme.d.ts +108 -0
  161. package/dist/components/containers/side-sheet/theme.js +72 -0
  162. package/dist/components/containers/side-sheet/types.d.ts +35 -7
  163. package/dist/components/date/DateField.stories.svelte +4 -3
  164. package/dist/components/date/DateField.svelte +11 -2
  165. package/dist/components/date/DateRangeField.stories.svelte +4 -3
  166. package/dist/components/date/DateRangeField.svelte +12 -3
  167. package/dist/components/date/theme.d.ts +7 -7
  168. package/dist/components/date/theme.js +31 -31
  169. package/dist/components/forms/checkbox/Checkbox.mdx +35 -0
  170. package/dist/components/forms/checkbox/Checkbox.stories.svelte +2 -2
  171. package/dist/components/forms/checkbox/Checkbox.svelte +1 -1
  172. package/dist/components/forms/checkbox/theme.js +3 -3
  173. package/dist/components/forms/command/Command.stories.svelte +1 -0
  174. package/dist/components/forms/command/theme.js +9 -9
  175. package/dist/components/forms/pin/theme.js +4 -4
  176. package/dist/components/forms/radio-group/RadioGroup.svelte +1 -1
  177. package/dist/components/forms/radio-group/theme.js +5 -5
  178. package/dist/components/forms/search/Search.mdx +38 -0
  179. package/dist/components/forms/search/Search.stories.svelte +19 -9
  180. package/dist/components/forms/search/Search.svelte +40 -17
  181. package/dist/components/forms/search/Search.svelte.d.ts +2 -2
  182. package/dist/components/forms/search/theme.d.ts +45 -12
  183. package/dist/components/forms/search/theme.js +13 -8
  184. package/dist/components/forms/search/types.d.ts +18 -12
  185. package/dist/components/forms/select/Select.mdx +53 -0
  186. package/dist/components/forms/select/Select.stories.svelte +4 -4
  187. package/dist/components/forms/select/Select.svelte +10 -2
  188. package/dist/components/forms/select/SelectItem.svelte +1 -1
  189. package/dist/components/forms/select/theme.js +12 -12
  190. package/dist/components/forms/slider/Slider.mdx +85 -0
  191. package/dist/components/forms/slider/Slider.stories.svelte +7 -7
  192. package/dist/components/forms/slider/Slider.svelte +46 -28
  193. package/dist/components/forms/slider/theme.d.ts +14 -0
  194. package/dist/components/forms/slider/theme.js +28 -17
  195. package/dist/components/forms/slider/types.d.ts +3 -2
  196. package/dist/components/forms/switch/Switch.mdx +38 -0
  197. package/dist/components/forms/switch/Switch.stories.svelte +3 -3
  198. package/dist/components/forms/switch/theme.js +6 -6
  199. package/dist/components/forms/textfield/Textfield.mdx +53 -0
  200. package/dist/components/forms/textfield/Textfield.stories.svelte +6 -6
  201. package/dist/components/forms/textfield/Textfield.svelte +1 -0
  202. package/dist/components/forms/textfield/theme.js +19 -19
  203. package/dist/components/forms/toggle-group/theme.js +2 -2
  204. package/dist/components/forms/tooltip/Tooltip.stories.svelte +1 -0
  205. package/dist/components/forms/tooltip/theme.js +3 -3
  206. package/dist/components/misc/Avatar.stories.svelte +5 -4
  207. package/dist/components/misc/ThemeSettings.svelte +9 -9
  208. package/dist/components/misc/ThemeSwitcher.svelte +3 -3
  209. package/dist/components/misc/theme.js +4 -4
  210. package/dist/components/nav/appbar/AppBar.mdx +71 -0
  211. package/dist/components/nav/appbar/AppBar.stories.svelte +93 -3
  212. package/dist/components/nav/appbar/AppBar.stories.svelte.d.ts +2 -17
  213. package/dist/components/nav/appbar/AppBar.svelte +90 -37
  214. package/dist/components/nav/appbar/AppBar.svelte.d.ts +9 -2
  215. package/dist/components/nav/appbar/theme.d.ts +85 -18
  216. package/dist/components/nav/appbar/theme.js +159 -23
  217. package/dist/components/nav/appbar/types.d.ts +46 -9
  218. package/dist/components/nav/breadcrumb/Breadcrumb.stories.svelte +1 -0
  219. package/dist/components/nav/breadcrumb/theme.js +5 -5
  220. package/dist/components/nav/navbar/Navbar.mdx +38 -0
  221. package/dist/components/nav/navbar/Navbar.svelte +1 -1
  222. package/dist/components/nav/navbar/NavbarItem.svelte +12 -9
  223. package/dist/components/nav/navbar/theme.js +6 -6
  224. package/dist/components/nav/rail/Rail.mdx +73 -0
  225. package/dist/components/nav/rail/Rail.stories.svelte +3 -7
  226. package/dist/components/nav/rail/Rail.svelte +1 -0
  227. package/dist/components/nav/rail/RailItem.svelte +12 -9
  228. package/dist/components/nav/rail/theme.js +13 -13
  229. package/dist/components/nav/rail/types.d.ts +4 -1
  230. package/dist/components/nav/tabs/Tabs.mdx +62 -0
  231. package/dist/components/nav/tabs/Tabs.stories.svelte +1 -1
  232. package/dist/components/nav/tabs/theme.js +4 -4
  233. package/dist/components/pill/Pill.mdx +32 -0
  234. package/dist/components/pill/Pill.stories.svelte +1 -1
  235. package/dist/components/pill/theme.d.ts +1 -1
  236. package/dist/components/pill/theme.js +1 -1
  237. package/dist/components/progress/LinearProgress.svelte +1 -1
  238. package/dist/components/progress/Progress.mdx +50 -0
  239. package/dist/components/progress/Progress.stories.svelte +6 -6
  240. package/dist/components/snackbar/Snackbar.mdx +52 -0
  241. package/dist/components/snackbar/Snackbar.stories.svelte +5 -5
  242. package/dist/components/snackbar/Snackbar.svelte +1 -1
  243. package/dist/components/snackbar/theme.d.ts +3 -3
  244. package/dist/components/snackbar/theme.js +4 -4
  245. package/dist/components/table/Table.stories.svelte +1 -0
  246. package/dist/components/table/TableHeader.svelte +1 -1
  247. package/dist/components/table/theme.js +3 -3
  248. package/dist/components/time/TimeField.stories.svelte +3 -2
  249. package/dist/components/time/TimeField.svelte +1 -1
  250. package/dist/components/time/TimepickerInput.svelte +1 -1
  251. package/dist/components/time/theme.js +5 -5
  252. package/dist/components/toolbar/Toolbar.stories.svelte +1 -0
  253. package/dist/components/toolbar/Toolbar.svelte +1 -1
  254. package/dist/components/toolbar/Toolbar.svelte.d.ts +1 -1
  255. package/dist/components/toolbar/theme.js +14 -14
  256. package/dist/components/typography/Typography.mdx +67 -0
  257. package/dist/components/typography/body/Body.stories.svelte +2 -1
  258. package/dist/components/typography/body/theme.d.ts +15 -12
  259. package/dist/components/typography/body/theme.js +19 -4
  260. package/dist/components/typography/body/types.d.ts +2 -1
  261. package/dist/components/typography/display/Display.stories.svelte +2 -1
  262. package/dist/components/typography/display/theme.d.ts +15 -12
  263. package/dist/components/typography/display/theme.js +19 -4
  264. package/dist/components/typography/display/types.d.ts +2 -1
  265. package/dist/components/typography/headline/Headline.stories.svelte +2 -1
  266. package/dist/components/typography/headline/theme.d.ts +15 -12
  267. package/dist/components/typography/headline/theme.js +19 -4
  268. package/dist/components/typography/headline/types.d.ts +2 -1
  269. package/dist/components/typography/kbd/Kbd.stories.svelte +2 -1
  270. package/dist/components/typography/kbd/theme.d.ts +4 -4
  271. package/dist/components/typography/kbd/theme.js +2 -2
  272. package/dist/components/typography/label/Label.stories.svelte +2 -1
  273. package/dist/components/typography/label/theme.d.ts +15 -12
  274. package/dist/components/typography/label/theme.js +19 -4
  275. package/dist/components/typography/label/types.d.ts +2 -1
  276. package/dist/components/typography/title/Title.stories.svelte +2 -1
  277. package/dist/components/typography/title/theme.d.ts +15 -12
  278. package/dist/components/typography/title/theme.js +19 -4
  279. package/dist/components/typography/title/types.d.ts +2 -1
  280. package/dist/index.css +1 -0
  281. package/dist/styles/component.css +78 -175
  282. package/dist/styles/elevation.css +9 -0
  283. package/dist/styles/spacing.css +29 -0
  284. package/dist/styles/typescale.css +635 -20
  285. package/dist/utils/Layer.svelte +1 -1
  286. package/dist/utils/icon/Icon.svelte +8 -6
  287. package/package.json +19 -15
  288. package/dist/components/containers/stack/HStack.svelte +0 -23
  289. package/dist/components/containers/stack/HStack.svelte.d.ts +0 -5
  290. package/dist/components/containers/stack/VStack.svelte +0 -23
  291. package/dist/components/containers/stack/VStack.svelte.d.ts +0 -5
  292. package/dist/components/containers/stack/index.d.ts +0 -4
  293. package/dist/components/containers/stack/theme.d.ts +0 -66
  294. package/dist/components/containers/stack/theme.js +0 -26
  295. package/dist/components/containers/stack/types.d.ts +0 -12
  296. /package/dist/components/containers/{stack → carousel}/types.js +0 -0
@@ -203,27 +203,30 @@
203
203
  <button
204
204
  type="button"
205
205
  data-demo="ct-card"
206
- class="bg-md-sys-color-primary-container text-md-sys-color-on-primary-container absolute top-4 left-4 flex items-center gap-3 rounded-lg p-3"
206
+ class="bg-md-sys-color-primary-container text-md-sys-color-on-primary-container top-spacing-200 left-spacing-200 gap-spacing-150 p-spacing-150 absolute flex items-center rounded-lg"
207
207
  onclick={runContainer}
208
208
  >
209
- {@render shape(pathFourSidedCookie, 'size-10 text-md-sys-color-primary')}
209
+ {@render shape(pathFourSidedCookie, 'size-spacing-500 text-md-sys-color-primary')}
210
210
  <span class="md-sys-typescale-label-large">Open</span>
211
211
  </button>
212
212
  {:else}
213
213
  <div
214
214
  data-demo="ct-detail"
215
- class="bg-md-sys-color-surface-container-highest text-md-sys-color-on-surface absolute inset-3 flex flex-col items-center justify-center gap-3 rounded-xl"
215
+ class="bg-md-sys-color-surface-container-highest text-md-sys-color-on-surface inset-spacing-150 gap-spacing-150 absolute flex flex-col items-center justify-center rounded-xl"
216
216
  >
217
217
  {@render shape(pathFourSidedCookie, 'size-24 text-md-sys-color-primary')}
218
218
  <span class="md-sys-typescale-title-medium">Detail</span>
219
219
  </div>
220
220
  {/if}
221
221
  {:else if pattern.id === 'axis'}
222
- <div data-demo="axis" class="absolute inset-0 flex flex-col items-center justify-center gap-3">
222
+ <div
223
+ data-demo="axis"
224
+ class="inset-spacing-0 gap-spacing-150 absolute flex flex-col items-center justify-center"
225
+ >
223
226
  {@render shape(
224
227
  pathPentagon,
225
228
  [
226
- 'size-16 text-md-sys-color-primary',
229
+ 'size-spacing-800 text-md-sys-color-primary',
227
230
  'size-20 text-md-sys-color-secondary',
228
231
  'size-24 text-md-sys-color-tertiary'
229
232
  ][level]
@@ -233,7 +236,9 @@
233
236
  </span>
234
237
  </div>
235
238
  {:else if pattern.id === 'lateral'}
236
- <div class="absolute inset-x-0 top-0 flex justify-center gap-6 pt-3">
239
+ <div
240
+ class="inset-x-spacing-0 top-spacing-0 gap-spacing-300 pt-spacing-150 absolute flex justify-center"
241
+ >
237
242
  {#each tabs as name, i (name)}
238
243
  <span
239
244
  class={[
@@ -245,7 +250,7 @@
245
250
  </div>
246
251
  <div
247
252
  data-demo="lateral"
248
- class="bg-md-sys-color-surface-container absolute inset-x-3 top-11 bottom-3 flex items-center justify-center rounded-lg"
253
+ class="bg-md-sys-color-surface-container inset-x-spacing-150 bottom-spacing-150 absolute top-11 flex items-center justify-center rounded-lg"
249
254
  >
250
255
  {@render shape(pathFourLeafClover, 'size-24 text-md-sys-color-primary')}
251
256
  <span
@@ -257,7 +262,7 @@
257
262
  <div
258
263
  data-demo="top"
259
264
  class={[
260
- 'absolute inset-3 flex flex-col items-center justify-center gap-3 rounded-lg',
265
+ 'inset-spacing-150 gap-spacing-150 absolute flex flex-col items-center justify-center rounded-lg',
261
266
  destinations[destination].color
262
267
  ]}
263
268
  >
@@ -267,23 +272,25 @@
267
272
  {:else if pattern.id === 'enter-exit'}
268
273
  {#if pop.mounted}
269
274
  <div
270
- class="bg-md-sys-color-surface-container-high shadow-elevation-3 absolute inset-x-8 inset-y-6 flex items-center justify-center rounded-xl"
275
+ class="bg-md-sys-color-surface-container-high shadow-elevation-3 inset-x-spacing-400 inset-y-spacing-300 absolute flex items-center justify-center rounded-xl"
271
276
  {@attach pop.attach(enterExit.scale)}
272
277
  >
273
278
  {@render shape(pathSoftBurst, 'size-24 text-md-sys-color-tertiary')}
274
279
  </div>
275
280
  {/if}
276
281
  {:else if pattern.id === 'skeleton'}
277
- <div class="absolute inset-3 grid [&>*]:[grid-area:1/1]">
282
+ <div class="inset-spacing-150 absolute grid [&>*]:[grid-area:1/1]">
278
283
  {#if !loaded}
279
- <div class="flex flex-col items-center justify-center gap-3" {@attach skeleton}>
284
+ <div class="gap-spacing-150 flex flex-col items-center justify-center" {@attach skeleton}>
280
285
  <div class="bg-md-sys-color-surface-container-highest size-24 rounded-full"></div>
281
- <div class="bg-md-sys-color-surface-container-highest h-4 w-28 rounded-full"></div>
286
+ <div
287
+ class="bg-md-sys-color-surface-container-highest h-spacing-200 w-28 rounded-full"
288
+ ></div>
282
289
  </div>
283
290
  {/if}
284
291
  {#if content.mounted}
285
292
  <div
286
- class="flex flex-col items-center justify-center gap-3"
293
+ class="gap-spacing-150 flex flex-col items-center justify-center"
287
294
  {@attach content.attach(enterExit.fade)}
288
295
  >
289
296
  {@render shape(pathGem, 'size-24 text-md-sys-color-secondary')}
@@ -296,10 +303,10 @@
296
303
 
297
304
  {#snippet demo(pattern: Pattern, tall = false)}
298
305
  <section
299
- class="bg-md-sys-color-surface-container-low text-md-sys-color-on-surface flex flex-col gap-3 rounded-xl p-4"
306
+ class="bg-md-sys-color-surface-container-low text-md-sys-color-on-surface gap-spacing-150 p-spacing-200 flex flex-col rounded-xl"
300
307
  >
301
- <header class="flex items-start justify-between gap-3">
302
- <div class="flex flex-col gap-1">
308
+ <header class="gap-spacing-150 flex items-start justify-between">
309
+ <div class="gap-spacing-50 flex flex-col">
303
310
  <Title size="medium">{pattern.title}</Title>
304
311
  <Body size="small" class="text-md-sys-color-on-surface-variant">{pattern.summary}</Body>
305
312
  </div>
@@ -320,7 +327,7 @@
320
327
  {/snippet}
321
328
 
322
329
  {#snippet toolbar()}
323
- <div class="flex items-center justify-between gap-4">
330
+ <div class="gap-spacing-200 flex items-center justify-between">
324
331
  <Title size="large">M3 transition patterns</Title>
325
332
  <Button variant={looping ? 'filled' : 'outlined'} onclick={() => (looping = !looping)}>
326
333
  {looping ? 'Pause loop' : 'Loop'}
@@ -329,9 +336,12 @@
329
336
  {/snippet}
330
337
 
331
338
  <Story name="Overview" asChild>
332
- <div class="bg-md-sys-color-surface flex min-h-dvh flex-col gap-6 p-6" {@attach roundRobin}>
339
+ <div
340
+ class="bg-md-sys-color-surface gap-spacing-300 p-spacing-300 flex min-h-dvh flex-col"
341
+ {@attach roundRobin}
342
+ >
333
343
  {@render toolbar()}
334
- <div class="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
344
+ <div class="gap-spacing-200 grid md:grid-cols-2 xl:grid-cols-3">
335
345
  {#each all as pattern (pattern.id)}
336
346
  {@render demo(pattern)}
337
347
  {/each}
@@ -341,7 +351,7 @@
341
351
 
342
352
  {#snippet single(pattern: Pattern)}
343
353
  <div
344
- class="bg-md-sys-color-surface flex min-h-dvh flex-col gap-6 p-6"
354
+ class="bg-md-sys-color-surface gap-spacing-300 p-spacing-300 flex min-h-dvh flex-col"
345
355
  {@attach loop(pattern.run)}
346
356
  >
347
357
  {@render toolbar()}
@@ -23,8 +23,10 @@ export interface ContainerTransformOptions {
23
23
  * the DOM at the same time — `update` swaps one for the other. Browsers without the API just run
24
24
  * `update`. A second call while one is running is queued, not interrupted.
25
25
  *
26
- * Not built into any component: `Search` is a plain field with no search view to expand into, and
27
- * card → detail is the app's own navigation.
26
+ * Not built into any component. FAB → sheet is the FAB's own morph (a clip-path on Motion, in
27
+ * `FAB.svelte`), because M3 keeps that container opaque and changes its colour, which a
28
+ * snapshot cross-fade can't. Card → detail is the app's own navigation, and `Search` is a plain
29
+ * field with no search view to expand into.
28
30
  *
29
31
  * ```ts
30
32
  * containerTransform(
@@ -15,8 +15,10 @@ import { springTokens, springTransition } from './spring.js';
15
15
  * the DOM at the same time — `update` swaps one for the other. Browsers without the API just run
16
16
  * `update`. A second call while one is running is queued, not interrupted.
17
17
  *
18
- * Not built into any component: `Search` is a plain field with no search view to expand into, and
19
- * card → detail is the app's own navigation.
18
+ * Not built into any component. FAB → sheet is the FAB's own morph (a clip-path on Motion, in
19
+ * `FAB.svelte`), because M3 keeps that container opaque and changes its colour, which a
20
+ * snapshot cross-fade can't. Card → detail is the app's own navigation, and `Search` is a plain
21
+ * field with no search view to expand into.
20
22
  *
21
23
  * ```ts
22
24
  * containerTransform(
@@ -53,9 +53,12 @@ export const enterExit = {
53
53
  enter: enterWith(springTokens.spatial),
54
54
  exit
55
55
  },
56
- /** Side sheet anchored to the inline-end edge. Slides only: no opacity (M3: don't fade sheets). */
56
+ /**
57
+ * Side sheet anchored to the inline-end edge. Slides only: no opacity (M3: don't fade sheets).
58
+ * The extra 1rem clears a detached sheet's 16dp margin, so no sliver is left at the edge.
59
+ */
57
60
  sideSheet: {
58
- hidden: { transform: 'translateX(100%)' },
61
+ hidden: { transform: 'translateX(calc(100% + 1rem))' },
59
62
  shown: { transform: 'translateX(0%)' },
60
63
  enter: springTransition(springTokens.spatial),
61
64
  exit: springTransition(springTokens.effects)
@@ -10,7 +10,7 @@ import type { Attachment } from 'svelte/attachments';
10
10
  * Not built into any component — none of them load their own data. Apps apply it to placeholders.
11
11
  *
12
12
  * ```svelte
13
- * <div class="bg-md-sys-color-surface-container-highest h-4 rounded-sm" {@attach skeleton}></div>
13
+ * <div class="bg-md-sys-color-surface-container-highest h-spacing-200 rounded-sm" {@attach skeleton}></div>
14
14
  * ```
15
15
  */
16
16
  export declare const skeleton: Attachment<HTMLElement>;
@@ -14,7 +14,7 @@ const PULSE_HOLD_S = 0.5;
14
14
  * Not built into any component — none of them load their own data. Apps apply it to placeholders.
15
15
  *
16
16
  * ```svelte
17
- * <div class="bg-md-sys-color-surface-container-highest h-4 rounded-sm" {@attach skeleton}></div>
17
+ * <div class="bg-md-sys-color-surface-container-highest h-spacing-200 rounded-sm" {@attach skeleton}></div>
18
18
  * ```
19
19
  */
20
20
  export const skeleton = (node) => {
@@ -0,0 +1,70 @@
1
+ import { Meta, Title, Subtitle, Canvas, Controls } from '@storybook/addon-docs/blocks';
2
+ import * as BadgeStories from './Badge.stories.svelte';
3
+
4
+ <Meta of={BadgeStories} />
5
+
6
+ <Title />
7
+
8
+ <Subtitle>
9
+ Badges show notifications, counts, or status information on navigation items and icons.
10
+ </Subtitle>
11
+
12
+ [M3 spec](https://m3.material.io/components/badges/specs) ·
13
+ `import { Badge } from '@noxlovette/material';`
14
+
15
+ <Canvas of={BadgeStories.Playground} />
16
+
17
+ <Controls of={BadgeStories.Playground} />
18
+
19
+ ## Choosing a size
20
+
21
+ | Size | Looks like | Use for |
22
+ | ---- | --------------------------------- | ------------------------------------------- |
23
+ | `sm` | 6dp dot, no text | A general notification ("something is new") |
24
+ | `lg` | 16dp-tall pill with a label-small | A specific count |
25
+
26
+ A large badge holds at most four characters, "+" included, so counts above `max` (999 by
27
+ default) show as `999+` in a 16×34dp container. A zero or missing `number` hides a large badge.
28
+
29
+ <Canvas of={BadgeStories.DotSmall} />
30
+
31
+ <Canvas of={BadgeStories.MaxCharacterCount} />
32
+
33
+ ## Placement
34
+
35
+ `NavbarItem` and `RailItem` take a `badge` prop (a count, or `-1` for the dot) and place it for
36
+ you. Use `Badge` directly only on icons those components don't cover, such as a custom icon
37
+ button.
38
+
39
+ By default the badge is absolutely positioned, with offsets measured from the **icon's**
40
+ top-trailing corner:
41
+
42
+ - **Small:** its bottom-leading corner sits 6dp down and 6dp in, so the dot fills the icon's
43
+ top-trailing corner.
44
+ - **Large:** its bottom-leading corner sits 14dp down and 12dp in. The badge grows away from the
45
+ icon as the count gets wider.
46
+
47
+ So wrap the icon itself in a `relative inline-flex` element. Don't anchor the badge to a larger
48
+ indicator or button container, or the offsets will be measured from the wrong corner.
49
+
50
+ ```svelte
51
+ <span class="relative inline-flex">
52
+ <Icon name="mail" />
53
+ <Badge number={5} />
54
+ </span>
55
+ ```
56
+
57
+ In normal flow, such as a list item's trailing slot, pass `standalone`. `ListItem`'s `badge`
58
+ prop already does this.
59
+
60
+ <Canvas of={BadgeStories.Standalone} />
61
+
62
+ ## Accessibility
63
+
64
+ - **Not announced on its own.** `Badge` renders a plain `<span>` with no role or live region.
65
+ Put the count in the anchor's accessible name (e.g. `aria-label="Inbox, 5 new"`), or use an
66
+ `aria-live` region if changes must be announced.
67
+ - **Contrast.** The badge uses the error/on-error pair in every theme, so its label keeps
68
+ contrast against its own container.
69
+ - **Clipping.** An anchored badge overhangs the icon by 2dp at the top and past the trailing edge
70
+ as it widens. Make sure no ancestor with `overflow: hidden` clips it.
@@ -1,9 +1,9 @@
1
1
  <!--
2
- Badge renders `absolute` (top/right offsets baked into its `size` variant), so it always needs a
3
- `position: relative` ancestor to anchor against — it's an overlay primitive, not a
4
- standalone/inline element. NavbarItem/RailItem already wire this up internally via their
5
- `badge` prop; the stories below reproduce that same relative-wrapper pattern by hand so Badge
6
- can be previewed in isolation.
2
+ Badge renders `absolute` by default, offset from its positioned ancestor's top-trailing corner
3
+ per the M3 spec, so that ancestor must hug the 24dp icon (not a larger indicator container).
4
+ NavbarItem/RailItem already wire this up internally via their `badge` prop; the stories below
5
+ reproduce that same `relative inline-flex` wrapper by hand so Badge can be previewed in
6
+ isolation. `standalone` renders it in normal flow instead (e.g. ListItem's trailing slot).
7
7
  -->
8
8
  <script module lang="ts">
9
9
  import { defineMeta } from '@storybook/addon-svelte-csf';
@@ -18,41 +18,59 @@
18
18
  control: 'select',
19
19
  options: ['sm', 'lg']
20
20
  },
21
- number: { control: 'number' }
21
+ number: { control: 'number' },
22
+ max: { control: 'number' },
23
+ standalone: { control: 'boolean' }
22
24
  },
23
25
  args: {
24
26
  size: 'lg',
25
- number: 5
27
+ number: 5,
28
+ max: 999,
29
+ standalone: false
26
30
  }
27
31
  });
28
32
  </script>
29
33
 
30
34
  <Story name="Playground">
31
35
  {#snippet template(args)}
32
- <div class="relative inline-flex size-10 items-center justify-center">
36
+ <span class="relative inline-flex">
33
37
  <Icon name="mail" />
34
- <Badge size={args.size} number={args.number} />
35
- </div>
38
+ <Badge size={args.size} number={args.number} max={args.max} standalone={args.standalone} />
39
+ </span>
36
40
  {/snippet}
37
41
  </Story>
38
42
 
39
- <Story name="Dot (Small)" asChild>
40
- <div class="relative inline-flex size-10 items-center justify-center">
43
+ <Story name="Dot (Small)" exportName="DotSmall" asChild>
44
+ <span class="relative inline-flex">
41
45
  <Icon name="notifications" />
42
46
  <Badge size="sm" />
43
- </div>
47
+ </span>
44
48
  </Story>
45
49
 
46
- <Story name="Numbered (Large)" asChild>
47
- <div class="relative inline-flex size-10 items-center justify-center">
50
+ <Story name="Numbered (Large)" exportName="NumberedLarge" asChild>
51
+ <span class="relative inline-flex">
48
52
  <Icon name="mail" />
49
53
  <Badge size="lg" number={7} />
50
- </div>
54
+ </span>
51
55
  </Story>
52
56
 
53
- <Story name="Overflow (99+)" asChild>
54
- <div class="relative inline-flex size-10 items-center justify-center">
57
+ <Story name="Two digits" asChild>
58
+ <span class="relative inline-flex">
55
59
  <Icon name="mail" />
56
- <Badge size="lg" number={128} />
57
- </div>
60
+ <Badge size="lg" number={42} />
61
+ </span>
62
+ </Story>
63
+
64
+ <Story name="Max character count (999+)" exportName="MaxCharacterCount" asChild>
65
+ <span class="relative inline-flex">
66
+ <Icon name="mail" />
67
+ <Badge size="lg" number={1280} />
68
+ </span>
69
+ </Story>
70
+
71
+ <Story name="Standalone" asChild>
72
+ <span class="gap-spacing-100 inline-flex items-center">
73
+ <Badge size="sm" standalone />
74
+ <Badge size="lg" number={3} standalone />
75
+ </span>
58
76
  </Story>
@@ -12,15 +12,22 @@ Large badges are used for specific counts.
12
12
  import { badge } from './theme.js';
13
13
  import type { BadgeProps } from './types.js';
14
14
 
15
- const { number, size = 'lg', class: className, ...restProps }: BadgeProps = $props();
15
+ const {
16
+ number,
17
+ size = 'lg',
18
+ max = 999,
19
+ standalone = false,
20
+ class: className,
21
+ ...restProps
22
+ }: BadgeProps = $props();
16
23
 
17
- const styling = $derived(badge({ size }));
24
+ const styling = $derived(badge({ size, standalone }));
18
25
  </script>
19
26
 
20
27
  {#if size === 'sm' || number}
21
- <div class={clsx(styling, className)} {...restProps}>
28
+ <span class={clsx(styling, className)} {...restProps}>
22
29
  {#if size !== 'sm' && number}
23
- {number > 99 ? '99+' : number}
30
+ {number > max ? `${max}+` : number}
24
31
  {/if}
25
- </div>
32
+ </span>
26
33
  {/if}
@@ -1,16 +1,39 @@
1
+ /**
2
+ * M3 badge (https://m3.material.io/components/badges/specs).
3
+ *
4
+ * Anchored offsets are measured from the positioned ancestor's top-trailing corner, so that
5
+ * ancestor must hug the icon (24dp box), not the larger active-indicator container:
6
+ * - small: 6dp, its bottom-leading corner 6dp down / 6dp in from the icon's top-trailing corner,
7
+ * i.e. flush with that corner (`top-spacing-0 right-spacing-0`).
8
+ * - large: 16dp tall, bottom-leading corner 14dp down / 12dp in from the icon's top-trailing
9
+ * corner, i.e. top = 14 − 16 = −2dp, left = 100% − 12dp. Anchored by its leading edge so a
10
+ * wider count grows away from the icon.
11
+ */
1
12
  export declare const badge: import("tailwind-variants").TVReturnType<{
2
13
  size: {
3
- sm: "size-1.5 top-0 right-3";
4
- lg: "h-3 min-w-3 p-1 -top-1 right-1";
14
+ sm: "size-spacing-75";
15
+ lg: "h-spacing-200 min-w-spacing-200 px-spacing-50 md-sys-typescale-label-small whitespace-nowrap tabular-nums";
5
16
  };
6
- }, undefined, "bg-md-sys-color-error text-md-sys-color-on-error absolute rounded-full md-sys-typescale-label-small inline-flex items-center justify-center", {
17
+ standalone: {
18
+ true: "relative";
19
+ false: "absolute pointer-events-none";
20
+ };
21
+ }, undefined, "bg-md-sys-color-error text-md-sys-color-on-error rounded-full inline-flex shrink-0 items-center justify-center", {
7
22
  size: {
8
- sm: "size-1.5 top-0 right-3";
9
- lg: "h-3 min-w-3 p-1 -top-1 right-1";
23
+ sm: "size-spacing-75";
24
+ lg: "h-spacing-200 min-w-spacing-200 px-spacing-50 md-sys-typescale-label-small whitespace-nowrap tabular-nums";
25
+ };
26
+ standalone: {
27
+ true: "relative";
28
+ false: "absolute pointer-events-none";
10
29
  };
11
30
  }, undefined, import("tailwind-variants").TVReturnTypeLike<{
12
31
  size: {
13
- sm: "size-1.5 top-0 right-3";
14
- lg: "h-3 min-w-3 p-1 -top-1 right-1";
32
+ sm: "size-spacing-75";
33
+ lg: "h-spacing-200 min-w-spacing-200 px-spacing-50 md-sys-typescale-label-small whitespace-nowrap tabular-nums";
34
+ };
35
+ standalone: {
36
+ true: "relative";
37
+ false: "absolute pointer-events-none";
15
38
  };
16
39
  }, undefined>>;
@@ -1,10 +1,30 @@
1
1
  import { tv } from 'tailwind-variants';
2
+ /**
3
+ * M3 badge (https://m3.material.io/components/badges/specs).
4
+ *
5
+ * Anchored offsets are measured from the positioned ancestor's top-trailing corner, so that
6
+ * ancestor must hug the icon (24dp box), not the larger active-indicator container:
7
+ * - small: 6dp, its bottom-leading corner 6dp down / 6dp in from the icon's top-trailing corner,
8
+ * i.e. flush with that corner (`top-spacing-0 right-spacing-0`).
9
+ * - large: 16dp tall, bottom-leading corner 14dp down / 12dp in from the icon's top-trailing
10
+ * corner, i.e. top = 14 − 16 = −2dp, left = 100% − 12dp. Anchored by its leading edge so a
11
+ * wider count grows away from the icon.
12
+ */
2
13
  export const badge = tv({
3
- base: 'bg-md-sys-color-error text-md-sys-color-on-error absolute rounded-full md-sys-typescale-label-small inline-flex items-center justify-center',
14
+ base: 'bg-md-sys-color-error text-md-sys-color-on-error rounded-full inline-flex shrink-0 items-center justify-center',
4
15
  variants: {
5
16
  size: {
6
- sm: 'size-1.5 top-0 right-3',
7
- lg: 'h-3 min-w-3 p-1 -top-1 right-1'
17
+ sm: 'size-spacing-75',
18
+ lg: 'h-spacing-200 min-w-spacing-200 px-spacing-50 md-sys-typescale-label-small whitespace-nowrap tabular-nums'
19
+ },
20
+ standalone: {
21
+ true: 'relative',
22
+ false: 'absolute pointer-events-none'
8
23
  }
9
- }
24
+ },
25
+ compoundVariants: [
26
+ { standalone: false, size: 'sm', class: 'top-spacing-0 right-spacing-0' },
27
+ { standalone: false, size: 'lg', class: '-top-spacing-25 left-[calc(100%-12px)]' }
28
+ ],
29
+ defaultVariants: { size: 'lg', standalone: false }
10
30
  });
@@ -2,17 +2,31 @@ import type { HTMLAttributes } from 'svelte/elements';
2
2
  import type { VariantProps } from 'tailwind-variants';
3
3
  import type { badge } from './theme.js';
4
4
  export type BadgeVariants = VariantProps<typeof badge>;
5
- export type BadgeProps = BadgeVariants & HTMLAttributes<HTMLDivElement> & {
5
+ export type BadgeProps = BadgeVariants & HTMLAttributes<HTMLSpanElement> & {
6
6
  /**
7
7
  * The size of the badge.
8
- * - 'sm': Small badge, no number displayed.
9
- * - 'lg': Large badge, displays number.
8
+ * - 'sm': Small badge (6dp dot), no number displayed.
9
+ * - 'lg': Large badge (16dp tall), displays number.
10
10
  */
11
11
  size?: BadgeVariants['size'];
12
12
  /**
13
13
  * The number to display in the badge.
14
- * Values greater than 99 will be displayed as "99+".
14
+ * Values greater than `max` are displayed as `${max}+`.
15
15
  * If size is 'sm', the number is not displayed.
16
16
  */
17
17
  number?: number;
18
+ /**
19
+ * Largest count shown before collapsing to `${max}+`. M3 caps a large badge at four
20
+ * characters including the "+" (16×34dp container), so the default is 999 → "999+".
21
+ * @default 999
22
+ */
23
+ max?: number;
24
+ /**
25
+ * By default the badge is absolutely positioned on its nearest positioned ancestor's
26
+ * top-trailing corner per the M3 offsets — that ancestor should hug the icon (e.g.
27
+ * `<span class="relative inline-flex">` around a 24dp `Icon`). Set `standalone` to render it
28
+ * in normal flow instead, e.g. in a list item's trailing slot.
29
+ * @default false
30
+ */
31
+ standalone?: boolean;
18
32
  };
@@ -0,0 +1,85 @@
1
+ import { Meta, Title, Subtitle, Canvas, Controls } from '@storybook/addon-docs/blocks';
2
+ import * as ButtonStories from './Button.stories.svelte';
3
+
4
+ <Meta of={ButtonStories} />
5
+
6
+ <Title />
7
+
8
+ <Subtitle>Buttons prompt most actions in a UI.</Subtitle>
9
+
10
+ [M3 spec](https://m3.material.io/components/buttons/specs) ·
11
+ `import { Button, Toggle } from '@noxlovette/material';`
12
+
13
+ <Canvas of={ButtonStories.Playground} />
14
+
15
+ <Controls of={ButtonStories.Playground} />
16
+
17
+ ## Choosing a button
18
+
19
+ One high-emphasis button per screen, for its primary action. Show the rest's importance through
20
+ their colour style.
21
+
22
+ | Emphasis | Use | For example |
23
+ | -------- | --------------------------------------------------------------------- | -------------------- |
24
+ | Highest | `FAB` or extended FAB, for the screen's main action | Compose, Create |
25
+ | High | `Button variant="filled"`: final or unblocking actions | Save, Confirm, Done |
26
+ | High | `SplitButton`: a key action with related options | Send + Schedule send |
27
+ | Medium | `variant="tonal"`: important, a step below filled | Save draft |
28
+ | Medium | `variant="elevated"`: only to stand apart from a patterned background | Add to cart |
29
+ | Medium | `variant="outlined"`: attention without being primary; leaving a flow | View all, Cancel |
30
+ | Low | `variant="text"`: optional actions | Learn more |
31
+ | Low | `ButtonIcon`: compact supplementary actions | Favourite, Print |
32
+
33
+ Put buttons side by side rather than stacked when there's room, with the higher-emphasis one
34
+ for the more important action.
35
+
36
+ <Canvas of={ButtonStories.ColourStyles} />
37
+
38
+ ## Sizes
39
+
40
+ | `size` | Height | Padding | Icon | Label |
41
+ | ------ | ------ | ------- | ---- | -------------- |
42
+ | `xs` | 32dp | 12dp | 20dp | label large |
43
+ | `sm` | 40dp | 16dp | 20dp | label large |
44
+ | `md` | 56dp | 24dp | 24dp | title medium |
45
+ | `lg` | 96dp | 48dp | 32dp | headline small |
46
+ | `xl` | 136dp | 64dp | 40dp | headline large |
47
+
48
+ `sm` is the default. XS and S buttons keep a 48dp target area around them, so leave room
49
+ between them (a `ButtonGroup` does). The outline thickens to 2dp at L and 3dp at XL.
50
+
51
+ <Canvas of={ButtonStories.Sizes} />
52
+
53
+ ## Shape
54
+
55
+ `round` (default) is fully rounded; `square` has 12, 12, 16, 28 and 28dp corners from XS to XL.
56
+ While pressed, both morph to the same tighter corner (8, 8, 12, 16, 16dp) on the fast spatial
57
+ spring, which carries its velocity when you let go mid-morph, so a quick tap still bounces.
58
+
59
+ <Canvas of={ButtonStories.RoundAndSquare} />
60
+
61
+ ## Toggle buttons
62
+
63
+ `Toggle` is a button with a selected state (`aria-pressed`). Selecting changes its colours and
64
+ swaps its shape: round becomes square, square becomes round. It comes in the elevated, filled,
65
+ tonal and outlined styles; there is no text toggle. See **Buttons/Toggle**.
66
+
67
+ ## States and accessibility
68
+
69
+ - Hover, focus and press tint the container 8%, 10% and 10% in the content colour. Keyboard
70
+ focus draws a 3dp ring 2dp outside the button.
71
+ - Disabled buttons drop to a 10% on-surface container and 38% on-surface content, and lose
72
+ their elevation. A disabled link gets `aria-disabled` and leaves the tab order.
73
+ - `loading` swaps the icon (or the label, with no icon) for a loading indicator and sets
74
+ `aria-busy`.
75
+
76
+ <Canvas of={ButtonStories.Disabled} />
77
+
78
+ ## Where this differs from the spec
79
+
80
+ - The text button's disabled state has no container. Its token lists a 10% container, but the
81
+ spec's figures show none.
82
+ - Shape morphs use the library's fast spatial spring (stiffness 800, damping 0.6) instead of
83
+ the button token's 1400 / 0.9, as every motion here uses the Expressive springs.
84
+ - `color` (primary, secondary, tertiary, error) is gone. M3 has one colour mapping per style;
85
+ restyle with a class if you need another role, keeping 3:1 contrast.