@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
@@ -36,14 +36,14 @@
36
36
 
37
37
  <Story name="Playground">
38
38
  {#snippet template(args)}
39
- <div class="max-w-sm p-10">
39
+ <div class="p-spacing-500 max-w-sm">
40
40
  <Slider {...args} />
41
41
  </div>
42
42
  {/snippet}
43
43
  </Story>
44
44
 
45
45
  <Story name="Sizes" asChild>
46
- <div class="flex max-w-sm flex-col gap-8 p-10">
46
+ <div class="gap-spacing-400 p-spacing-500 flex max-w-sm flex-col">
47
47
  <Slider value={30} size="xs" />
48
48
  <Slider value={30} size="s" />
49
49
  <Slider value={30} size="m" />
@@ -52,8 +52,8 @@
52
52
  </div>
53
53
  </Story>
54
54
 
55
- <Story name="With Icons" asChild>
56
- <div class="max-w-sm p-10">
55
+ <Story name="With Icons" exportName="WithIcons" asChild>
56
+ <div class="p-spacing-500 max-w-sm">
57
57
  <Slider
58
58
  value={60}
59
59
  size="m"
@@ -63,14 +63,14 @@
63
63
  </div>
64
64
  </Story>
65
65
 
66
- <Story name="Discrete With Stops" asChild>
67
- <div class="max-w-sm p-10">
66
+ <Story name="Discrete With Stops" exportName="DiscreteWithStops" asChild>
67
+ <div class="p-spacing-500 max-w-sm">
68
68
  <Slider value={40} step={20} stops />
69
69
  </div>
70
70
  </Story>
71
71
 
72
72
  <Story name="Disabled" asChild>
73
- <div class="max-w-sm p-10">
73
+ <div class="p-spacing-500 max-w-sm">
74
74
  <Slider value={40} disabled />
75
75
  </div>
76
76
  </Story>
@@ -41,13 +41,18 @@ the track, a new `value` from outside) springs to it on the fast spatial spring.
41
41
 
42
42
  /* M3 slider geometry, in px. */
43
43
  const HANDLE_PX = 4;
44
+ /* Pressed and focused handles narrow to 2dp. */
45
+ const HANDLE_NARROW_PX = 2;
44
46
  const GAP_PX = 6;
45
- const ICON_PADDING_PX = 8;
46
- /* Stops closer than this to the handle's center are hidden under its gap. */
47
- const STOP_CLEARANCE_PX = HANDLE_PX / 2 + GAP_PX;
47
+ /* Stop indicators are 4dp with 4dp trailing space, so their centers sit 6dp in from each end. */
48
+ const STOP_INSET_PX = 6;
48
49
 
49
- const iconSize = $derived(size === 'xl' ? 'lg' : 'md');
50
- const iconPx = $derived(size === 'xl' ? 40 : 24);
50
+ /* Inset icons exist only on M, L and XL: 24dp/6dp padding, 32dp/8dp on XL. */
51
+ const hasIcons = $derived(size === 'm' || size === 'l' || size === 'xl');
52
+ const iconPx = $derived(size === 'xl' ? 32 : 24);
53
+ const iconPaddingPx = $derived(size === 'xl' ? 8 : 6);
54
+ const leadingIcon = $derived(hasIcons ? leadingIconProps : undefined);
55
+ const trailingIcon = $derived(hasIcons ? trailingIconProps : undefined);
51
56
 
52
57
  const stepValue = $derived(typeof step === 'number' ? step : undefined);
53
58
  const range = $derived(max - min);
@@ -60,17 +65,21 @@ the track, a new `value` from outside) springs to it on the fast spatial spring.
60
65
  pressed, the handle is being dragged and tracks it directly.
61
66
  */
62
67
  let dragging = $state(false);
68
+ let pressed = $state(false);
69
+ let focusVisible = $state(false);
63
70
 
