@marianmeres/stuic 2.66.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/README.md +292 -4
  2. package/dist/README.md +41 -18
  3. package/dist/actions/index.d.ts +1 -0
  4. package/dist/actions/index.js +1 -0
  5. package/dist/actions/popover/README.md +19 -0
  6. package/dist/actions/popover/index.css +6 -9
  7. package/dist/actions/popover/popover.svelte.js +2 -2
  8. package/dist/actions/tooltip/README.md +18 -0
  9. package/dist/actions/tooltip/index.css +5 -8
  10. package/dist/actions/tooltip/tooltip.svelte.js +1 -1
  11. package/dist/actions/typeahead.svelte.d.ts +53 -0
  12. package/dist/actions/typeahead.svelte.js +328 -0
  13. package/dist/base.css +17 -0
  14. package/dist/components/AlertConfirmPrompt/AlertConfirmPrompt.svelte +10 -10
  15. package/dist/components/AlertConfirmPrompt/AlertConfirmPrompt.svelte.d.ts +4 -3
  16. package/dist/components/AlertConfirmPrompt/Current.svelte +15 -18
  17. package/dist/components/AlertConfirmPrompt/Current.svelte.d.ts +4 -3
  18. package/dist/components/AlertConfirmPrompt/acp-icons.js +5 -4
  19. package/dist/components/AlertConfirmPrompt/index.css +66 -0
  20. package/dist/components/AssetsPreview/AssetsPreview.svelte +91 -73
  21. package/dist/components/AssetsPreview/index.css +61 -0
  22. package/dist/components/Avatar/Avatar.svelte +31 -18
  23. package/dist/components/Avatar/README.md +166 -0
  24. package/dist/components/Avatar/index.css +130 -0
  25. package/dist/components/Backdrop/Backdrop.svelte +7 -2
  26. package/dist/components/Backdrop/README.md +71 -6
  27. package/dist/components/Backdrop/index.css +31 -0
  28. package/dist/components/Button/Button.svelte +116 -124
  29. package/dist/components/Button/Button.svelte.d.ts +35 -24
  30. package/dist/components/Button/README.md +87 -21
  31. package/dist/components/Button/index.css +475 -9
  32. package/dist/components/Button/index.d.ts +1 -1
  33. package/dist/components/Button/index.js +1 -1
  34. package/dist/components/ButtonGroupRadio/ButtonGroupRadio.svelte +7 -39
  35. package/dist/components/ButtonGroupRadio/ButtonGroupRadio.svelte.d.ts +0 -1
  36. package/dist/components/ButtonGroupRadio/README.md +82 -4
  37. package/dist/components/ButtonGroupRadio/index.css +158 -14
  38. package/dist/components/Collapsible/Collapsible.svelte +7 -7
  39. package/dist/components/Collapsible/Collapsible.svelte.d.ts +2 -2
  40. package/dist/components/Collapsible/README.md +34 -2
  41. package/dist/components/Collapsible/index.css +40 -0
  42. package/dist/components/CommandMenu/CommandMenu.svelte +18 -26
  43. package/dist/components/CommandMenu/CommandMenu.svelte.d.ts +0 -1
  44. package/dist/components/CommandMenu/README.md +39 -0
  45. package/dist/components/CommandMenu/index.css +47 -2
  46. package/dist/components/DismissibleMessage/DismissibleMessage.svelte +53 -51
  47. package/dist/components/DismissibleMessage/DismissibleMessage.svelte.d.ts +6 -6
  48. package/dist/components/DismissibleMessage/README.md +93 -11
  49. package/dist/components/DismissibleMessage/index.css +128 -8
  50. package/dist/components/DismissibleMessage/index.d.ts +1 -1
  51. package/dist/components/DropdownMenu/DropdownMenu.svelte +14 -51
  52. package/dist/components/DropdownMenu/DropdownMenu.svelte.d.ts +6 -7
  53. package/dist/components/DropdownMenu/README.md +132 -0
  54. package/dist/components/DropdownMenu/index.css +258 -52
  55. package/dist/components/Input/FieldAssets.svelte +8 -5
  56. package/dist/components/Input/FieldCheckbox.svelte +7 -44
  57. package/dist/components/Input/FieldFile.svelte +1 -6
  58. package/dist/components/Input/FieldInput.svelte +9 -1
  59. package/dist/components/Input/FieldInput.svelte.d.ts +2 -0
  60. package/dist/components/Input/FieldOptions.svelte +42 -39
  61. package/dist/components/Input/FieldRadios.svelte +7 -16
  62. package/dist/components/Input/FieldSelect.svelte +1 -1
  63. package/dist/components/Input/FieldSwitch.svelte +1 -5
  64. package/dist/components/Input/FieldTextarea.svelte +1 -1
  65. package/dist/components/Input/README.md +194 -0
  66. package/dist/components/Input/_internal/FieldRadioInternal.svelte +2 -40
  67. package/dist/components/Input/_internal/InputWrap.svelte +8 -48
  68. package/dist/components/Input/index.css +524 -116
  69. package/dist/components/KbdShortcut/KbdShortcut.svelte +4 -12
  70. package/dist/components/KbdShortcut/README.md +34 -0
  71. package/dist/components/KbdShortcut/index.css +55 -0
  72. package/dist/components/ListItemButton/ListItemButton.svelte +37 -74
  73. package/dist/components/ListItemButton/ListItemButton.svelte.d.ts +1 -10
  74. package/dist/components/ListItemButton/README.md +100 -45
  75. package/dist/components/ListItemButton/index.css +173 -52
  76. package/dist/components/ListItemButton/index.d.ts +1 -1
  77. package/dist/components/ListItemButton/index.js +1 -1
  78. package/dist/components/Modal/Modal.svelte +1 -8
  79. package/dist/components/Modal/README.md +29 -0
  80. package/dist/components/Modal/index.css +38 -0
  81. package/dist/components/ModalDialog/ModalDialog.svelte +2 -21
  82. package/dist/components/ModalDialog/README.md +35 -0
  83. package/dist/components/ModalDialog/index.css +59 -0
  84. package/dist/components/Nav/Nav.svelte +732 -0
  85. package/dist/components/Nav/Nav.svelte.d.ts +110 -0
  86. package/dist/components/Nav/README.md +334 -0
  87. package/dist/components/Nav/index.css +318 -0
  88. package/dist/components/Nav/index.d.ts +1 -0
  89. package/dist/components/Nav/index.js +1 -0
  90. package/dist/components/Notifications/Notifications.svelte +44 -129
  91. package/dist/components/Notifications/Notifications.svelte.d.ts +9 -18
  92. package/dist/components/Notifications/README.md +186 -70
  93. package/dist/components/Notifications/index.css +212 -15
  94. package/dist/components/Notifications/notifications-stack.svelte.d.ts +4 -0
  95. package/dist/components/Notifications/notifications-stack.svelte.js +8 -0
  96. package/dist/components/Progress/Progress.svelte +4 -2
  97. package/dist/components/Progress/Progress.svelte.d.ts +1 -0
  98. package/dist/components/Progress/README.md +97 -11
  99. package/dist/components/Progress/_internal/Bar.svelte +4 -15
  100. package/dist/components/Progress/_internal/Bar.svelte.d.ts +1 -1
  101. package/dist/components/Progress/_internal/Circle.svelte +30 -2
  102. package/dist/components/Progress/_internal/Circle.svelte.d.ts +1 -0
  103. package/dist/components/Progress/index.css +50 -4
  104. package/dist/components/Skeleton/README.md +152 -0
  105. package/dist/components/Skeleton/Skeleton.svelte +9 -9
  106. package/dist/components/Skeleton/Skeleton.svelte.d.ts +0 -1
  107. package/dist/components/Skeleton/index.css +72 -45
  108. package/dist/components/Spinner/README.md +149 -37
  109. package/dist/components/Spinner/Spinner.svelte +14 -38
  110. package/dist/components/Spinner/Spinner.svelte.d.ts +2 -1
  111. package/dist/components/Spinner/SpinnerCircle.svelte +6 -34
  112. package/dist/components/Spinner/SpinnerCircle.svelte.d.ts +1 -0
  113. package/dist/components/Spinner/SpinnerCircleOscillate.svelte +10 -5
  114. package/dist/components/Spinner/SpinnerUnicode.svelte +3 -1
  115. package/dist/components/Spinner/SpinnerUnicode.svelte.d.ts +1 -0
  116. package/dist/components/Spinner/index.css +104 -0
  117. package/dist/components/Switch/README.md +45 -14
  118. package/dist/components/Switch/Switch.svelte +23 -48
  119. package/dist/components/Switch/Switch.svelte.d.ts +4 -2
  120. package/dist/components/Switch/index.css +121 -4
  121. package/dist/components/Switch/index.d.ts +1 -2
  122. package/dist/components/Switch/index.js +1 -2
  123. package/dist/components/TabbedMenu/README.md +37 -21
  124. package/dist/components/TabbedMenu/TabbedMenu.svelte +5 -46
  125. package/dist/components/TabbedMenu/TabbedMenu.svelte.d.ts +0 -1
  126. package/dist/components/TabbedMenu/index.css +84 -17
  127. package/dist/components/ThemePreview/README.md +289 -0
  128. package/dist/components/ThemePreview/ThemePreview.svelte +394 -0
  129. package/dist/components/ThemePreview/ThemePreview.svelte.d.ts +35 -0
  130. package/dist/components/ThemePreview/index.css +509 -0
  131. package/dist/components/ThemePreview/index.d.ts +1 -0
  132. package/dist/components/ThemePreview/index.js +1 -0
  133. package/dist/components/TwCheck/README.md +32 -13
  134. package/dist/components/TwCheck/TwCheck.svelte +11 -9
  135. package/dist/components/TwCheck/TwCheck.svelte.d.ts +0 -1
  136. package/dist/components/TwCheck/index.css +17 -2
  137. package/dist/components/TypeaheadInput/TypeaheadInput.svelte +20 -188
  138. package/dist/components/TypeaheadInput/TypeaheadInput.svelte.d.ts +4 -2
  139. package/dist/components/X/X.svelte +12 -5
  140. package/dist/components/X/X.svelte.d.ts +1 -0
  141. package/dist/icons/index.d.ts +1 -0
  142. package/dist/icons/index.js +1 -0
  143. package/dist/index.css +46 -26
  144. package/dist/index.d.ts +2 -0
  145. package/dist/index.js +2 -0
  146. package/dist/themes/blue-orange.css +217 -0
  147. package/dist/themes/blue-orange.d.ts +6 -0
  148. package/dist/themes/blue-orange.js +175 -0
  149. package/dist/themes/cyan-red.css +217 -0
  150. package/dist/themes/cyan-red.d.ts +6 -0
  151. package/dist/themes/cyan-red.js +175 -0
  152. package/dist/themes/cyan-slate.css +217 -0
  153. package/dist/themes/cyan-slate.d.ts +6 -0
  154. package/dist/themes/cyan-slate.js +175 -0
  155. package/dist/themes/emerald-pink.css +217 -0
  156. package/dist/themes/emerald-pink.d.ts +6 -0
  157. package/dist/themes/emerald-pink.js +175 -0
  158. package/dist/themes/fuchsia-emerald.css +217 -0
  159. package/dist/themes/fuchsia-emerald.d.ts +6 -0
  160. package/dist/themes/fuchsia-emerald.js +175 -0
  161. package/dist/themes/gray.css +217 -0
  162. package/dist/themes/gray.d.ts +6 -0
  163. package/dist/themes/gray.js +175 -0
  164. package/dist/themes/indigo-amber.css +217 -0
  165. package/dist/themes/indigo-amber.d.ts +6 -0
  166. package/dist/themes/indigo-amber.js +175 -0
  167. package/dist/themes/neutral.css +217 -0
  168. package/dist/themes/neutral.d.ts +6 -0
  169. package/dist/themes/neutral.js +175 -0
  170. package/dist/themes/pink-emerald.css +217 -0
  171. package/dist/themes/pink-emerald.d.ts +6 -0
  172. package/dist/themes/pink-emerald.js +175 -0
  173. package/dist/themes/purple-yellow.css +217 -0
  174. package/dist/themes/purple-yellow.d.ts +6 -0
  175. package/dist/themes/purple-yellow.js +175 -0
  176. package/dist/themes/rainbow.css +217 -0
  177. package/dist/themes/rainbow.d.ts +6 -0
  178. package/dist/themes/rainbow.js +180 -0
  179. package/dist/themes/red-blue.css +217 -0
  180. package/dist/themes/red-blue.d.ts +6 -0
  181. package/dist/themes/red-blue.js +175 -0
  182. package/dist/themes/red-cyan.css +217 -0
  183. package/dist/themes/red-cyan.d.ts +6 -0
  184. package/dist/themes/red-cyan.js +175 -0
  185. package/dist/themes/rose-teal.css +217 -0
  186. package/dist/themes/rose-teal.d.ts +6 -0
  187. package/dist/themes/rose-teal.js +175 -0
  188. package/dist/themes/sky-amber.css +217 -0
  189. package/dist/themes/sky-amber.d.ts +6 -0
  190. package/dist/themes/sky-amber.js +175 -0
  191. package/dist/themes/slate-cyan.css +217 -0
  192. package/dist/themes/slate-cyan.d.ts +6 -0
  193. package/dist/themes/slate-cyan.js +175 -0
  194. package/dist/themes/tailwind-color-pairs.md +31 -0
  195. package/dist/themes/teal-rose.css +217 -0
  196. package/dist/themes/teal-rose.d.ts +6 -0
  197. package/dist/themes/teal-rose.js +175 -0
  198. package/dist/themes/violet-lime.css +217 -0
  199. package/dist/themes/violet-lime.d.ts +6 -0
  200. package/dist/themes/violet-lime.js +175 -0
  201. package/dist/utils/design-tokens.d.ts +43 -0
  202. package/dist/utils/design-tokens.js +127 -0
  203. package/dist/utils/index.d.ts +1 -0
  204. package/dist/utils/index.js +1 -0
  205. package/dist/utils/storage-abstraction.js +1 -1
  206. package/package.json +14 -11
  207. package/dist/components/Switch/SwitchButton.svelte +0 -135
  208. package/dist/components/Switch/SwitchButton.svelte.d.ts +0 -21
