@noxlovette/material 0.6.0 → 0.7.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 (176) hide show
  1. package/claude-skill/material-design/SKILL.md +5 -2
  2. package/claude-skill/material-design/references/accessibility-checklist.md +5 -0
  3. package/claude-skill/material-design/references/component-patterns.md +24 -3
  4. package/claude-skill/material-design/references/motion-guide.md +53 -11
  5. package/claude-skill/material-design/references/tokens-and-styles.md +4 -0
  6. package/dist/animation/TransitionPatterns.stories.svelte +50 -0
  7. package/dist/animation/containerTransform.js +10 -4
  8. package/dist/animation/reducedMotion.d.ts +2 -0
  9. package/dist/animation/reducedMotion.js +2 -0
  10. package/dist/animation/sharedAxisTransition.d.ts +20 -3
  11. package/dist/animation/sharedAxisTransition.js +67 -8
  12. package/dist/attachments/drag.d.ts +52 -0
  13. package/dist/attachments/drag.js +217 -0
  14. package/dist/attachments/index.d.ts +1 -0
  15. package/dist/attachments/index.js +1 -0
  16. package/dist/components/badge/theme.js +1 -1
  17. package/dist/components/buttons/button-group/theme.d.ts +1 -1
  18. package/dist/components/buttons/button-group/theme.js +1 -1
  19. package/dist/components/buttons/connected/theme.d.ts +1 -1
  20. package/dist/components/buttons/connected/theme.js +1 -1
  21. package/dist/components/buttons/split-button/SplitButton.svelte +1 -1
  22. package/dist/components/buttons/split-button/theme.d.ts +1 -1
  23. package/dist/components/buttons/split-button/theme.js +1 -1
  24. package/dist/components/buttons/theme.d.ts +1 -1
  25. package/dist/components/buttons/theme.js +3 -3
  26. package/dist/components/cards/theme.js +1 -1
  27. package/dist/components/chips/Chip.mdx +24 -11
  28. package/dist/components/chips/Chip.stories.svelte +10 -0
  29. package/dist/components/chips/Chip.svelte +58 -18
  30. package/dist/components/chips/Chip.svelte.d.ts +3 -1
  31. package/dist/components/chips/ChipGroup.stories.svelte +73 -0
  32. package/dist/components/chips/ChipGroup.stories.svelte.d.ts +4 -0
  33. package/dist/components/chips/ChipGroup.svelte +242 -0
  34. package/dist/components/chips/ChipGroup.svelte.d.ts +33 -0
  35. package/dist/components/chips/index.d.ts +1 -0
  36. package/dist/components/chips/index.js +1 -0
  37. package/dist/components/chips/theme.d.ts +183 -22
  38. package/dist/components/chips/theme.js +117 -26
  39. package/dist/components/chips/types.d.ts +36 -2
  40. package/dist/components/containers/action-rail/theme.d.ts +1 -1
  41. package/dist/components/containers/action-rail/theme.js +2 -2
  42. package/dist/components/containers/app/theme.d.ts +2 -2
  43. package/dist/components/containers/app/theme.js +2 -2
  44. package/dist/components/containers/bottom-sheet/BottomSheet.stories.svelte +1 -1
  45. package/dist/components/containers/bottom-sheet/theme.d.ts +1 -1
  46. package/dist/components/containers/bottom-sheet/theme.js +2 -2
  47. package/dist/components/containers/carousel/theme.d.ts +1 -1
  48. package/dist/components/containers/carousel/theme.js +1 -1
  49. package/dist/components/containers/context-menu/theme.d.ts +1 -1
  50. package/dist/components/containers/context-menu/theme.js +2 -2
  51. package/dist/components/containers/dialogue/Dialogue.svelte +1 -1
  52. package/dist/components/containers/dialogue/theme.d.ts +1 -1
  53. package/dist/components/containers/dialogue/theme.js +3 -3
  54. package/dist/components/containers/dialogue/types.d.ts +1 -1
  55. package/dist/components/containers/divider/theme.d.ts +1 -1
  56. package/dist/components/containers/divider/theme.js +1 -1
  57. package/dist/components/containers/link-preview/theme.d.ts +1 -1
  58. package/dist/components/containers/link-preview/theme.js +2 -2
  59. package/dist/components/containers/list/List.svelte +8 -2
  60. package/dist/components/containers/list/ListItem.mdx +6 -0
  61. package/dist/components/containers/list/ListItem.svelte +77 -2
  62. package/dist/components/containers/list/context.d.ts +14 -0
  63. package/dist/components/containers/list/theme.d.ts +7 -1
  64. package/dist/components/containers/list/theme.js +11 -3
  65. package/dist/components/containers/menu/Menu.svelte +1 -1
  66. package/dist/components/containers/menu/MenuSub.svelte +1 -1
  67. package/dist/components/containers/menu/theme.d.ts +1 -1
  68. package/dist/components/containers/menu/theme.js +1 -1
  69. package/dist/components/containers/pane/DraggablePane.svelte +25 -63
  70. package/dist/components/containers/pane/Pane.svelte +1 -1
  71. package/dist/components/containers/pane/theme.d.ts +1 -1
  72. package/dist/components/containers/pane/theme.js +35 -9
  73. package/dist/components/containers/popover/theme.d.ts +4 -4
  74. package/dist/components/containers/popover/theme.js +2 -2
  75. package/dist/components/containers/scroll-area/theme.js +1 -1
  76. package/dist/components/containers/side-sheet/theme.d.ts +1 -1
  77. package/dist/components/containers/side-sheet/theme.js +1 -1
  78. package/dist/components/date/theme.d.ts +7 -7
  79. package/dist/components/date/theme.js +1 -1
  80. package/dist/components/forms/checkbox/theme.d.ts +1 -1
  81. package/dist/components/forms/checkbox/theme.js +2 -2
  82. package/dist/components/forms/command/theme.d.ts +4 -4
  83. package/dist/components/forms/command/theme.js +1 -1
  84. package/dist/components/forms/pin/theme.d.ts +1 -1
  85. package/dist/components/forms/pin/theme.js +1 -1
  86. package/dist/components/forms/radio-group/theme.d.ts +1 -1
  87. package/dist/components/forms/radio-group/theme.js +1 -1
  88. package/dist/components/forms/search/theme.d.ts +1 -1
  89. package/dist/components/forms/search/theme.js +1 -1
  90. package/dist/components/forms/select/Select.svelte +2 -2
  91. package/dist/components/forms/select/theme.d.ts +1 -1
  92. package/dist/components/forms/select/theme.js +2 -2
  93. package/dist/components/forms/slider/theme.d.ts +1 -1
  94. package/dist/components/forms/slider/theme.js +1 -1
  95. package/dist/components/forms/switch/theme.d.ts +1 -1
  96. package/dist/components/forms/switch/theme.js +1 -1
  97. package/dist/components/forms/textfield/theme.d.ts +1 -1
  98. package/dist/components/forms/textfield/theme.js +1 -1
  99. package/dist/components/forms/toggle-group/theme.d.ts +1 -1
  100. package/dist/components/forms/toggle-group/theme.js +1 -1
  101. package/dist/components/forms/tooltip/theme.d.ts +1 -1
  102. package/dist/components/forms/tooltip/theme.js +2 -2
  103. package/dist/components/misc/Avatar.stories.svelte +56 -7
  104. package/dist/components/misc/Avatar.stories.svelte.d.ts +2 -17
  105. package/dist/components/misc/Avatar.svelte +82 -8
  106. package/dist/components/misc/Avatar.svelte.d.ts +5 -0
  107. package/dist/components/misc/ThemeSwitcher.svelte +1 -1
  108. package/dist/components/misc/theme.d.ts +6 -0
  109. package/dist/components/misc/theme.js +6 -4
  110. package/dist/components/misc/types.d.ts +17 -1
  111. package/dist/components/nav/appbar/theme.d.ts +1 -1
  112. package/dist/components/nav/appbar/theme.js +3 -2
  113. package/dist/components/nav/breadcrumb/theme.d.ts +1 -1
  114. package/dist/components/nav/breadcrumb/theme.js +1 -1
  115. package/dist/components/nav/currentRoute.d.ts +11 -0
  116. package/dist/components/nav/currentRoute.js +28 -0
  117. package/dist/components/nav/navbar/NavbarItem.svelte +3 -6
  118. package/dist/components/nav/navbar/theme.d.ts +3 -3
  119. package/dist/components/nav/navbar/theme.js +2 -2
  120. package/dist/components/nav/rail/Rail.mdx +65 -23
  121. package/dist/components/nav/rail/Rail.stories.svelte +24 -4
  122. package/dist/components/nav/rail/Rail.stories.svelte.d.ts +2 -17
  123. package/dist/components/nav/rail/Rail.svelte +165 -48
  124. package/dist/components/nav/rail/Rail.svelte.d.ts +6 -3
  125. package/dist/components/nav/rail/RailItem.svelte +122 -65
  126. package/dist/components/nav/rail/RailItem.svelte.d.ts +3 -2
  127. package/dist/components/nav/rail/theme.d.ts +70 -85
  128. package/dist/components/nav/rail/theme.js +80 -67
  129. package/dist/components/nav/rail/types.d.ts +4 -0
  130. package/dist/components/nav/tabs/Tabs.stories.svelte +2 -1
  131. package/dist/components/nav/tabs/theme.d.ts +1 -1
  132. package/dist/components/nav/tabs/theme.js +1 -1
  133. package/dist/components/pill/theme.d.ts +1 -1
  134. package/dist/components/pill/theme.js +1 -1
  135. package/dist/components/progress/LinearProgress.svelte +1 -1
  136. package/dist/components/snackbar/theme.d.ts +1 -1
  137. package/dist/components/snackbar/theme.js +2 -2
  138. package/dist/components/table/theme.d.ts +1 -1
  139. package/dist/components/table/theme.js +1 -1
  140. package/dist/components/time/TimeField.svelte +1 -1
  141. package/dist/components/time/theme.d.ts +1 -1
  142. package/dist/components/time/theme.js +1 -1
  143. package/dist/components/toolbar/theme.d.ts +1 -1
  144. package/dist/components/toolbar/theme.js +2 -2
  145. package/dist/components/typography/body/theme.d.ts +1 -1
  146. package/dist/components/typography/body/theme.js +1 -1
  147. package/dist/components/typography/display/theme.d.ts +1 -1
  148. package/dist/components/typography/display/theme.js +1 -1
  149. package/dist/components/typography/headline/theme.d.ts +1 -1
  150. package/dist/components/typography/headline/theme.js +1 -1
  151. package/dist/components/typography/kbd/theme.d.ts +1 -1
  152. package/dist/components/typography/kbd/theme.js +1 -1
  153. package/dist/components/typography/label/theme.js +1 -1
  154. package/dist/components/typography/title/theme.d.ts +1 -1
  155. package/dist/components/typography/title/theme.js +1 -1
  156. package/dist/index.css +3 -0
  157. package/dist/styles/components.css +50 -0
  158. package/dist/styles/icon.css +13 -0
  159. package/dist/styles/layers.css +33 -0
  160. package/dist/utils/icon/Icon.stories.svelte +97 -0
  161. package/dist/utils/icon/Icon.stories.svelte.d.ts +4 -0
  162. package/dist/utils/icon/Icon.svelte +101 -12
  163. package/dist/utils/icon/Icon.svelte.d.ts +9 -0
  164. package/dist/utils/icon/MaterialSymbolsProvider.svelte +11 -4
  165. package/dist/utils/icon/base-icons.d.ts +1 -1
  166. package/dist/utils/icon/base-icons.js +1 -1
  167. package/dist/utils/icon/icon-names.d.ts +2 -0
  168. package/dist/utils/icon/icon-names.js +2 -0
  169. package/dist/utils/icon/swap.d.ts +6 -0
  170. package/dist/utils/icon/swap.js +45 -0
  171. package/dist/utils/icon/theme.d.ts +1 -1
  172. package/dist/utils/icon/theme.js +1 -1
  173. package/dist/utils/icon/types.d.ts +59 -7
  174. package/dist/utils/tv.d.ts +5 -0
  175. package/dist/utils/tv.js +20 -0
  176. package/package.json +2 -1