64
71
  const trackDrag: Attachment<HTMLElement> = (node) => {
65
72
  const move = () => (dragging = true);
66
73
  const release = () => {
67
74
  dragging = false;
75
+ pressed = false;
68
76
  window.removeEventListener('pointermove', move);
69
77
  window.removeEventListener('pointerup', release);
70
78
  window.removeEventListener('pointercancel', release);
71
79
  };
72
80
  const press = () => {
73
81
  if (disabled) return;
82
+ pressed = true;
74
83
  window.addEventListener('pointermove', move);
75
84
  window.addEventListener('pointerup', release);
76
85
  window.addEventListener('pointercancel', release);
@@ -92,6 +101,15 @@ the track, a new `value` from outside) springs to it on the fast spatial spring.
92
101
  /* The spring may overshoot the ends; the track can't. */
93
102
  const pos = $derived(Math.min(1, Math.max(0, fraction.current)));
94
103
 
104
+ /** Handle thickness: 4dp at rest, 2dp while pressed or keyboard-focused. */
105
+ const handleThickness = new SpringValue(
106
+ () => (pressed || focusVisible ? HANDLE_NARROW_PX : HANDLE_PX),
107
+ springTokens.fastSpatial
108
+ );
109
+ const handlePx = $derived(Math.max(1, handleThickness.current));
110
+ /* The gap is measured from the handle's current edge, so it stays 6dp as the handle narrows. */
111
+ const clearPx = $derived(handlePx / 2 + GAP_PX);
112
+
95
113
  /* ---- geometry -------------------------------------------------------------------------- */
96
114
 
97
115
  let trackWidth = $state(0);
@@ -101,21 +119,19 @@ the track, a new `value` from outside) springs to it on the fast spatial spring.
101
119
  const start = $derived(vertical ? 'bottom' : 'left');
102
120
  const extent = $derived(vertical ? 'height' : 'width');
103
121
 
104
- /** Handle center: travels the track inset by half a handle so it never overhangs the ends. */
122
+ /** Handle center: travels the track inset by half a resting handle so it never overhangs the ends. */
105
123
  const center = (f: number) => `calc(${HANDLE_PX / 2}px + ${f} * (100% - ${HANDLE_PX}px))`;
106
124
  const centerPx = $derived(HANDLE_PX / 2 + pos * (length - HANDLE_PX));
107
125
 
108
- const activePx = $derived(centerPx - HANDLE_PX / 2 - GAP_PX);
109
- const inactivePx = $derived(length - centerPx - HANDLE_PX / 2 - GAP_PX);
126
+ const activePx = $derived(centerPx - clearPx);
127
+ const inactivePx = $derived(length - centerPx - clearPx);
110
128
 
111
- const activeStyle = $derived(
112
- `${extent}: max(0px, calc(${pos} * (100% - ${HANDLE_PX}px) - ${GAP_PX}px))`
113
- );
114
- const inactiveStyle = $derived(
115
- `${extent}: max(0px, calc(${1 - pos} * (100% - ${HANDLE_PX}px) - ${GAP_PX}px))`
116
- );
129
+ const trackStyle = (f: number) =>
130
+ `${extent}: max(0px, calc(${f} * (100% - ${HANDLE_PX}px) + ${HANDLE_PX / 2 - clearPx}px))`;
131
+ const activeStyle = $derived(trackStyle(pos));
132
+ const inactiveStyle = $derived(trackStyle(1 - pos));
117
133
 
118
- /* Stops sit inset by half the track thickness so the end stops land in the rounded caps. */
134
+ /* Stops span the track inset by STOP_INSET_PX, so the end stops keep their 4dp trailing space. */
119
135
  const stopList = $derived.by(() => {
120
136
  const output: number[] = [];
121
137
  if (stops && stepValue && range > 0) {
@@ -124,24 +140,24 @@ the track, a new `value` from outside) springs to it on the fast spatial spring.
124
140
  output.push(1);
125
141
  }
126
142
  return output.filter((s) => {
127
- if (leadingIconProps && s === 0) return false;
128
- if (trailingIconProps && s === 1) return false;
129
- return !length || Math.abs(s - pos) * length > STOP_CLEARANCE_PX;
143
+ if (leadingIcon && s === 0) return false;
144
+ if (trailingIcon && s === 1) return false;
145
+ return !length || Math.abs(s - pos) * length > clearPx;
130
146
  });
131
147
  });
132
148
 