@@ -1,13 +1,14 @@
1
1
  # Switch
2
2
 
3
- A toggle switch component with size variants, keyboard support, and optional async validation.
3
+ A toggle switch component with size variants, semantic intents, keyboard support, and optional async validation.
4
4
 
5
5
  ## Props
6
6
 
7
7
  | Prop | Type | Default | Description |
8
8
  |------|------|---------|-------------|
9
9
  | `checked` | `boolean` | - | Toggle state (bindable) |
10
- | `size` | `"xs" \| "sm" \| "md" \| "lg" \| "xl" \| string` | `"md"` | Switch size |
10
+ | `size` | `"sm" \| "md" \| "lg" \| "xl" \| string` | `"lg"` | Switch size |
11
+ | `intent` | `"primary" \| "accent" \| "success" \| "warning" \| "destructive" \| "info"` | - | Semantic color intent |
11
12
  | `name` | `string` | - | Form field name for hidden checkbox |
12
13
  | `label` | `string` | - | Screen reader label (visually hidden) |
13
14
  | `required` | `boolean` | `false` | Mark as required |
@@ -17,7 +18,6 @@ A toggle switch component with size variants, keyboard support, and optional asy
17
18
  | `validate` | `boolean \| ValidateOptions` | - | Enable validation |
18
19
  | `class` | `string` | - | CSS for switch container |