@@ -1,10 +1,35 @@
1
- import { tv } from 'tailwind-variants';
1
+ import { tv } from '../../../utils/tv.js';
2
+ /*
3
+ M3 Expressive navigation rail (https://m3.material.io/components/navigation-rail/specs):
4
+ - Container: `surface-container`, the spec's optional container role, which is this library's
5
+ window colour (App), so the rail reads as part of the window beside `surface` panes rather
6
+ than as a strip. The modal rail's token is `surface-container` too.
7
+ - Collapsed: 96dp wide, no elevation or shape. Destinations sit 4dp apart.
8
+ - Expanded: hugs its widest destination between 220 and 360dp, destinations touching. Standard
9
+ (lg+, pushes content) keeps the collapsed container; modal (md, over a scrim) adds
10
+ elevation 3 and 16dp trailing corners.
11
+ - 44dp above the first element; at least 40dp between the menu/FAB header and the destinations.
12
+ - Header elements sit in a 56dp column 20dp from the leading edge, the same column as the
13
+ destinations' active indicators, so nothing shifts sideways when the rail expands.
14
+ Expanding is one spatial spring on a progress value, 0 collapsed to 1 expanded, which
15
+ Rail.svelte writes to `--rail-p` (after Compose's WideNavigationRail). The width, the gap
16
+ between destinations, the modal corners and each destination's geometry all follow it, so
17
+ nothing here transitions spatially on its own.
18
+ */
2
19
  export const rail = tv({
3
20
  slots: {
4
- base: 'py-spacing-600 hidden bg-md-sys-color-surface-container z-40 flex-col md:flex gap-spacing-300 transition-[width,padding,background-color] md-sys-motion-spatial',
5
- items: 'flex flex-col gap-spacing-150 pt-spacing-300 scrollbar-none w-full overflow-x-hidden',
6
- ghost: 'hidden md:block shrink-0 transition-[width] md-sys-motion-spatial',
7
- scrim: 'hidden md:block lg:hidden inset-spacing-0'
21
+ // Modal corners grow with the progress (0 → 16dp); the standard rail stays square.
22
+ base: 'rounded-r-[calc(var(--radius-lg)_*_var(--rail-p,0))] lg:rounded-r-none hidden md:flex flex-col z-layer-rail overflow-hidden pt-(--md-comp-nav-rail-collapsed-top-space) pb-spacing-250 bg-md-sys-color-surface-container transition-[box-shadow] md-sys-motion-effects',
23
+ // Always start-aligned: collapsed, the content box is exactly the 56dp column, so this is
24
+ // centred anyway. Centring it would fling the menu and FAB to the middle of the still-wide
25
+ // rail the moment a collapse starts.
26
+ header: 'flex flex-col items-start gap-spacing-300 px-spacing-250',
27
+ menu: 'flex w-spacing-700 justify-center',
28
+ nav: 'flex min-h-spacing-0 w-full flex-1 flex-col',
29
+ // 4dp between destinations collapsed, 0 expanded, following the progress.
30
+ items: 'flex w-full flex-col overflow-x-hidden overflow-y-auto [scrollbar-width:none] gap-[calc(var(--spacing-spacing-50)_*_(1_-_var(--rail-p,0)))]',
31
+ ghost: 'hidden md:block shrink-0 w-(--md-comp-nav-rail-collapsed-container-width) lg:w-(--rail-width)',
32
+ scrim: 'hidden md:block lg:hidden inset-spacing-0 z-layer-rail bg-md-sys-color-scrim/32 transition-opacity md-sys-motion-effects'
8
33
  },
9
34
  variants: {
10
35
  anchor: {
@@ -19,15 +44,19 @@ export const rail = tv({
19
44
  },
20
45
  expanded: {
21
46
  true: {
22
- base: 'pl-spacing-450 w-60 items-start bg-md-sys-color-surface-container-highest lg:bg-md-sys-color-surface-container rounded-r-lg',
23
- ghost: 'w-24 lg:w-60'
47
+ base: 'w-max min-w-(--md-comp-nav-rail-expanded-container-width-minimum) max-w-(--md-comp-nav-rail-expanded-container-width-maximum) pt-(--md-comp-nav-rail-expanded-top-space) shadow-elevation-3 lg:shadow-elevation-0',
48
+ scrim: 'opacity-100'
24
49
  },
25
50
  false: {
26
- base: 'w-24 items-center',
27
- items: 'items-center',
28
- ghost: 'w-24'
51
+ base: 'w-(--md-comp-nav-rail-collapsed-container-width)',
52
+ scrim: 'pointer-events-none opacity-0'
29
53
  }
30
54
  },
55
+ /* Header space: only when there is a header to keep the destinations away from. */
56
+ header: {
57
+ true: { nav: 'mt-spacing-500' },
58
+ false: {}
59
+ },
31
60
  rounded: {
32
61
  true: {
33
62
  base: 'rounded-xl'
@@ -36,81 +65,65 @@ export const rail = tv({
36
65
  }
37
66
  },
38
67
  defaultVariants: {
39
- anchor: 'viewport'
68
+ anchor: 'viewport',
69
+ header: false
40
70
  }
41
71
  });
72
+ /*
73
+ One destination. The link spans the rail's full width (M3: the target area always does); the
74
+ active indicator inside it hugs its contents. Collapsed, the indicator is 56×32 around the icon
75
+ with the label 4dp below; expanded, it's a 56dp pill around icon and label, 16dp padding, 8dp
76
+ between them. State layers are `on-secondary-container` at 8% hover and 10% focus/press, drawn
77
+ on the indicator but triggered anywhere on the link. Selecting a destination grows its fill
78
+ out from the centre (M3's active indicator, after Compose's NavigationRailItem).
79
+
80
+ Geometry isn't here: it interpolates on the rail's expand progress, in RailItem.svelte's
81
+ <style>. `layout` is which side of the halfway point that progress is on, and decides only
82
+ what may switch while the label is invisible: its typescale and colour.
83
+ */
42
84
  export const railElement = tv({
43
85
  slots: {
44
- base: 'group relative z-30 flex w-full transition-all md-sys-motion-spatial',
45
- content: 'flex items-center min-w-spacing-0 rounded-full gap-spacing-25 py-spacing-75 transition-all md-sys-motion-spatial',
46
- iconContainer: 'relative rounded-full items-center justify-center inline-flex transition-all md-sys-motion-spatial',
47
- icon: 'text-[24px] transition-all md-sys-motion-spatial',
48
- label: 'transition-all md-sys-motion-spatial whitespace-nowrap'
86
+ base: 'group rail-item relative flex w-full px-spacing-250 outline-none',
87
+ // As wide as the expanded pill, so an expanded rail (w-max) hugs its widest destination.
88
+ sizer: 'relative h-full shrink-0 pl-spacing-600 pr-spacing-200',
89
+ measure: 'invisible md-sys-typescale-label-large whitespace-nowrap',
90
+ indicator: 'rail-indicator absolute left-spacing-0 isolate rounded-full state-layer before:rounded-full',
91
+ // The selected fill, under the state layer. It grows from the indicator's centre to its
92
+ // edges on select and shrinks back on deselect, on RailItem's --rail-sel spring.
93
+ fill: 'rail-fill absolute -z-10 rounded-full bg-md-sys-color-secondary-container',
94
+ icon: 'rail-icon absolute left-spacing-200 inline-flex',
95
+ label: 'rail-label absolute whitespace-nowrap transition-colors md-sys-motion-fast-effects'
49
96
  },
50
97
  variants: {
51
98
  active: {
52
99
  true: {
53
- content: 'text-md-sys-color-on-secondary-container',
54
- label: 'font-bold',
55
- iconContainer: 'group-hover:text-md-sys-color-secondary bg-md-sys-color-secondary-container'
100
+ icon: 'text-md-sys-color-on-secondary-container'
56
101
  },
57
102
  false: {
58
- content: 'text-md-sys-color-on-surface-variant',
59
- iconContainer: ''
103
+ icon: 'text-md-sys-color-on-surface-variant',
104
+ label: 'text-md-sys-color-on-surface-variant'
60
105
  }
61
106
  },
62
- expanded: {
63
- true: {
64
- base: 'justify-start',
65
- content: 'p-spacing-200 gap-spacing-150',
66
- iconContainer: '',
67
- label: 'md-sys-typescale-label-large'
68
- },
69
- false: {
70
- base: 'text-center justify-center',
71
- content: 'flex-col',
72
- iconContainer: 'py-spacing-75 px-spacing-200',
73
- label: 'md-sys-typescale-label-medium'
74
- }
107
+ layout: {
108
+ top: { label: 'md-sys-typescale-label-medium' },
109
+ start: { label: 'md-sys-typescale-label-large' }
75
110
  },
76
111
  disabled: {
77
112
  true: {
78
113
  base: 'cursor-not-allowed opacity-38'
79
- }
80
- },
81
- mobile: {
82
- true: {
83
- base: 'flex-1 flex-col items-center justify-center h-full min-w-spacing-0 max-w-[168px]',
84
- content: 'py-spacing-0 gap-spacing-50 items-center justify-center w-full',
85
- iconContainer: 'h-spacing-400 w-spacing-800 py-spacing-0 px-spacing-0',
86
- icon: 'size-spacing-300',
87
- label: 'truncate w-full text-center'
88
114
  },
89
- false: {}
115
+ false: {
116
+ // State layer and focus ring on the indicator, triggered by the full-width link.
117
+ indicator: 'group-hover:before:bg-md-sys-color-on-secondary-container/8 group-focus-visible:before:bg-md-sys-color-on-secondary-container/10 group-active:before:bg-md-sys-color-on-secondary-container/10 group-focus-visible:outline-3 group-focus-visible:outline-offset-2 group-focus-visible:outline-md-sys-color-secondary'
118
+ }
90
119
  }
91
120
  },
92
121
  compoundVariants: [
93
- {
94
- expanded: true,
95
- active: true,
96
- class: {
97
- iconContainer: 'bg-transparent',
98
- content: 'bg-md-sys-color-secondary-container'
99
- }
100
- },
101
- {
102
- expanded: true,
103
- active: false,
104
- class: {
105
- content: 'group-hover:bg-md-sys-color-surface-variant'
106
- }
107
- },
108
- {
109
- expanded: false,
110
- active: false,
111
- class: {
112
- iconContainer: 'group-hover:bg-md-sys-color-surface-variant'
113
- }
114
- }
115
- ]
122
+ { active: true, layout: 'top', class: { label: 'text-md-sys-color-secondary' } },
123
+ { active: true, layout: 'start', class: { label: 'text-md-sys-color-on-secondary-container' } }
124
+ ],
125
+ defaultVariants: {
126
+ layout: 'top',
127
+ disabled: false
128
+ }
116
129
  });
@@ -41,6 +41,10 @@ export type RailProps = RailVariants & HTMLAttributes<HTMLDivElement> & {
41
41
  anchor?: RailVariants['anchor'];
42
42
  /** Top offset in px for viewport/parent anchor (e.g. height of a top bar). Default 0. */
43
43
  railTop?: number;
44
+ /** Tooltip and accessible name of the toggle while collapsed. Default 'Expand navigation'. */
45
+ expandLabel?: string;
46
+ /** Tooltip and accessible name of the toggle while expanded. Default 'Collapse navigation'. */
47
+ collapseLabel?: string;
44
48
  /**
45
49
  * @deprecated No effect: Rail no longer renders a navigation bar below `md`. Mount a `Navbar`
46
50
  * next to it for small windows.
@@ -3,6 +3,7 @@
3
3
  import TabHolder from './TabHolder.svelte';
4
4
  import TabContent from './TabContent.svelte';
5
5
  import Body from '../../typography/body/Body.svelte';
6
+ import type { TabProps } from './types.js';
6
7
 
7
8
  const { Story } = defineMeta({
8
9
  title: 'Navigation/Tabs',
@@ -27,7 +28,7 @@
27
28
  { value: 'flights', name: 'Flights', iconProps: { name: 'flight' } },
28
29
  { value: 'trips', name: 'Trips', iconProps: { name: 'luggage' } },
29
30
  { value: 'explore', name: 'Explore', iconProps: { name: 'explore' } }
30
- ];
31
+ ] satisfies TabProps[];
31
32
  </script>
32
33
 
33
34
  <Story name="Playground">
@@ -1,4 +1,4 @@
1
- import { type VariantProps } from 'tailwind-variants';
1
+ import { type VariantProps } from '../../../utils/tv.js';
2
2
  export type TabVariants = VariantProps<typeof tab>;
3
3
  export type TabHolderVariants = VariantProps<typeof tabHolder>;
4
4
  export declare const tabHolder: import("tailwind-variants").TVReturnType<{
@@ -1,4 +1,4 @@
1
- import { tv } from 'tailwind-variants';
1
+ import { tv } from '../../../utils/tv.js';
2
2
  export const tabHolder = tv({
3
3
  slots: {
4
4
  base: 'relative flex w-full bg-md-sys-color-surface',
@@ -1,4 +1,4 @@
1
- import { type VariantProps } from 'tailwind-variants';
1
+ import { type VariantProps } from '../../utils/tv.js';
2
2
  export type PillVariants = VariantProps<typeof pill>;
3
3
  export declare const pill: import("tailwind-variants").TVReturnType<{
4
4
  variant: {
@@ -1,4 +1,4 @@
1
- import { tv } from 'tailwind-variants';
1
+ import { tv } from '../../utils/tv.js';
2
2
  export const pill = tv({
3
3
  base: 'relative inline-flex items-center justify-center px-spacing-200 py-spacing-75 md-sys-typescale-label-large select-none rounded-full whitespace-nowrap',
4
4
  variants: {
@@ -11,7 +11,7 @@ The track always keeps a gap either side of the bar.
11
11
  -->
12
12
  <script lang="ts">
13
13
  import clsx from 'clsx';
14
- import { tv } from 'tailwind-variants';
14
+ import { tv } from '../../utils/tv.js';
15
15
  import { Progress } from 'bits-ui';
16
16
  import { animate, type AnimationPlaybackControls } from 'motion';
17
17
  import { springTokens, springTransition } from '../../animation/spring.js';
@@ -1,4 +1,4 @@
1
- import { type VariantProps } from 'tailwind-variants';
1
+ import { type VariantProps } from '../../utils/tv.js';
2
2
  export type SnackBarVariants = VariantProps<typeof snackbar>;
3
3
  export declare const snackbar: import("tailwind-variants").TVReturnType<{
4
4
  fixed: {
@@ -1,7 +1,7 @@
1
- import { tv } from 'tailwind-variants';
1
+ import { tv } from '../../utils/tv.js';
2
2
  export const snackbar = tv({
3
3
  slots: {
4
- base: 'rounded-xs relative min-h-spacing-600 md:max-h-17 flex z-[100] items-center bg-md-sys-color-inverse-surface shadow-elevation-3 pr-spacing-100 pl-spacing-200 justify-between h-max py-spacing-150',
4
+ base: 'rounded-xs relative min-h-spacing-600 md:max-h-17 flex z-layer-snackbar items-center bg-md-sys-color-inverse-surface shadow-elevation-3 pr-spacing-100 pl-spacing-200 justify-between h-max py-spacing-150',
5
5
  label: 'text-md-sys-color-inverse-primary md-sys-typescale-label-large px-spacing-150 cursor-pointer ',
6
6
  actionWrapper: 'flex items-center z-40',
7
7
  supportingText: 'text-md-sys-color-inverse-on-surface md-sys-typescale-body-medium max-w-[60ch] ',
@@ -1,4 +1,4 @@
1
- import { type VariantProps } from 'tailwind-variants';
1
+ import { type VariantProps } from '../../utils/tv.js';
2
2
  export declare const table: import("tailwind-variants").TVReturnType<{
3
3
  hoverable: {
4
4
  true: {
@@ -1,4 +1,4 @@
1
- import { tv } from 'tailwind-variants';
1
+ import { tv } from '../../utils/tv.js';
2
2
  export const table = tv({
3
3
  slots: {
4
4
  root: 'w-full border-collapse text-left md-sys-typescale-body-medium text-md-sys-color-on-surface',
@@ -92,7 +92,7 @@ It combines a Material 3 Textfield with a TimepickerInput for intuitive time sel
92
92
  align="start"
93
93
  sideOffset={12}
94
94
  collisionPadding={8}
95
- class="z-100"
95
+ class="z-layer-popup"
96
96
  >
97
97
  {#snippet child({ wrapperProps, props, open })}
98
98
  <div {...wrapperProps}>
@@ -1,4 +1,4 @@
1
- import { type VariantProps } from 'tailwind-variants';
1
+ import { type VariantProps } from '../../utils/tv.js';
2
2
  export type TimepickerVariants = VariantProps<typeof timepicker>;
3
3
  export declare const timepicker: import("tailwind-variants").TVReturnType<{
4
4
  [key: string]: {
@@ -1,4 +1,4 @@
1
- import { tv } from 'tailwind-variants';
1
+ import { tv } from '../../utils/tv.js';
2
2
  export const timepicker = tv({
3
3
  slots: {
4
4
  base: 'bg-md-sys-color-surface-container-high shadow-elevation-3 rounded-xl max-w-fit p-spacing-300 group relative',
@@ -1,4 +1,4 @@
1
- import { type VariantProps } from 'tailwind-variants';
1
+ import { type VariantProps } from '../../utils/tv.js';
2
2
  export type ToolbarVariants = VariantProps<typeof toolbar>;
3
3
  export type ToolbarButtonVariants = VariantProps<typeof toolbarButton>;
4
4
  export type ToolbarGroupItemVariants = VariantProps<typeof toolbarGroupItem>;
@@ -1,4 +1,4 @@
1
- import { tv } from 'tailwind-variants';
1
+ import { tv } from '../../utils/tv.js';
2
2
  export const toolbar = tv({
3
3
  slots: {
4
4
  // Layout/position only — no paint. Holds one implicit `segment` by default, or,
@@ -33,7 +33,7 @@ export const toolbar = tv({
33
33
  },
34
34
  variant: {
35
35
  floating: { root: 'inline-flex', segment: 'rounded-full shadow-elevation-1' },
36
- docked: { root: 'fixed left-spacing-0 z-10 w-full', segment: 'w-full rounded-none' }
36
+ docked: { root: 'fixed left-spacing-0 z-layer-bar w-full', segment: 'w-full rounded-none' }
37
37
  },
38
38
  color: {
39
39
  standard: { segment: 'bg-md-sys-color-surface-container' },
@@ -1,4 +1,4 @@
1
- import { type VariantProps } from 'tailwind-variants';
1
+ import { type VariantProps } from '../../../utils/tv.js';
2
2
  export type BodyVariants = VariantProps<typeof body>;
3
3
  export declare const body: import("tailwind-variants").TVReturnType<{
4
4
  emphasized: {
@@ -1,4 +1,4 @@
1
- import { tv } from 'tailwind-variants';
1
+ import { tv } from '../../../utils/tv.js';
2
2
  export const body = tv({
3
3
  base: '',
4
4
  variants: {
@@ -1,4 +1,4 @@
1
- import { type VariantProps } from 'tailwind-variants';
1
+ import { type VariantProps } from '../../../utils/tv.js';
2
2
  export type DisplayVariants = VariantProps<typeof display>;
3
3
  export declare const display: import("tailwind-variants").TVReturnType<{
4
4
  emphasized: {
@@ -1,4 +1,4 @@
1
- import { tv } from 'tailwind-variants';
1
+ import { tv } from '../../../utils/tv.js';
2
2
  export const display = tv({
3
3
  base: '',
4
4
  variants: {
@@ -1,4 +1,4 @@
1
- import { type VariantProps } from 'tailwind-variants';
1
+ import { type VariantProps } from '../../../utils/tv.js';
2
2
  export type HeadlineVariants = VariantProps<typeof headline>;
3
3
  export declare const headline: import("tailwind-variants").TVReturnType<{
4
4
  emphasized: {
@@ -1,4 +1,4 @@
1
- import { tv } from 'tailwind-variants';
1
+ import { tv } from '../../../utils/tv.js';
2
2
  export const headline = tv({
3
3
  base: '',
4
4
  variants: {
@@ -1,4 +1,4 @@
1
- import { type VariantProps } from 'tailwind-variants';
1
+ import { type VariantProps } from '../../../utils/tv.js';
2
2
  export type KbdVariants = VariantProps<typeof kbd>;
3
3
  export declare const kbd: import("tailwind-variants").TVReturnType<{
4
4
  position: {
@@ -1,4 +1,4 @@
1
- import { tv } from 'tailwind-variants';
1
+ import { tv } from '../../../utils/tv.js';
2
2
  export const kbd = tv({
3
3
  base: 'md-sys-typescale-emphasized-label-small text-md-sys-color-on-surface-variant',
4
4
  variants: {
@@ -1,4 +1,4 @@
1
- import { tv } from 'tailwind-variants';
1
+ import { tv } from '../../../utils/tv.js';
2
2
  export const label = tv({
3
3
  base: '',
4
4
  variants: {
@@ -1,4 +1,4 @@
1
- import { type VariantProps } from 'tailwind-variants';
1
+ import { type VariantProps } from '../../../utils/tv.js';
2
2
  export type TitleVariants = VariantProps<typeof title>;
3
3
  export declare const title: import("tailwind-variants").TVReturnType<{
4
4
  emphasized: {
@@ -1,4 +1,4 @@
1
- import { tv } from 'tailwind-variants';
1
+ import { tv } from '../../../utils/tv.js';
2
2
  export const title = tv({
3
3
  base: '',
4
4
  variants: {
package/dist/index.css CHANGED
@@ -3,5 +3,8 @@
3
3
  @import './styles/motion.css';
4
4
  @import './styles/rounding.css';
5
5
  @import './styles/elevation.css';
6
+ @import './styles/layers.css';
7
+ @import './styles/components.css';
8
+ @import './styles/icon.css';
6
9
  @import './styles/prose.css';
7
10
  @import './styles/component.css';
@@ -0,0 +1,50 @@
1
+ /*
2
+ * M3 component dimensions that aren't on the spacing scale (issue #30). Each property is named
3
+ * after its token in the M3 token database, md.comp.<component>.<…> with dots as dashes, and
4
+ * used as `w-(--md-comp-…)`. A dimension that lands on the spacing grid uses the spacing token
5
+ * instead (the rail's 4dp item gap is `gap-spacing-50`, not a property here).
6
+ */
7
+ :root {
8
+ /* Navigation rail: https://m3.material.io/components/navigation-rail/specs */
9
+ --md-comp-nav-rail-collapsed-container-width: 6rem; /* 96dp */
10
+ --md-comp-nav-rail-collapsed-top-space: 2.75rem; /* 44dp */
11
+ --md-comp-nav-rail-expanded-top-space: 2.75rem; /* 44dp */
12
+ --md-comp-nav-rail-expanded-container-width-minimum: 13.75rem; /* 220dp */
13
+ --md-comp-nav-rail-expanded-container-width-maximum: 22.5rem; /* 360dp */
14
+ }
15
+
16
+ /*
17
+ * How far the page's content starts from the leading edge to clear a viewport-anchored Rail.
18
+ * The Rail writes --md-rail-width (its drawn width, every frame while it springs) on <html>;
19
+ * with no Rail mounted it's unset and the inset is 0. On medium windows the expanded rail is
20
+ * modal and overlays the content, so the inset stays at the collapsed width; from lg the
21
+ * standard rail pushes the content, so the inset follows its width. App's shell pads by it;
22
+ * fixed surfaces that span the window (AppBar) start at it. Custom layouts: `ps-(--md-rail-inset)`.
23
+ */
24
+ :root {
25
+ --md-rail-inset: 0px;
26
+ }
27
+ @media (width >= 48rem) {
28
+ :root {
29
+ --md-rail-inset: var(--md-rail-rest, 0px);
30
+ }
31
+ }
32
+ @media (width >= 64rem) {
33
+ :root {
34
+ --md-rail-inset: var(--md-rail-width, 0px);
35
+ }
36
+ }
37
+
38
+ /*
39
+ * A page built on App behaves as an app, not a document: scrolling stops at its edges instead of
40
+ * bouncing (macOS/iOS rubber-banding) or chaining into pull-to-refresh, which showed the blank
41
+ * canvas past App's shell. The canvas takes the window colour anyway, for the browsers that
42
+ * still overscroll and the safe areas around it. Scoped to pages with an App, and in the base
43
+ * layer, so a consumer can restore overscroll with one `html { overscroll-behavior: auto }`.
44
+ */
45
+ @layer base {
46
+ :root:has(.md-app) {
47
+ overscroll-behavior: none;
48
+ background-color: var(--color-md-sys-color-surface-container);
49
+ }
50
+ }
@@ -0,0 +1,13 @@
1
+ /*
2
+ Default grade for Icon's `grad="auto"`. Light icons on a dark background look heavier than dark
3
+ ones on a light background (visual bleed), so M3 drops the grade to -25 on dark UI.
4
+ https://m3.material.io/styles/icons/applying-icons#grade
5
+ */
6
+ :root {
7
+ --md-icon-grade: 0;
8
+ }
9
+
10
+ .dark,
11
+ [data-theme^='dark'] {
12
+ --md-icon-grade: -25;
13
+ }
@@ -0,0 +1,33 @@
1
+ /*
2
+ * Stacking layers for surfaces that float over the page. M3 defines no z-index tokens, so the
3
+ * library names its own: every fixed, sticky or portaled component takes one of these instead
4
+ * of a number, so their relative order is set here and nowhere else. Apply with
5
+ * `z-layer-<name>` (z-layer-modal); override the value on :root with `--z-layer-<name>`.
6
+ * Stacking inside one component (a slider thumb over its track) keeps plain z-10/z-20.
7
+ *
8
+ * Order, lowest first:
9
+ * bar Navbar, AppBar, ActionRail, docked Toolbar
10
+ * rail Rail and its tablet scrim (the expanded rail covers the bars)
11
+ * sheet standard (non-modal) BottomSheet
12
+ * fab fixed FABs such as ThemeSwitcher
13
+ * pane DraggablePane and its minimised pill, floating windows over app chrome
14
+ * modal Dialogue scrim and content, a FAB's container-transform surface
15
+ * snackbar stays readable while a dialog is open
16
+ * popup menus, selects, tooltips, popovers, date/time pickers, opened from any layer
17
+ * beneath, including a dialog or a pane
18
+ * Modal sheets are native <dialog>s in the top layer and sit above all of these.
19
+ */
20
+ @theme static {
21
+ --z-layer-bar: 30;
22
+ --z-layer-rail: 40;
23
+ --z-layer-sheet: 40;
24
+ --z-layer-fab: 50;
25
+ --z-layer-pane: 60;
26
+ --z-layer-modal: 70;
27
+ --z-layer-snackbar: 80;
28
+ --z-layer-popup: 90;
29
+ }
30
+
31
+ @utility z-layer-* {
32
+ z-index: --value(--z-layer-*);
33
+ }
@@ -0,0 +1,97 @@
1
+ <script module lang="ts">
2
+ import { defineMeta } from '@storybook/addon-svelte-csf';
3
+ import Body from '../../components/typography/body/Body.svelte';
4
+ import ButtonIcon from '../../components/buttons/ButtonIcon.svelte';
5
+ import Icon from './Icon.svelte';
6
+
7
+ const sizes = ['xs', 'sm', 'md', 'lg', 'xl'] as const;
8
+ const variants = ['rounded', 'outlined', 'sharp'] as const;
9
+
10
+ const { Story } = defineMeta({
11
+ title: 'Utils/Icon',
12
+ component: Icon,
13
+ tags: ['autodocs'],
14
+ argTypes: {
15
+ size: { control: 'select', options: ['inline', ...sizes] },
16
+ variant: { control: 'inline-radio', options: variants },
17
+ fill: { control: { type: 'range', min: 0, max: 1, step: 0.1 } },
18
+ wght: { control: { type: 'range', min: 100, max: 700, step: 100 } },
19
+ grad: { control: 'select', options: ['auto', -50, -25, 0, 100, 200] },
20
+ opsz: { control: { type: 'range', min: 20, max: 48, step: 1 } },
21
+ transition: { control: 'inline-radio', options: ['none', 'fade', 'rotate'] }
22
+ },
23
+ args: { name: 'favorite', size: 'xl', variant: 'rounded', fill: 0, wght: 400, grad: 'auto' }
24
+ });
25
+ </script>
26
+
27
+ <script lang="ts">
28
+ let menuOpen = $state(false);
29
+ let adding = $state(false);
30
+ let playing = $state(false);
31
+ </script>
32
+
33
+ <Story name="Playground" />
34
+
35
+ <!-- Toggle each button, and toggle again mid-flight: the spring reverses from where it is. -->
36
+ <Story name="Transitions" asChild>
37
+ <div class="gap-spacing-300 flex items-center">
38
+ <ButtonIcon
39
+ variant="standard"
40
+ tooltipContent="rotate: menu ↔ menu_open"
41
+ iconProps={{ name: menuOpen ? 'menu_open' : 'menu', transition: 'rotate' }}
42
+ onclick={() => (menuOpen = !menuOpen)}
43
+ />
44
+ <ButtonIcon
45
+ variant="tonal"
46
+ tooltipContent="rotate: add ↔ close"
47
+ iconProps={{ name: adding ? 'close' : 'add', transition: 'rotate' }}
48
+ onclick={() => (adding = !adding)}
49
+ />
50
+ <ButtonIcon
51
+ variant="filled"
52
+ tooltipContent="fade: play_arrow ↔ pause"
53
+ iconProps={{ name: playing ? 'pause' : 'play_arrow', transition: 'fade' }}
54
+ onclick={() => (playing = !playing)}
55
+ />
56
+ </div>
57
+ </Story>
58
+
59
+ <Story name="Styles" asChild>
60
+ <div class="gap-spacing-300 flex">
61
+ {#each variants as variant (variant)}
62
+ <Icon name="home" size="lg" {variant} />
63
+ {/each}
64
+ </div>
65
+ </Story>
66
+
67
+ <Story name="Sizes" asChild>
68
+ <div class="gap-spacing-300 flex items-end">
69
+ {#each sizes as size (size)}
70
+ <Icon name="settings" {size} />
71
+ {/each}
72
+ </div>
73
+ </Story>
74
+
75
+ <Story name="Fill for state" asChild>
76
+ <div class="gap-spacing-300 flex">
77
+ <Icon name="favorite" size="lg" fill={0} />
78
+ <Icon name="favorite" size="lg" fill={1} />
79
+ </div>
80
+ </Story>
81
+
82
+ <Story name="Weights" asChild>
83
+ <div class="gap-spacing-200 flex">
84
+ {#each [200, 300, 400, 500, 600, 700] as wght (wght)}
85
+ <Icon name="search" size="lg" {wght} />
86
+ {/each}
87
+ </div>
88
+ </Story>
89
+
90
+ <Story name="Inline with text" asChild>
91
+ <Body size="large">
92
+ Saved to favorites <Icon name="favorite" size="inline" fill={1} />, shared <Icon
93
+ name="share"
94
+ size="inline"
95
+ />
96
+ </Body>
97
+ </Story>
@@ -0,0 +1,4 @@
1
+ import Icon from './Icon.svelte';
2
+ declare const Icon: import("svelte").Component<Record<string, never>, {}, "">;
3
+ type Icon = ReturnType<typeof Icon>;
4
+ export default Icon;