133
149
  /* Icons ride inside their track; when it gets too short they hop across the handle. */
134
- const iconFits = (trackPx: number) => trackPx >= iconPx + ICON_PADDING_PX * 2;
150
+ const iconFits = (trackPx: number) => trackPx >= iconPx + iconPaddingPx * 2;
135
151
  const leadingOnActive = $derived(!length || iconFits(activePx));
136
152
  const trailingOnInactive = $derived(!length || iconFits(inactivePx));
137
153
 
138
154
  const leadingOffset = $derived(
139
- leadingOnActive ? ICON_PADDING_PX : centerPx + HANDLE_PX / 2 + GAP_PX + ICON_PADDING_PX
155
+ leadingOnActive ? iconPaddingPx : centerPx + clearPx + iconPaddingPx
140
156
  );
141
157
  const trailingOffset = $derived(
142
158
  trailingOnInactive
143
- ? length - ICON_PADDING_PX - iconPx
144
- : centerPx - HANDLE_PX / 2 - GAP_PX - ICON_PADDING_PX - iconPx
159
+ ? length - iconPaddingPx - iconPx
160
+ : centerPx - clearPx - iconPaddingPx - iconPx
145
161
  );
146
162
 
147
163
  const sliderDir = $derived(dir === null || dir === 'auto' ? undefined : dir);