19
20
  | `dotClass` | `string` | - | CSS for toggle knob |
20
- | `button` | `HTMLButtonElement` | - | Button element reference (bindable) |
21
21
 
22
22
  ## Snippets
23
23
 
@@ -32,34 +32,41 @@ A toggle switch component with size variants, keyboard support, and optional asy
32
32
 
33
33
  ```svelte
34
34
  <script lang="ts">
35
- import { Switch } from 'stuic';
35
+ import { Switch } from '@marianmeres/stuic';
36
36
 
37
37
  let enabled = $state(false);
38
38
  </script>
39
39
 
40
40
  <Switch bind:checked={enabled} />
41
- <span>{enabled ? 'On' : 'Off'}</span>
42
41
  ```
43
42
 
44
43
  ### Different Sizes
45
44
 
46
45
  ```svelte
47
- <Switch size="xs" />
48
46
  <Switch size="sm" />
49
47
  <Switch size="md" />
50
48
  <Switch size="lg" />
51
49
  <Switch size="xl" />
52
50
  ```
53
51
 
52
+ ### Semantic Intents
53
+
54
+ ```svelte
55
+ <Switch intent="primary" checked />
56
+ <Switch intent="success" checked />
57
+ <Switch intent="warning" checked />
58
+ <Switch intent="destructive" checked />
59
+ ```
60
+
54
61
  ### With Icons Inside
55
62
 
56
63
  ```svelte
57
64
  <Switch bind:checked={darkMode}>
58
65
  {#snippet on()}
59
- <span class="text-xs">🌙</span>
66
+ <span class="text-xs">ON</span>
60
67
  {/snippet}
61
68
  {#snippet off()}
62
- <span class="text-xs">☀️</span>
69
+ <span class="text-xs">OFF</span>
63
70
  {/snippet}
64
71
  </Switch>
65
72
  ```
@@ -72,21 +79,17 @@ A toggle switch component with size variants, keyboard support, and optional asy
72
79
 
73
80
  async function checkPremium(current: boolean) {
74
81
  if (!current) {
75
- // Turning on - check if user can enable premium
76
82
  const canEnable = await checkSubscription();
77
83
  if (!canEnable) {
78
84
  alert('Premium subscription required');
79
- return false; // Prevent toggle
85
+ return false;
80
86
  }
81
87
  }
82
88
  return true;
83
89
  }
84
90
  </script>
85
91
 
86
- <Switch
87
- bind:checked={premium}
88
- preHook={checkPremium}
89
- />
92
+ <Switch bind:checked={premium} preHook={checkPremium} />
90
93
  ```
91
94
 
92
95
  ### In a Form
@@ -107,6 +110,34 @@ A toggle switch component with size variants, keyboard support, and optional asy
107
110
  <Switch checked={false} disabled />
108
111
  ```
109
112
 
113
+ ## CSS Variables
114
+
115
+ ### Component Tokens
116
+
117
+ | Variable | Default | Description |
118
+ |----------|---------|-------------|
119
+ | `--stuic-switch-track` | `--stuic-color-border` | Unchecked track color |
120
+ | `--stuic-switch-track-checked` | `--stuic-color-primary` | Checked track color |
121
+ | `--stuic-switch-dot` | `--color-white` | Knob background color |
122
+ | `--stuic-switch-dot-foreground` | `--stuic-color-foreground` | Knob text/icon color |
123
+ | `--stuic-switch-ring-width` | `4px` | Focus ring width |
124
+ | `--stuic-switch-ring-color` | `--stuic-color-ring` | Focus ring color |
125
+ | `--stuic-switch-transition` | `100ms` | Transition duration |
126
+
127
+ ### Example Overrides
128
+
129
+ ```css
130
+ /* Global: green switches */
131
+ :root {
132
+ --stuic-switch-track-checked: var(--color-green-500);
133
+ }
134
+ ```
135
+
136
+ ```svelte
137
+ <!-- Local: orange switch -->
138
+ <Switch style="--stuic-switch-track-checked: var(--color-orange-500);" />
139
+ ```
140
+
110
141
  ## Keyboard Support
111
142
 
112
143
  - **Space**: Toggle switch
@@ -6,10 +6,20 @@
6
6
  ValidationResult,
7
7
  } from "../../actions/validate.svelte.js";
8
8
 