@@ -175,26 +191,26 @@ the track, a new `value` from outside) springs to it on the fast spatial spring.
175
191
  {#each stopList as s (s)}
176
192
  <div
177
193
  class={cls.stop({ class: s < pos ? cls.stopOnActive() : cls.stopOnInactive() })}
178
- style="{start}: calc(var(--track) / 2 + {s} * (100% - var(--track)))"
194
+ style="{start}: calc({STOP_INSET_PX}px + {s} * (100% - {STOP_INSET_PX * 2}px))"
179
195
  ></div>
180
196
  {/each}
181
197
 
182
- {#if leadingIconProps}
198
+ {#if leadingIcon}
183
199
  <span
184
200
  class={cls.icon({ class: leadingOnActive ? cls.iconOnActive() : cls.iconOnInactive() })}
185
201
  style="{start}: {leadingOffset}px"
186
202
  >
187
- <Icon size={iconSize} {...leadingIconProps} />
203
+ <Icon {...leadingIcon} class={cls.iconGlyph({ class: clsx(leadingIcon.class) })} />
188
204
  </span>
189
205
  {/if}
190
- {#if trailingIconProps}
206
+ {#if trailingIcon}
191
207
  <span
192
208
  class={cls.icon({
193
209
  class: trailingOnInactive ? cls.iconOnInactive() : cls.iconOnActive()
194
210
  })}
195
211
  style="{start}: {trailingOffset}px"
196
212
  >
197
- <Icon size={iconSize} {...trailingIconProps} />
213
+ <Icon {...trailingIcon} class={cls.iconGlyph({ class: clsx(trailingIcon.class) })} />
198
214
  </span>
199
215
  {/if}
200
216
 
@@ -204,7 +220,9 @@ the track, a new `value` from outside) springs to it on the fast spatial spring.
204
220
  {...thumbProps}
205
221
  class={cls.handle()}
206
222
  data-active={active || undefined}
207
- style="{start}: {center(pos)}"
223
+ style="{start}: {center(pos)}; {extent}: {handlePx}px"
224
+ onfocus={(e) => (focusVisible = e.currentTarget.matches(':focus-visible'))}
225
+ onblur={() => (focusVisible = false)}
208
226
  ></div>
209
227
  {/snippet}
210
228
  </Slider.Thumb>
@@ -1,6 +1,8 @@
1
1
  import { type VariantProps } from 'tailwind-variants';
2
2
  /**
3
3
  * M3 Expressive slider: an active and an inactive track separated from a bar handle by a 6dp gap.
4
+ * Per-size tokens (track / handle / outer corner / inset icon): XS 16/44/8/–, S 24/44/8/–,
5
+ * M 40/52/12/24, L 56/68/16/24, XL 96/108/28/32. The value indicator sits 12dp above the handle.
4
6
  * Track/handle positions are set inline by `Slider.svelte` from one animated value; these slots
5
7
  * only carry size, shape and color.
6
8
  *
@@ -16,12 +18,15 @@ export declare const slider: import("tailwind-variants").TVReturnType<{
16
18
  };
17
19
  m: {
18
20
  root: string;
21
+ iconGlyph: string;
19
22
  };
20
23
  l: {
21
24
  root: string;
25
+ iconGlyph: string;
22
26
  };
23
27
  xl: {
24
28
  root: string;
29
+ iconGlyph: string;
25
30
  };
26
31
  };
27
32
  vertical: {
@@ -60,6 +65,7 @@ export declare const slider: import("tailwind-variants").TVReturnType<{
60
65
  stopOnActive: string;
61
66
  stopOnInactive: string;
62
67
  icon: string;
68
+ iconGlyph: string;
63
69
  iconOnActive: string;
64
70
  iconOnInactive: string;
65
71
  handle: string;
@@ -74,12 +80,15 @@ export declare const slider: import("tailwind-variants").TVReturnType<{
74
80
  };
75
81
  m: {
76
82
  root: string;
83
+ iconGlyph: string;
77
84
  };
78
85
  l: {
79
86
  root: string;
87
+ iconGlyph: string;
80
88
  };
81
89
  xl: {
82
90
  root: string;
91
+ iconGlyph: string;
83
92
  };
84
93
  };
85
94
  vertical: {
@@ -118,6 +127,7 @@ export declare const slider: import("tailwind-variants").TVReturnType<{
118
127
  stopOnActive: string;
119
128
  stopOnInactive: string;
120
129
  icon: string;
130
+ iconGlyph: string;
121
131
  iconOnActive: string;
122
132
  iconOnInactive: string;
123
133
  handle: string;
@@ -132,12 +142,15 @@ export declare const slider: import("tailwind-variants").TVReturnType<{
132
142
  };
133
143
  m: {
134
144
  root: string;
145
+ iconGlyph: string;
135
146
  };
136
147
  l: {
137
148
  root: string;
149
+ iconGlyph: string;
138
150
  };
139
151
  xl: {
140
152
  root: string;
153
+ iconGlyph: string;
141
154
  };
142
155
  };
143
156
  vertical: {
@@ -176,6 +189,7 @@ export declare const slider: import("tailwind-variants").TVReturnType<{
176
189
  stopOnActive: string;
177
190
  stopOnInactive: string;
178
191
  icon: string;
192
+ iconGlyph: string;
179
193
  iconOnActive: string;
180
194
  iconOnInactive: string;
181
195
  handle: string;
@@ -1,6 +1,8 @@
1
1
  import { tv } from 'tailwind-variants';
2
2
  /**
3
3
  * M3 Expressive slider: an active and an inactive track separated from a bar handle by a 6dp gap.
4
+ * Per-size tokens (track / handle / outer corner / inset icon): XS 16/44/8/–, S 24/44/8/–,
5
+ * M 40/52/12/24, L 56/68/16/24, XL 96/108/28/32. The value indicator sits 12dp above the handle.
4
6
  * Track/handle positions are set inline by `Slider.svelte` from one animated value; these slots
5
7
  * only carry size, shape and color.
6
8
  *
@@ -12,22 +14,22 @@ export const slider = tv({
12
14
  track: 'pointer-events-none absolute',
13
15
  activeTrack: 'bg-md-sys-color-primary group-data-[disabled]:bg-md-sys-color-on-surface/38',
14
16
  inactiveTrack: 'bg-md-sys-color-secondary-container group-data-[disabled]:bg-md-sys-color-on-surface/12',
15
- stop: 'pointer-events-none absolute size-1 rounded-full',
17
+ stop: 'pointer-events-none absolute size-spacing-50 rounded-full',
16
18
  stopOnActive: 'bg-md-sys-color-on-primary group-data-[disabled]:bg-md-sys-color-inverse-on-surface',
17
19
  stopOnInactive: 'bg-md-sys-color-on-secondary-container group-data-[disabled]:bg-md-sys-color-on-surface/38',
18
- icon: 'pointer-events-none absolute',
20
+ icon: 'pointer-events-none absolute flex',
21
+ iconGlyph: '',
19
22
  iconOnActive: 'text-md-sys-color-on-primary',
20
23
  iconOnInactive: 'text-md-sys-color-on-secondary-container',
21
24
  handle: `
22
25
  absolute rounded-full bg-md-sys-color-primary outline-none
23
- transition-[width,height] md-sys-motion-fast-spatial
24
26
  focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-md-sys-color-secondary
25
27
  group-data-[disabled]:bg-md-sys-color-on-surface/38
26
28
  `,
27
29
  value: `
28
- pointer-events-none absolute z-10 flex min-w-12 items-center justify-center rounded-full
29
- bg-md-sys-color-inverse-surface px-4 py-3
30
- md-sys-typescale-label-large text-md-sys-color-inverse-on-surface
30
+ pointer-events-none absolute z-10 flex min-w-spacing-600 items-center justify-center rounded-full
31
+ bg-md-sys-color-inverse-surface px-spacing-200 py-spacing-150
32
+ md-sys-typescale-body-medium tracking-[0.5px] text-md-sys-color-inverse-on-surface
31
33
  opacity-0 transition-opacity md-sys-motion-effects
32
34
  group-has-[[data-slider-thumb]:focus-visible]:opacity-100
33
35
  `
@@ -36,30 +38,39 @@ export const slider = tv({
36
38
  size: {
37
39
  xs: { root: '[--track:1rem] [--handle:2.75rem] [--outer:var(--radius-sm)]' },
38
40
  s: { root: '[--track:1.5rem] [--handle:2.75rem] [--outer:var(--radius-sm)]' },
39
- m: { root: '[--track:2.5rem] [--handle:3.25rem] [--outer:var(--radius-md)]' },
40
- l: { root: '[--track:3.5rem] [--handle:4.25rem] [--outer:var(--radius-lg)]' },
41
- xl: { root: '[--track:6rem] [--handle:6.75rem] [--outer:var(--radius-xl)]' }
41
+ m: {
42
+ root: '[--track:2.5rem] [--handle:3.25rem] [--outer:var(--radius-md)]',
43
+ iconGlyph: 'size-spacing-300 text-[24px]'
44
+ },
45
+ l: {
46
+ root: '[--track:3.5rem] [--handle:4.25rem] [--outer:var(--radius-lg)]',
47
+ iconGlyph: 'size-spacing-300 text-[24px]'
48
+ },
49
+ xl: {
50
+ root: '[--track:6rem] [--handle:6.75rem] [--outer:var(--radius-xl)]',
51
+ iconGlyph: 'size-spacing-400 text-[32px]'
52
+ }
42
53
  },
43
54
  vertical: {
44
55
  false: {
45
56
  root: 'h-[var(--handle)] w-full min-w-40',
46
57
  track: 'top-1/2 h-[var(--track)] -translate-y-1/2',
47
- activeTrack: 'left-0 rounded-l-[var(--outer)] rounded-r-[2px]',
48
- inactiveTrack: 'right-0 rounded-l-[2px] rounded-r-[var(--outer)]',
58
+ activeTrack: 'left-spacing-0 rounded-l-[var(--outer)] rounded-r-[2px]',
59
+ inactiveTrack: 'right-spacing-0 rounded-l-[2px] rounded-r-[var(--outer)]',
49
60
  stop: 'top-1/2 -translate-x-1/2 -translate-y-1/2',
50
61
  icon: 'top-1/2 -translate-y-1/2',
51
- handle: 'top-0 h-full w-1 -translate-x-1/2 data-[active]:w-0.5',
52
- value: 'bottom-[calc(100%+4px)] -translate-x-1/2'
62
+ handle: 'top-spacing-0 h-full -translate-x-1/2',
63
+ value: 'bottom-[calc(100%+12px)] -translate-x-1/2'
53
64
  },
54
65
  true: {
55
66
  root: 'h-full min-h-40 w-[var(--handle)]',
56
67
  track: 'left-1/2 w-[var(--track)] -translate-x-1/2',
57
- activeTrack: 'bottom-0 rounded-t-[2px] rounded-b-[var(--outer)]',
58
- inactiveTrack: 'top-0 rounded-t-[var(--outer)] rounded-b-[2px]',
68
+ activeTrack: 'bottom-spacing-0 rounded-t-[2px] rounded-b-[var(--outer)]',
69
+ inactiveTrack: 'top-spacing-0 rounded-t-[var(--outer)] rounded-b-[2px]',
59
70
  stop: 'left-1/2 -translate-x-1/2 translate-y-1/2',
60
71
  icon: 'left-1/2 -translate-x-1/2',
61
- handle: 'left-0 h-1 w-full translate-y-1/2 data-[active]:h-0.5',
62
- value: 'left-[calc(100%+4px)] translate-y-1/2'
72
+ handle: 'left-spacing-0 w-full translate-y-1/2',
73
+ value: 'left-[calc(100%+12px)] translate-y-1/2'
63
74
  }
64
75
  },
65
76
  active: {
@@ -30,11 +30,12 @@ export type SliderProps = Omit<SliderVariants, 'active'> & Omit<HTMLAttributes<H
30
30
  */
31
31
  showValue?: boolean;
32
32
  /**
33
- * Props for the leading icon.
33
+ * Props for the leading inset icon. M3 only defines inset icons for sizes `m`, `l` and `xl`
34
+ * (24dp, or 32dp on XL); it is ignored on `xs` and `s`.
34
35
  */
35
36
  leadingIconProps?: IconProps;
36
37
  /**
37
- * Props for the trailing icon.
38
+ * Props for the trailing inset icon. Only rendered on sizes `m`, `l` and `xl`.
38
39
  */
39
40
  trailingIconProps?: IconProps;
40
41
  /**
@@ -0,0 +1,38 @@
1
+ import { Meta, Title, Subtitle, Canvas, Controls } from '@storybook/addon-docs/blocks';
2
+ import * as SwitchStories from './Switch.stories.svelte';
3
+
4
+ <Meta of={SwitchStories} />
5
+
6
+ <Title />
7
+
8
+ <Subtitle>Switches toggle a single setting on or off.</Subtitle>
9
+
10
+ [M3 spec](https://m3.material.io/components/switch/specs) ·
11
+ `import { Switch } from '@noxlovette/material';`
12
+
13
+ <Canvas of={SwitchStories.Playground} />
14
+
15
+ <Controls of={SwitchStories.Playground} />
16
+
17
+ ## Icons
18
+
19
+ `icons` controls the thumb icon: `checked` (the default) shows a check only when on, `both`
20
+ shows a check when on and a close icon when off, and `none` shows a plain thumb. Showing icons
21
+ gives a signal that doesn't depend on color alone.
22
+
23
+ <Canvas of={SwitchStories.IconModes} />
24
+
25
+ ## Dragging
26
+
27
+ Besides click and tap, the thumb can be dragged: moving it more than 16px toward the other side
28
+ flips the state, like a native mobile switch.
29
+
30
+ ```svelte
31
+ <Switch bind:checked={notifications} />
32
+ ```
33
+
34
+ ## Accessibility
35
+
36
+ - **Role.** The control is a `role="switch"` from bits-ui; Space and Enter toggle it.
37
+ - **Label.** The switch has no built-in label. Wrap it in a `<label>` with visible text, or give
38
+ it an `aria-label`.
@@ -23,14 +23,14 @@
23
23
 
24
24
  <Story name="Playground">
25
25
  {#snippet template(args)}
26
- <div class="p-6">
26
+ <div class="p-spacing-300">
27
27
  <Switch {...args} />
28
28
  </div>
29
29
  {/snippet}
30
30
  </Story>
31
31
 
32
32
  <Story name="Icon Modes" asChild>
33
- <div class="flex flex-col gap-4 p-6">
33
+ <div class="gap-spacing-200 p-spacing-300 flex flex-col">
34
34
  <Switch checked icons="checked" />
35
35
  <Switch checked icons="both" />
36
36
  <Switch checked icons="none" />
@@ -38,7 +38,7 @@
38
38
  </Story>
39
39
 
40
40
  <Story name="States" asChild>
41
- <div class="flex flex-col gap-4 p-6">
41
+ <div class="gap-spacing-200 p-spacing-300 flex flex-col">
42
42
  <Switch checked={false} />
43
43
  <Switch checked={true} />
44
44
  <Switch checked={false} disabled />
@@ -5,10 +5,10 @@ import { tv } from 'tailwind-variants';
5
5
  // Hover — 40dp state layer ring around thumb
6
6
  export const toggle = tv({
7
7
  slots: {
8
- root: 'group relative inline-flex h-8 w-[3.25rem] shrink-0 [print-color-adjust:exact]',
8
+ root: 'group relative inline-flex h-spacing-400 w-[3.25rem] shrink-0 [print-color-adjust:exact]',
9
9
  // The input IS the track — styled as the pill background
10
10
  input: [
11
- 'absolute inset-0 m-0 h-full w-full appearance-none rounded-full',
11
+ 'absolute inset-spacing-0 m-spacing-0 h-full w-full appearance-none rounded-full',
12
12
  'border-2 border-md-sys-color-outline bg-md-sys-color-surface-container-highest',
13
13
  'group-data-[state=checked]:border-md-sys-color-primary group-data-[state=checked]:bg-md-sys-color-primary',
14
14
  'group-data-[disabled]:cursor-not-allowed',
@@ -18,7 +18,7 @@ export const toggle = tv({
18
18
  ].join(' '),
19
19
  // Thumb — absolute over track
20
20
  handle: [
21
- 'pointer-events-none absolute left-2 top-1/2 flex h-4 w-4 -translate-y-1/2',
21
+ 'pointer-events-none absolute left-spacing-100 top-1/2 flex h-spacing-200 w-spacing-200 -translate-y-1/2',
22
22
  'items-center justify-center rounded-full relative',
23
23
  'bg-md-sys-color-outline',
24
24
  'transition-[left,transform,background-color] md-sys-motion-fast-spatial',
@@ -36,17 +36,17 @@ export const toggle = tv({
36
36
  ].join(' '),
37
37
  // State layer ring around thumb
38
38
  hover: [
39
- 'pointer-events-none absolute left-4 top-1/2 h-12 w-12',
39
+ 'pointer-events-none absolute left-spacing-200 top-1/2 h-spacing-600 w-spacing-600',
40
40
  '-translate-x-1/2 -translate-y-1/2 rounded-full',
41
41
  'transition-[left,background-color] md-sys-motion-fast-spatial',
42
- 'group-data-[state=checked]:left-9',
42
+ 'group-data-[state=checked]:left-spacing-450',
43
43
  'group-data-[disabled]:hidden',
44
44
  'group-hover:bg-md-sys-color-on-surface/8',
45
45
  'group-data-[state=checked]:group-hover:bg-md-sys-color-primary/8'
46
46
  ].join(' '),
47
47
  // Icon inside thumb - shared base (sized via size="xs" prop on Icon, not here)
48
48
  icon: [
49
- 'absolute inset-0 m-auto pointer-events-none flex items-center justify-center',
49
+ 'absolute inset-spacing-0 m-auto pointer-events-none flex items-center justify-center',
50
50
  'opacity-0 transition-opacity md-sys-motion-fast-effects shrink-0'
51
51
  ].join(' '),
52
52
  // Specific icon states - colour matches M3 spec for each thumb background
@@ -0,0 +1,53 @@
1
+ import { Meta, Title, Subtitle, Canvas, Controls } from '@storybook/addon-docs/blocks';
2
+ import * as TextfieldStories from './Textfield.stories.svelte';
3
+
4
+ <Meta of={TextfieldStories} />
5
+
6
+ <Title />
7
+
8
+ <Subtitle>Text fields let users enter and edit text.</Subtitle>
9
+
10
+ [M3 spec](https://m3.material.io/components/text-fields/specs) ·
11
+ `import { Textfield } from '@noxlovette/material';`
12
+
13
+ <Canvas of={TextfieldStories.Playground} />
14
+
15
+ <Controls of={TextfieldStories.Playground} />
16
+
17
+ ## Outlined or filled
18
+
19
+ `variant` defaults to `outlined`: a bordered field whose floating label opens a gap in the border.
20
+ `filled` has a container and a bottom indicator line, and carries more emphasis. Use outlined in
21
+ busy surroundings (over an image, in dense forms) or next to a filled field that should stand
22
+ out.
23
+
24
+ <Canvas of={TextfieldStories.Outlined} />
25
+
26
+ The `label` starts inside the field and floats up on focus or once there's a value.
27
+
28
+ ```svelte
29
+ <Textfield label="Email" bind:value={email} required>
30
+ {#snippet supportingText()}
31
+ We'll never share your email.
32
+ {/snippet}
33
+ </Textfield>
34
+ ```
35
+
36
+ ## Icons and supporting text
37
+
38
+ `leadingIconProps` and `trailingIconProps` add icons (a trailing icon is a good place for a clear
39
+ button). The `supportingText` snippet renders helper or error text below the field. Other native
40
+ `<input>` attributes (`type`, `maxlength`, `autocomplete`…) pass through to the input.
41
+
42
+ <Canvas of={TextfieldStories.WithIcons} />
43
+
44
+ <Canvas of={TextfieldStories.CharacterLimit} />
45
+
46
+ ## Accessibility
47
+
48
+ - **Label.** The floating label is a real `<label for>` tied to the input's id (generated when you
49
+ don't pass one), not an `aria-label`.
50
+ - **Errors.** `error` sets `aria-invalid`. Say what's wrong in `supportingText`, not just with
51
+ color.
52
+ - **Required.** `required` sets `required` and `aria-required`, and shows an asterisk that screen
53
+ readers skip.
@@ -26,14 +26,14 @@
26
26
 
27
27
  <Story name="Playground">
28
28
  {#snippet template(args)}
29
- <div class="max-w-sm p-6">
29
+ <div class="p-spacing-300 max-w-sm">
30
30
  <Textfield {...args} />
31
31
  </div>
32
32
  {/snippet}
33
33
  </Story>
34
34
 
35
35
  <Story name="With Supporting Text" asChild>
36
- <div class="max-w-sm p-6">
36
+ <div class="p-spacing-300 max-w-sm">
37
37
  <Textfield label="Email">
38
38
  {#snippet supportingText()}
39
39
  We'll never share your email.
@@ -43,7 +43,7 @@
43
43
  </Story>
44
44
 
45
45
  <Story name="With Icons" asChild>
46
- <div class="max-w-sm p-6">
46
+ <div class="p-spacing-300 max-w-sm">
47
47
  <Textfield
48
48
  label="Search"
49
49
  leadingIconProps={{ name: 'search' }}
@@ -53,7 +53,7 @@
53
53
  </Story>
54
54
 
55
55
  <Story name="Character Limit" asChild>
56
- <div class="max-w-sm p-6">
56
+ <div class="p-spacing-300 max-w-sm">
57
57
  <Textfield label="Bio" characterLimit={80} value="">
58
58
  {#snippet supportingText()}
59
59
  Keep it short.
@@ -63,7 +63,7 @@
63
63
  </Story>
64
64
 
65
65
  <Story name="States" asChild>
66
- <div class="flex max-w-sm flex-col gap-6 p-6">
66
+ <div class="gap-spacing-300 p-spacing-300 flex max-w-sm flex-col">
67
67
  <Textfield label="Default" />
68
68
  <Textfield label="Required" required />
69
69
  <Textfield label="Error" error value="Invalid value">
@@ -76,7 +76,7 @@
76
76
  </Story>
77
77
 
78
78
  <Story name="Outlined" asChild>
79
- <div class="flex max-w-sm flex-col gap-6 p-6">
79
+ <div class="gap-spacing-300 p-spacing-300 flex max-w-sm flex-col">
80
80
  <Textfield variant="outlined" label="Default" />
81
81
  <Textfield variant="outlined" label="With value" value="Hello" />
82
82
  <Textfield variant="outlined" label="Required" required />
@@ -70,6 +70,7 @@ Text fields allow users to enter and edit text.
70
70
  {@render trailingIcon()}
71
71
  {:else if trailingIconProps}
72
72
  <ButtonIcon
73
+ variant="standard"
73
74
  type="button"
74
75
  onclick={trailingOnClick}
75
76
  class={cls.trailingIcon()}