9
+ export type SwitchIntent =
10
+ | "primary"
11
+ | "accent"
12
+ | "success"
13
+ | "warning"
14
+ | "destructive"
15
+ | "info";
16
+
9
17
  export interface Props extends Omit<HTMLLabelAttributes, "children" | "onchange"> {
10
18
  button?: HTMLButtonElement;
11
19
  checked?: boolean;
12
- size?: "xs" | "sm" | "md" | "lg" | "xl" | string;
20
+ size?: "sm" | "md" | "lg" | string;
21
+ /** Semantic color intent */
22
+ intent?: SwitchIntent;
13
23
  /** Form field name for the hidden checkbox */
14
24
  name?: string;
15
25
  class?: string;
@@ -38,11 +48,10 @@
38
48
  import { twMerge } from "../../utils/tw-merge.js";
39
49
  import { validate as validateAction } from "../../actions/validate.svelte.js";
40
50
 
41
- import "./index.css";
42
-
43
51
  let {
44
52
  button = $bindable(),
45
53
  size = "md",
54
+ intent,
46
55
  name,
47
56
  class: classProp,
48
57
  dotClass,
@@ -62,19 +71,17 @@
62
71
 
63
72
  const _preset: any = {
64
73
  size: {
65
- xs: `h-4 w-7`,
66
- sm: `h-5 w-9`,
67
- md: `h-6 w-11`,
68
- lg: `h-7 w-13`,
69
- xl: `h-8 w-15`,
74
+ xs: `h-5 w-9`,
75
+ sm: `h-6 w-11`,
76
+ md: `h-7 w-13`,
77
+ lg: `h-8 w-15`,
70
78
  },
71
79
  dot: {
72
80
  size: {
73
- xs: `size-2 data-[checked=true]:translate-x-4`,
74
- sm: `size-3 data-[checked=true]:translate-x-5`,
75
- md: `size-4 data-[checked=true]:translate-x-6`,
76
- lg: `size-5 data-[checked=true]:translate-x-7`,
77
- xl: `size-6 data-[checked=true]:translate-x-8`,
81
+ xs: `size-3 data-[checked=true]:translate-x-5`,
82
+ sm: `size-4 data-[checked=true]:translate-x-6`,
83
+ md: `size-5 data-[checked=true]:translate-x-7`,
84
+ lg: `size-6 data-[checked=true]:translate-x-8`,
78
85
  },
79
86
  },
80
87
  };
@@ -94,31 +101,10 @@
94
101
  <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
95
102
  <label
96
103
  bind:this={wrap}
97
- class={twMerge(
98
- "stuic-switch",
99
- `m-2
100
- relative inline-flex shrink-0 items-center
101
- rounded-full cursor-pointer
102
-
103
- transition-colors duration-100
104
-
105
- hover:brightness-105 active:brightness-95
106
- data-[disabled=true]:cursor-not-allowed! data-[disabled=true]:opacity-50! data-[disabled=true]:hover:brightness-100
107
-
108
- bg-neutral-400 dark:bg-neutral-400
109
-
110
- data-[checked=true]:bg-switch-accent
111
- dark:data-[checked=true]:bg-switch-accent-dark
112
-
113
- focus:outline-0
114
- focus:ring-switch-accent/20 focus:dark:ring-switch-accent-dark/20
115
- focus:ring-4`,
116
- size,
117
- _preset.size[size],
118
- classProp
119
- )}
104
+ class={twMerge("stuic-switch m-2", _preset.size[size], classProp)}
120
105
  data-checked={checked}
121
106
  data-disabled={disabled}
107
+ data-intent={intent}
122
108
  tabindex={disabled ? -1 : tabindex}
123
109
  onkeydown={(e: KeyboardEvent) => {
124
110
  if (!disabled && !e.metaKey && ["Space", "Enter"].includes(e.code)) {
@@ -142,18 +128,7 @@
142
128
  {...rest as Record<string, unknown>}
143
129
  >
144
130
  <span
145
- class={twMerge(
146
- "dot",
147
- `flex items-center justify-center
148
- translate-x-1 rounded-full
149
- transition-all duration-100
150
- shadow
151
- bg-neutral-50 dark:bg-neutral-50
152
- text-neutral-950 dark:text-neutral-950`,
153
- size,
154
- _preset.dot.size[size],
155
- dotClass
156
- )}
131
+ class={twMerge("dot translate-x-1", _preset.dot.size[size], dotClass)}
157
132
  data-checked={checked}
158
133
  >
159
134
  {#if checked}
@@ -1,10 +1,13 @@
1
1
  import type { Snippet } from "svelte";
2
2
  import type { FormEventHandler, HTMLLabelAttributes } from "svelte/elements";
3
3
  import type { ValidateOptions, ValidationResult } from "../../actions/validate.svelte.js";
4
+ export type SwitchIntent = "primary" | "accent" | "success" | "warning" | "destructive" | "info";
4
5
  export interface Props extends Omit<HTMLLabelAttributes, "children" | "onchange"> {
5
6
  button?: HTMLButtonElement;
6
7
  checked?: boolean;
7
- size?: "xs" | "sm" | "md" | "lg" | "xl" | string;
8
+ size?: "sm" | "md" | "lg" | string;
9
+ /** Semantic color intent */
10
+ intent?: SwitchIntent;
8
11
  /** Form field name for the hidden checkbox */
9
12
  name?: string;
10
13
  class?: string;
@@ -26,7 +29,6 @@ export interface Props extends Omit<HTMLLabelAttributes, "children" | "onchange"
26
29
  validate?: boolean | Omit<ValidateOptions, "setValidationResult">;
27
30
  setValidationResult?: (res: ValidationResult) => void;
28
31
  }
29
- import "./index.css";
30
32
  declare const Switch: import("svelte").Component<Props, {}, "button" | "checked">;
31
33
  type Switch = ReturnType<typeof Switch>;
32
34
  export default Switch;
@@ -1,5 +1,122 @@
1
- /* prettier-ignore */
2
- @theme inline {
3
- --color-switch-accent: var(--color-switch-accent, var(--color-red-600));
4
- --color-switch-accent-dark: var(--color-switch-accent-dark, var(--color-red-400));
1
+ /* ============================================================================
2
+ SWITCH COMPONENT TOKENS
3
+ Override globally: :root { --stuic-switch-track-checked: var(--color-green-500); }
4
+ Override locally: <Switch style="--stuic-switch-track-checked: var(--color-green-500);">
5
+ ============================================================================ */
6
+
7
+ :root {
8
+ /* Track (background) */
9
+ --stuic-switch-track: var(
10
+ --stuic-color-border
11
+ ); /* intentionally border, to make it more contrast */
12
+ --stuic-switch-track-checked: var(--stuic-color-primary);
13
+
14
+ /* Dot (knob) */
15
+ --stuic-switch-dot: var(--color-white);
16
+ --stuic-switch-dot-foreground: var(--stuic-color-foreground);
17
+
18
+ /* Focus ring */
19
+ --stuic-switch-ring-width: 4px;
20
+ --stuic-switch-ring-color: var(--stuic-color-ring);
21
+
22
+ /* Transition */
23
+ --stuic-switch-transition: 100ms;
24
+ }
25
+
26
+ @layer components {
27
+ /* ============================================================================
28
+ BASE STYLES
29
+ ============================================================================ */
30
+
31
+ .stuic-switch {
32
+ /* Use internal vars that can be set by intent */
33
+ --_track: var(--stuic-switch-track);
34
+ --_track-checked: var(--stuic-switch-track-checked);
35
+ --_dot: var(--stuic-switch-dot);
36
+ --_dot-fg: var(--stuic-switch-dot-foreground);
37
+
38
+ position: relative;
39
+ display: inline-flex;
40
+ flex-shrink: 0;
41
+ align-items: center;
42
+ border-radius: 9999px;
43
+ cursor: pointer;
44
+
45
+ background: var(--_track);
46
+ transition:
47
+ background var(--stuic-switch-transition),
48
+ box-shadow var(--stuic-switch-transition);
49
+ }
50
+
51
+ /* Checked state */
52
+ .stuic-switch[data-checked="true"] {
53
+ background: var(--_track-checked);
54
+ }
55
+
56
+ /* Hover */
57
+ .stuic-switch:hover:not([data-disabled="true"]) {
58
+ /* filter: brightness(1.05); */
59
+ filter: brightness(0.95);
60
+ }
61
+
62
+ /* Active */
63
+ .stuic-switch:active:not([data-disabled="true"]) {
64
+ /* filter: brightness(0.95); */
65
+ }
66
+
67
+ /* Focus */
68
+ .stuic-switch:focus {
69
+ outline: none;
70
+ box-shadow: 0 0 0 var(--stuic-switch-ring-width)
71
+ color-mix(in srgb, var(--stuic-switch-ring-color) 30%, transparent);
72
+ }
73
+
74
+ /* Disabled */
75
+ .stuic-switch[data-disabled="true"] {
76
+ cursor: not-allowed;
77
+ opacity: 0.8;
78
+ }
79
+
80
+ /* ============================================================================
81
+ DOT (KNOB) STYLES
82
+ ============================================================================ */
83
+
84
+ .stuic-switch > .dot {
85
+ display: flex;
86
+ align-items: center;
87
+ justify-content: center;
88
+ border-radius: 9999px;
89
+ background: var(--_dot);
90
+ color: var(--_dot-fg);
91
+ box-shadow: var(--shadow-sm);
92
+ transition: transform var(--stuic-switch-transition);
93
+ }
94
+
95
+ /* ============================================================================
96
+ INTENT COLOR MAPPING
97
+ ============================================================================ */
98
+
99
+ .stuic-switch[data-intent="primary"] {
100
+ --_track-checked: var(--stuic-color-primary);
101
+ }
102
+
103
+ .stuic-switch[data-intent="accent"] {
104
+ --_track-checked: var(--stuic-color-accent);
105
+ }
106
+
107
+ .stuic-switch[data-intent="success"] {
108
+ --_track-checked: var(--stuic-color-success);
109
+ }
110
+
111
+ .stuic-switch[data-intent="warning"] {
112
+ --_track-checked: var(--stuic-color-warning);
113
+ }
114
+
115
+ .stuic-switch[data-intent="destructive"] {
116
+ --_track-checked: var(--stuic-color-destructive);
117
+ }
118
+
119
+ .stuic-switch[data-intent="info"] {
120
+ --_track-checked: var(--stuic-color-info);
121
+ }
5
122
  }
@@ -1,2 +1 @@
1
- export { default as Switch, type Props as SwitchProps } from "./Switch.svelte";
2
- export { default as SwitchButton, type Props as SwitchButtonProps, } from "./SwitchButton.svelte";
1
+ export { default as Switch, type Props as SwitchProps, type SwitchIntent, } from "./Switch.svelte";
@@ -1,2 +1 @@
1
- export { default as Switch } from "./Switch.svelte";
2
- export { default as SwitchButton, } from "./SwitchButton.svelte";
1
+ export { default as Switch, } from "./Switch.svelte";
@@ -46,33 +46,49 @@ interface TabbedMenuItem {
46
46
  class?: string;
47
47
  data?: Record<string, any>;
48
48
  onSelect?: () => void | boolean; // Return false to prevent selection
49
+ href?: string; // Render as anchor instead of button
49
50
  }
50
51
  ```
51
52
 
52
53
  ## CSS Variables
53
54
 
55
+ ### Component Tokens
56
+
57
+ Override to customize appearance:
58
+
59
+ | Variable | Default | Description |
60
+ |----------|---------|-------------|
61
+ | `--stuic-tabbed-menu-tab-bg` | `--stuic-color-muted` | Tab background |
62
+ | `--stuic-tabbed-menu-tab-text` | `--stuic-color-muted-foreground` | Tab text color |
63
+ | `--stuic-tabbed-menu-tab-bg-active` | `--stuic-color-surface` | Active tab background |
64
+ | `--stuic-tabbed-menu-tab-text-active` | `--stuic-color-foreground` | Active tab text color |
65
+ | `--stuic-tabbed-menu-border` | `--stuic-color-border` | Border color |
66
+ | `--stuic-tabbed-menu-border-active` | `--stuic-color-primary` | Active tab border color |
67
+ | `--stuic-tabbed-menu-gap` | `calc(var(--spacing) * 1)` | Gap between tabs |
68
+ | `--stuic-tabbed-menu-radius` | `var(--radius-md)` | Tab border radius (top corners) |
69
+ | `--stuic-tabbed-menu-padding-x` | `calc(var(--spacing) * 4)` | Horizontal padding |
70
+ | `--stuic-tabbed-menu-padding-y` | `calc(var(--spacing) * 2)` | Vertical padding |
71
+ | `--stuic-tabbed-menu-transition` | `150ms` | Transition duration |
72
+ | `--stuic-tabbed-menu-font-weight-active` | `var(--font-weight-medium)` | Active tab font weight |
73
+ | `--stuic-tabbed-menu-item-max-width` | `10rem` | Max width per tab item |
74
+
75
+ ### Example Overrides
76
+
54
77
  ```css
55
- /* Tab button */
56
- --color-tabbed-menu-tab-bg
57
- --color-tabbed-menu-tab-bg-dark
58
- --color-tabbed-menu-tab-text
59
- --color-tabbed-menu-tab-text-dark
60
-
61
- /* Active tab */
62
- --color-tabbed-menu-tab-active-bg
63
- --color-tabbed-menu-tab-active-bg-dark
64
- --color-tabbed-menu-tab-active-text
65
- --color-tabbed-menu-tab-active-text-dark
66
-
67
- /* Border */
68
- --color-tabbed-menu-border
69
- --color-tabbed-menu-border-dark
70
-
71
- /* Sizing */
72
- --tabbed-menu-border-radius: 0.5rem
73
- --tabbed-menu-padding-x: 1rem
74
- --tabbed-menu-padding-y: 0.5rem
75
- --tabbed-menu-gap: 0.25rem
78
+ /* Global override */
79
+ :root {
80
+ --stuic-tabbed-menu-tab-bg-active: var(--color-indigo-100);
81
+ --stuic-tabbed-menu-tab-text-active: var(--color-indigo-900);
82
+ --stuic-tabbed-menu-radius: var(--radius-lg);
83
+ }
84
+ ```
85
+
86
+ ```svelte
87
+ <!-- Local override -->
88
+ <TabbedMenu
89
+ {items}
90
+ style="--stuic-tabbed-menu-radius: 9999px; --stuic-tabbed-menu-padding-x: 2rem;"
91
+ />
76
92
  ```
77
93
 
78
94
  ## Keyboard Navigation
@@ -33,7 +33,6 @@
33
33
  </script>
34
34
 
35
35
  <script lang="ts">
36
- import "./index.css";
37
36
 
38
37
  //
39
38
  let {
@@ -54,44 +53,6 @@
54
53
  ...rest
55
54
  }: Props = $props();
56
55
 
57
- const CLS = `
58
- stuic-tabbed-menu
59
- flex flex-row
60
- gap-1
61
- list-none m-0 p-0
62
- `;
63
-
64
- const CLS_ITEM = `
65
- min-w-0 flex-1 max-w-40
66
- `;
67
-
68
- const CLS_BUTTON = `
69
- px-4 py-2
70
- rounded-t-md
71
- border border-b-0
72
- border-tabbed-menu-border dark:border-tabbed-menu-border-dark
73
- bg-tabbed-menu-tab-bg dark:bg-tabbed-menu-tab-bg-dark
74
- text-tabbed-menu-tab-text dark:text-tabbed-menu-tab-text-dark
75
- cursor-pointer
76
- transition-colors duration-150
77
- hover:brightness-105
78
- truncate w-full
79
- block
80
- text-center
81
- `;
82
- // focus-visible:outline-2 focus-visible:outline-offset-2
83
-
84
- const CLS_BUTTON_ACTIVE = `
85
- bg-tabbed-menu-tab-active-bg dark:bg-tabbed-menu-tab-active-bg-dark
86
- text-tabbed-menu-tab-active-text dark:text-tabbed-menu-tab-active-text-dark
87
- font-medium
88
- `;
89
-
90
- const CLS_BUTTON_DISABLED = `
91
- opacity-50 cursor-not-allowed
92
- pointer-events-none
93
- `;
94
-
95
56
  let buttonEls = $state<Record<string | number, HTMLButtonElement | HTMLAnchorElement>>(
96
57
  {}
97
58
  );
@@ -135,16 +96,14 @@
135
96
  }
136
97
  }
137
98
 
138
- function getButtonClass(item: TabbedMenuItem): string {
99
+ function getTabClass(item: TabbedMenuItem): string {
139
100
  const isActive = value === item.id;
140
101
  const isDisabled = item.disabled || disabled;
141
102
 
142
103
  return twMerge(
143
- !unstyled && CLS_BUTTON,
104
+ !unstyled && "stuic-tabbed-menu-tab",
144
105
  classButton,
145
- isActive && !unstyled && CLS_BUTTON_ACTIVE,
146
106
  isActive && classButtonActive,
147
- isDisabled && !unstyled && CLS_BUTTON_DISABLED,
148
107
  isDisabled && classButtonDisabled,
149
108
  item.class
150
109
  );
@@ -154,7 +113,7 @@
154
113
  {#if items.length}
155
114
  <ul
156
115
  bind:this={el}
157
- class={twMerge(!unstyled && CLS, classProp)}
116
+ class={twMerge(!unstyled && "stuic-tabbed-menu", classProp)}
158
117
  role="tablist"
159
118
  {...rest}
160
119
  >
@@ -164,11 +123,11 @@
164
123
  ["aria-selected"]: value === item.id,
165
124
  ["aria-disabled"]: item.disabled || disabled || undefined,
166
125
  tabindex: value === item.id ? 0 : -1,
167
- class: getButtonClass(item),
126
+ class: getTabClass(item),
168
127
  onclick: () => selectItem(item),
169
128
  onkeydown: (e: KeyboardEvent) => handleKeydown(e, item),
170
129
  }}
171
- <li class={twMerge(CLS_ITEM, classItem)} role="presentation">
130
+ <li class={twMerge(!unstyled && "stuic-tabbed-menu-item", classItem)} role="presentation">
172
131
  {#if item.href}
173
132
  <a href={item.href} {...props} bind:this={buttonEls[item.id]}>
174
133
  <Thc thc={item.label} />
@@ -22,7 +22,6 @@ export interface Props extends Omit<HTMLAttributes<HTMLUListElement>, "children"
22
22
  unstyled?: boolean;
23
23
  el?: HTMLUListElement;
24
24
  }
25
- import "./index.css";
26
25
  declare const TabbedMenu: import("svelte").Component<Props, {}, "value" | "el">;
27
26
  type TabbedMenu = ReturnType<typeof TabbedMenu>;
28
27
  export default TabbedMenu;
@@ -1,23 +1,90 @@
1
- /* prettier-ignore */
2
- @theme inline {
3
- /* Tab button background */
4
- --color-tabbed-menu-tab-bg: var(--color-tabbed-menu-tab-bg, var(--color-neutral-100));
5
- --color-tabbed-menu-tab-bg-dark: var(--color-tabbed-menu-tab-bg-dark, var(--color-neutral-700));
1
+ /* ============================================================================
2
+ TABBED MENU COMPONENT TOKENS
3
+ Override globally: :root { --stuic-tabbed-menu-radius: 0; }
4
+ Override locally: <TabbedMenu style="--stuic-tabbed-menu-radius: 9999px;">
5
+ ============================================================================ */
6
6
 
7
- /* Tab button text */
8
- --color-tabbed-menu-tab-text: var(--color-tabbed-menu-tab-text, var(--color-neutral-600));
9
- --color-tabbed-menu-tab-text-dark: var(--color-tabbed-menu-tab-text-dark, var(--color-neutral-300));
7
+ :root {
8
+ /* Color tokens */
9
+ --stuic-tabbed-menu-tab-bg: var(--stuic-color-muted);
10
+ --stuic-tabbed-menu-tab-text: var(--stuic-color-muted-foreground);
11
+ --stuic-tabbed-menu-tab-bg-active: var(--stuic-color-primary);
12
+ --stuic-tabbed-menu-tab-text-active: var(--stuic-color-primary-foreground);
13
+ --stuic-tabbed-menu-border: var(--stuic-color-border);
14
+ --stuic-tabbed-menu-border-active: var(--stuic-color-primary);
10
15
 
11
- /* Active tab background */
12
- --color-tabbed-menu-tab-active-bg: var(--color-tabbed-menu-tab-active-bg, var(--color-white));
13
- --color-tabbed-menu-tab-active-bg-dark: var(--color-tabbed-menu-tab-active-bg-dark, var(--color-neutral-800));
16
+ /* Layout tokens */
17
+ --stuic-tabbed-menu-gap: calc(var(--spacing) * 1);
18
+ --stuic-tabbed-menu-radius: var(--radius-md);
19
+ --stuic-tabbed-menu-padding-x: calc(var(--spacing) * 4);
20
+ --stuic-tabbed-menu-padding-y: calc(var(--spacing) * 2);
21
+ --stuic-tabbed-menu-transition: 150ms;
22
+ --stuic-tabbed-menu-font-weight-active: var(--font-weight-medium);
23
+ --stuic-tabbed-menu-item-max-width: 10rem;
24
+ }
25
+
26
+ @layer components {
27
+ /* ============================================================================
28
+ BASE STYLES
29
+ ============================================================================ */
30
+
31
+ .stuic-tabbed-menu {
32
+ display: flex;
33
+ flex-direction: row;
34
+ gap: var(--stuic-tabbed-menu-gap);
35
+ list-style: none;
36
+ margin: 0;
37
+ padding: 0;
38
+ }
39
+
40
+ .stuic-tabbed-menu-item {
41
+ min-width: 0;
42
+ flex: 1;
43
+ max-width: var(--stuic-tabbed-menu-item-max-width);
44
+ }
45
+
46
+ .stuic-tabbed-menu-tab {
47
+ display: block;
48
+ width: 100%;
49
+ padding: var(--stuic-tabbed-menu-padding-y) var(--stuic-tabbed-menu-padding-x);
50
+ border-radius: var(--stuic-tabbed-menu-radius) var(--stuic-tabbed-menu-radius) 0 0;
51
+ border: 1px solid var(--stuic-tabbed-menu-border);
52
+ border-bottom: 0;
53
+ background: var(--stuic-tabbed-menu-tab-bg);
54
+ color: var(--stuic-tabbed-menu-tab-text);
55
+ cursor: pointer;
56
+ transition:
57
+ background var(--stuic-tabbed-menu-transition),
58
+ color var(--stuic-tabbed-menu-transition),
59
+ filter var(--stuic-tabbed-menu-transition);
60
+ text-align: center;
61
+ overflow: hidden;
62
+ text-overflow: ellipsis;
63
+ white-space: nowrap;
64
+ }
65
+
66
+ .stuic-tabbed-menu-tab:hover:not([aria-disabled="true"]) {
67
+ filter: brightness(1.05);
68
+ }
14
69
 
15
- /* Active tab text */
16
- --color-tabbed-menu-tab-active-text: var(--color-tabbed-menu-tab-active-text, var(--color-neutral-900));
17
- --color-tabbed-menu-tab-active-text-dark: var(--color-tabbed-menu-tab-active-text-dark, var(--color-white));
70
+ /* Active tab state */
71
+ .stuic-tabbed-menu-tab[aria-selected="true"] {
72
+ background: var(--stuic-tabbed-menu-tab-bg-active);
73
+ color: var(--stuic-tabbed-menu-tab-text-active);
74
+ font-weight: var(--stuic-tabbed-menu-font-weight-active);
75
+ border-color: var(--stuic-tabbed-menu-border-active);
76
+ }
18
77
 
19
- /* Border */
20
- --color-tabbed-menu-border: var(--color-tabbed-menu-border, var(--color-neutral-300));
21
- --color-tabbed-menu-border-dark: var(--color-tabbed-menu-border-dark, var(--color-neutral-600));
78
+ /* Disabled tab state */
79
+ .stuic-tabbed-menu-tab[aria-disabled="true"] {
80
+ opacity: 0.5;
81
+ cursor: not-allowed;
82
+ pointer-events: none;
83
+ }
22
84
 
85
+ /* Focus styles */
86
+ .stuic-tabbed-menu-tab:focus-visible {
87
+ outline: 2px solid var(--stuic-color-ring);
88
+ outline-offset: 2px;
89
+ }
23
90
  }