@marianmeres/stuic 3.0.0 → 3.0.2

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 (156) hide show
  1. package/dist/actions/index.d.ts +1 -0
  2. package/dist/actions/index.js +1 -0
  3. package/dist/actions/typeahead.svelte.d.ts +53 -0
  4. package/dist/actions/typeahead.svelte.js +328 -0
  5. package/dist/base.css +17 -0
  6. package/dist/components/AlertConfirmPrompt/AlertConfirmPrompt.svelte +4 -3
  7. package/dist/components/AlertConfirmPrompt/AlertConfirmPrompt.svelte.d.ts +4 -3
  8. package/dist/components/AlertConfirmPrompt/Current.svelte +1 -2
  9. package/dist/components/AlertConfirmPrompt/Current.svelte.d.ts +0 -1
  10. package/dist/components/AlertConfirmPrompt/index.css +47 -43
  11. package/dist/components/AssetsPreview/AssetsPreview.svelte +0 -1
  12. package/dist/components/AssetsPreview/AssetsPreview.svelte.d.ts +0 -1
  13. package/dist/components/AssetsPreview/index.css +31 -29
  14. package/dist/components/Avatar/Avatar.svelte +0 -1
  15. package/dist/components/Avatar/Avatar.svelte.d.ts +0 -1
  16. package/dist/components/Avatar/index.css +87 -85
  17. package/dist/components/Backdrop/Backdrop.svelte +0 -1
  18. package/dist/components/Backdrop/Backdrop.svelte.d.ts +0 -1
  19. package/dist/components/Backdrop/index.css +15 -13
  20. package/dist/components/Button/Button.svelte +0 -1
  21. package/dist/components/Button/Button.svelte.d.ts +0 -1
  22. package/dist/components/Button/index.css +431 -429
  23. package/dist/components/ButtonGroupRadio/ButtonGroupRadio.svelte +0 -1
  24. package/dist/components/ButtonGroupRadio/ButtonGroupRadio.svelte.d.ts +0 -1
  25. package/dist/components/ButtonGroupRadio/index.css +123 -117
  26. package/dist/components/Collapsible/index.css +17 -15
  27. package/dist/components/CommandMenu/CommandMenu.svelte +7 -4
  28. package/dist/components/CommandMenu/CommandMenu.svelte.d.ts +0 -1
  29. package/dist/components/CommandMenu/index.css +27 -25
  30. package/dist/components/DismissibleMessage/DismissibleMessage.svelte +0 -2
  31. package/dist/components/DismissibleMessage/DismissibleMessage.svelte.d.ts +0 -1
  32. package/dist/components/DismissibleMessage/index.css +116 -110
  33. package/dist/components/DropdownMenu/DropdownMenu.svelte +317 -74
  34. package/dist/components/DropdownMenu/DropdownMenu.svelte.d.ts +19 -1
  35. package/dist/components/DropdownMenu/index.css +236 -170
  36. package/dist/components/DropdownMenu/index.d.ts +1 -1
  37. package/dist/components/HoverExpandableWidth/HoverExpandableWidth.svelte +3 -1
  38. package/dist/components/HoverExpandableWidth/HoverExpandableWidth.svelte.d.ts +1 -0
  39. package/dist/components/Input/FieldInput.svelte +8 -0
  40. package/dist/components/Input/FieldInput.svelte.d.ts +2 -0
  41. package/dist/components/Input/FieldOptions.svelte +1 -1
  42. package/dist/components/Input/index.css +411 -398
  43. package/dist/components/KbdShortcut/KbdShortcut.svelte +4 -12
  44. package/dist/components/KbdShortcut/README.md +34 -0
  45. package/dist/components/KbdShortcut/index.css +55 -0
  46. package/dist/components/ListItemButton/ListItemButton.svelte +0 -1
  47. package/dist/components/ListItemButton/ListItemButton.svelte.d.ts +0 -1
  48. package/dist/components/ListItemButton/index.css +118 -116
  49. package/dist/components/Modal/Modal.svelte +0 -1
  50. package/dist/components/Modal/Modal.svelte.d.ts +0 -1
  51. package/dist/components/Modal/index.css +18 -16
  52. package/dist/components/ModalDialog/index.css +29 -27
  53. package/dist/components/Nav/Nav.svelte +732 -0
  54. package/dist/components/Nav/Nav.svelte.d.ts +110 -0
  55. package/dist/components/Nav/README.md +334 -0
  56. package/dist/components/Nav/index.css +318 -0
  57. package/dist/components/Nav/index.d.ts +1 -0
  58. package/dist/components/Nav/index.js +1 -0
  59. package/dist/components/Notifications/Notifications.svelte +2 -3
  60. package/dist/components/Notifications/Notifications.svelte.d.ts +0 -1
  61. package/dist/components/Notifications/index.css +158 -158
  62. package/dist/components/Notifications/notifications-stack.svelte.d.ts +4 -0
  63. package/dist/components/Notifications/notifications-stack.svelte.js +8 -0
  64. package/dist/components/Progress/Progress.svelte +4 -2
  65. package/dist/components/Progress/Progress.svelte.d.ts +1 -0
  66. package/dist/components/Progress/README.md +86 -15
  67. package/dist/components/Progress/_internal/Bar.svelte +4 -15
  68. package/dist/components/Progress/_internal/Bar.svelte.d.ts +1 -1
  69. package/dist/components/Progress/_internal/Circle.svelte +30 -2
  70. package/dist/components/Progress/_internal/Circle.svelte.d.ts +1 -0
  71. package/dist/components/Progress/index.css +47 -1
  72. package/dist/components/Skeleton/README.md +152 -0
  73. package/dist/components/Skeleton/Skeleton.svelte +6 -7
  74. package/dist/components/Skeleton/Skeleton.svelte.d.ts +0 -1
  75. package/dist/components/Skeleton/index.css +73 -43
  76. package/dist/components/Spinner/README.md +149 -37
  77. package/dist/components/Spinner/Spinner.svelte +14 -38
  78. package/dist/components/Spinner/Spinner.svelte.d.ts +2 -1
  79. package/dist/components/Spinner/SpinnerCircle.svelte +6 -34
  80. package/dist/components/Spinner/SpinnerCircle.svelte.d.ts +1 -0
  81. package/dist/components/Spinner/SpinnerCircleOscillate.svelte +10 -5
  82. package/dist/components/Spinner/SpinnerUnicode.svelte +3 -1
  83. package/dist/components/Spinner/SpinnerUnicode.svelte.d.ts +1 -0
  84. package/dist/components/Spinner/index.css +104 -0
  85. package/dist/components/Switch/README.md +34 -18
  86. package/dist/components/Switch/Switch.svelte +24 -46
  87. package/dist/components/Switch/Switch.svelte.d.ts +4 -2
  88. package/dist/components/Switch/index.css +120 -2
  89. package/dist/components/Switch/index.d.ts +1 -2
  90. package/dist/components/Switch/index.js +1 -2
  91. package/dist/components/TabbedMenu/README.md +28 -17
  92. package/dist/components/TabbedMenu/TabbedMenu.svelte +5 -46
  93. package/dist/components/TabbedMenu/TabbedMenu.svelte.d.ts +0 -1
  94. package/dist/components/TabbedMenu/index.css +85 -3
  95. package/dist/components/ThemePreview/ThemePreview.svelte +86 -33
  96. package/dist/components/ThemePreview/ThemePreview.svelte.d.ts +3 -1
  97. package/dist/components/ThemePreview/index.css +24 -8
  98. package/dist/components/TwCheck/README.md +32 -13
  99. package/dist/components/TwCheck/TwCheck.svelte +11 -9
  100. package/dist/components/TwCheck/TwCheck.svelte.d.ts +0 -1
  101. package/dist/components/TwCheck/index.css +14 -0
  102. package/dist/components/TypeaheadInput/TypeaheadInput.svelte +19 -187
  103. package/dist/components/TypeaheadInput/TypeaheadInput.svelte.d.ts +4 -2
  104. package/dist/icons/index.d.ts +1 -0
  105. package/dist/icons/index.js +1 -0
  106. package/dist/index.css +44 -39
  107. package/dist/index.d.ts +1 -0
  108. package/dist/index.js +1 -0
  109. package/dist/themes/blue-orange.css +246 -156
  110. package/dist/themes/blue-orange.js +24 -0
  111. package/dist/themes/cyan-red.css +246 -156
  112. package/dist/themes/cyan-red.js +24 -0
  113. package/dist/themes/cyan-slate.css +246 -156
  114. package/dist/themes/cyan-slate.js +25 -1
  115. package/dist/themes/emerald-pink.css +246 -156
  116. package/dist/themes/emerald-pink.js +25 -1
  117. package/dist/themes/fuchsia-emerald.css +246 -156
  118. package/dist/themes/fuchsia-emerald.js +25 -1
  119. package/dist/themes/gray.css +246 -156
  120. package/dist/themes/gray.js +24 -0
  121. package/dist/themes/indigo-amber.css +246 -156
  122. package/dist/themes/indigo-amber.js +26 -2
  123. package/dist/themes/neutral.css +246 -156
  124. package/dist/themes/neutral.js +24 -0
  125. package/dist/themes/pink-emerald.css +246 -156
  126. package/dist/themes/pink-emerald.js +25 -1
  127. package/dist/themes/pink-teal.css +253 -0
  128. package/dist/themes/pink-teal.d.ts +6 -0
  129. package/dist/themes/pink-teal.js +175 -0
  130. package/dist/themes/purple-yellow.css +246 -156
  131. package/dist/themes/purple-yellow.js +24 -0
  132. package/dist/themes/rainbow.css +246 -156
  133. package/dist/themes/rainbow.js +25 -1
  134. package/dist/themes/red-blue.css +246 -156
  135. package/dist/themes/red-blue.js +24 -0
  136. package/dist/themes/red-cyan.css +246 -156
  137. package/dist/themes/red-cyan.js +24 -0
  138. package/dist/themes/red-sky.css +253 -0
  139. package/dist/themes/red-sky.d.ts +6 -0
  140. package/dist/themes/red-sky.js +175 -0
  141. package/dist/themes/rose-teal.css +246 -156
  142. package/dist/themes/rose-teal.js +24 -0
  143. package/dist/themes/sky-amber.css +246 -156
  144. package/dist/themes/sky-amber.js +26 -2
  145. package/dist/themes/slate-cyan.css +246 -156
  146. package/dist/themes/slate-cyan.js +25 -1
  147. package/dist/themes/teal-rose.css +246 -156
  148. package/dist/themes/teal-rose.js +24 -0
  149. package/dist/themes/violet-lime.css +246 -156
  150. package/dist/themes/violet-lime.js +27 -3
  151. package/dist/utils/design-tokens.d.ts +1 -1
  152. package/dist/utils/design-tokens.js +44 -3
  153. package/dist/utils/storage-abstraction.js +1 -1
  154. package/package.json +11 -28
  155. package/dist/components/Switch/SwitchButton.svelte +0 -134
  156. package/dist/components/Switch/SwitchButton.svelte.d.ts +0 -21
@@ -0,0 +1,104 @@
1
+ :root {
2
+ /* Spinner (radial bars) */
3
+ --stuic-spinner-opacity: 0.8;
4
+ --stuic-spinner-fade-end-opacity: 0.12;
5
+ --stuic-spinner-duration: 750ms;
6
+
7
+ /* SpinnerCircle */
8
+ --stuic-spinner-circle-thickness-thin: 1px;
9
+ --stuic-spinner-circle-thickness-normal: 2px;
10
+ --stuic-spinner-circle-thickness-thick: 4px;
11
+ --stuic-spinner-circle-duration: 750ms;
12
+
13
+ /* SpinnerCircleOscillate */
14
+ --stuic-spinner-circle-oscillate-bg-stroke: var(
15
+ --stuic-color-border,
16
+ rgba(0 0 0 / 0.1)
17
+ );
18
+ --stuic-spinner-circle-oscillate-duration: 0.75s;
19
+
20
+ /* SpinnerUnicode */
21
+ --stuic-spinner-unicode-font-size: var(--text-xl);
22
+ }
23
+
24
+ @layer components {
25
+ /* ========================================================================
26
+ Spinner (radial bars)
27
+ ======================================================================== */
28
+
29
+ .stuic-spinner {
30
+ position: relative;
31
+ opacity: var(--stuic-spinner-opacity);
32
+ }
33
+
34
+ .stuic-spinner-bar {
35
+ position: absolute;
36
+ left: 50%;
37
+ top: 0;
38
+ background: currentColor;
39
+ animation: stuic-spinner-fade var(--stuic-spinner-duration) linear infinite;
40
+ }
41
+
42
+ @keyframes stuic-spinner-fade {
43
+ from {
44
+ opacity: 1;
45
+ }
46
+ to {
47
+ opacity: var(--stuic-spinner-fade-end-opacity);
48
+ }
49
+ }
50
+
51
+ /* ========================================================================
52
+ SpinnerCircle
53
+ ======================================================================== */
54
+
55
+ .stuic-spinner-circle {
56
+ display: inline-block;
57
+ box-sizing: border-box;
58
+ border-radius: 50%;
59
+ border-style: solid;
60
+ border-color: currentColor;
61
+ border-top-color: transparent;
62
+ animation: stuic-spinner-circle-spin linear infinite;
63
+ animation-duration: var(--stuic-spinner-circle-duration);
64
+ }
65
+
66
+ .stuic-spinner-circle[data-thickness="thin"] {
67
+ border-width: var(--stuic-spinner-circle-thickness-thin);
68
+ }
69
+
70
+ .stuic-spinner-circle[data-thickness="normal"] {
71
+ border-width: var(--stuic-spinner-circle-thickness-normal);
72
+ }
73
+
74
+ .stuic-spinner-circle[data-thickness="thick"] {
75
+ border-width: var(--stuic-spinner-circle-thickness-thick);
76
+ }
77
+
78
+ @keyframes stuic-spinner-circle-spin {
79
+ to {
80
+ transform: rotate(360deg);
81
+ }
82
+ }
83
+
84
+ /* ========================================================================
85
+ SpinnerCircleOscillate
86
+ ======================================================================== */
87
+
88
+ .stuic-spinner-circle-oscillate {
89
+ animation: stuic-spinner-circle-spin linear infinite;
90
+ animation-duration: var(--stuic-spinner-circle-oscillate-duration);
91
+ }
92
+
93
+ /* ========================================================================
94
+ SpinnerUnicode
95
+ ======================================================================== */
96
+
97
+ .stuic-spinner-unicode {
98
+ display: inline-block;
99
+ font-family: var(--font-mono);
100
+ line-height: 1;
101
+ color: currentColor;
102
+ font-size: var(--stuic-spinner-unicode-font-size);
103
+ }
104
+ }
@@ -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
@@ -109,19 +112,32 @@ A toggle switch component with size variants, keyboard support, and optional asy
109
112
 
110
113
  ## CSS Variables
111
114
 
115
+ ### Component Tokens
116
+
112
117
  | Variable | Default | Description |
113
118
  |----------|---------|-------------|
114
- | `--stuic-switch-accent` | `--stuic-accent` | Active (checked) color |
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 |
115
126
 
116
- ### Example Override
127
+ ### Example Overrides
117
128
 
118
129
  ```css
130
+ /* Global: green switches */
119
131
  :root {
120
- /* Green switches globally */
121
- --stuic-switch-accent: var(--color-green-500);
132
+ --stuic-switch-track-checked: var(--color-green-500);
122
133
  }
123
134
  ```
124
135
 
136
+ ```svelte
137
+ <!-- Local: orange switch -->
138
+ <Switch style="--stuic-switch-track-checked: var(--color-orange-500);" />
139
+ ```
140
+
125
141
  ## Keyboard Support
126
142
 
127
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?: "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
- size = "lg",
53
+ size = "md",
54
+ intent,
46
55
  name,
47
56
  class: classProp,
48
57
  dotClass,
@@ -62,17 +71,17 @@
62
71
 
63
72
  const _preset: any = {
64
73
  size: {
65
- sm: `h-5 w-9`,
66
- md: `h-6 w-11`,
67
- lg: `h-7 w-13`,
68
- 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`,
69
78
  },
70
79
  dot: {
71
80
  size: {
72
- sm: `size-3 data-[checked=true]:translate-x-5`,
73
- md: `size-4 data-[checked=true]:translate-x-6`,
74
- lg: `size-5 data-[checked=true]:translate-x-7`,
75
- 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`,
76
85
  },
77
86
  },
78
87
  };
@@ -92,30 +101,10 @@
92
101
  <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
93
102
  <label
94
103
  bind:this={wrap}
95
- class={twMerge(
96
- "stuic-switch",
97
- `m-2
98
- relative inline-flex shrink-0 items-center
99
- rounded-full cursor-pointer
100
-
101
- transition-colors duration-100
102
-
103
- hover:brightness-105 active:brightness-95
104
- data-[disabled=true]:cursor-not-allowed! data-[disabled=true]:opacity-50! data-[disabled=true]:hover:brightness-100
105
-
106
- bg-neutral-400 dark:bg-neutral-400
107
-
108
- data-[checked=true]:bg-(--stuic-switch-accent)
109
-
110
- focus:outline-0
111
- focus:ring-(--stuic-switch-accent)/20
112
- focus:ring-4`,
113
- size,
114
- _preset.size[size],
115
- classProp
116
- )}
104
+ class={twMerge("stuic-switch m-2", _preset.size[size], classProp)}
117
105
  data-checked={checked}
118
106
  data-disabled={disabled}
107
+ data-intent={intent}
119
108
  tabindex={disabled ? -1 : tabindex}
120
109
  onkeydown={(e: KeyboardEvent) => {
121
110
  if (!disabled && !e.metaKey && ["Space", "Enter"].includes(e.code)) {
@@ -139,18 +128,7 @@
139
128
  {...rest as Record<string, unknown>}
140
129
  >
141
130
  <span
142
- class={twMerge(
143
- "dot",
144
- `flex items-center justify-center
145
- translate-x-1 rounded-full
146
- transition-all duration-100
147
- shadow
148
- bg-neutral-50 dark:bg-neutral-50
149
- text-neutral-950 dark:text-neutral-950`,
150
- size,
151
- _preset.dot.size[size],
152
- dotClass
153
- )}
131
+ class={twMerge("dot translate-x-1", _preset.dot.size[size], dotClass)}
154
132
  data-checked={checked}
155
133
  >
156
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?: "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,4 +1,122 @@
1
- /* Switch component tokens */
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
+
2
7
  :root {
3
- --stuic-switch-accent: var(--stuic-color-primary);
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%, var(--stuic-color-background));
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
+ }
4
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,39 +46,50 @@ 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
 
54
- Customize the component's appearance using CSS custom properties:
55
+ ### Component Tokens
56
+
57
+ Override to customize appearance:
55
58
 
56
59
  | Variable | Default | Description |
57
60
  |----------|---------|-------------|
58
- | `--stuic-tabbed-menu-tab-bg` | `--stuic-surface-sunken` | Tab background |
59
- | `--stuic-tabbed-menu-tab-text` | `--stuic-text-muted` | Tab text color |
60
- | `--stuic-tabbed-menu-tab-bg-active` | `--stuic-surface` | Active tab background |
61
- | `--stuic-tabbed-menu-tab-text-active` | `--stuic-text` | Active tab text color |
62
- | `--stuic-tabbed-menu-border` | `--stuic-border` | Border color |
63
-
64
- ### Example Override
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
65
76
 
66
77
  ```css
78
+ /* Global override */
67
79
  :root {
68
80
  --stuic-tabbed-menu-tab-bg-active: var(--color-indigo-100);
69
81
  --stuic-tabbed-menu-tab-text-active: var(--color-indigo-900);
82
+ --stuic-tabbed-menu-radius: var(--radius-lg);
70
83
  }
71
84
  ```
72
85
 
73
- ### Legacy Variables (Backwards Compatibility)
74
-
75
- The following legacy variable names still work as aliases:
76
-
77
- | Legacy Name | New Name |
78
- |-------------|----------|
79
- | `--color-tabbed-menu-tab-bg` | `--stuic-tabbed-menu-tab-bg` |
80
- | `--color-tabbed-menu-tab-active-bg` | `--stuic-tabbed-menu-tab-bg-active` |
81
- | `--color-tabbed-menu-border` | `--stuic-tabbed-menu-border` |
86
+ ```svelte
87
+ <!-- Local override -->
88
+ <TabbedMenu
89
+ {items}
90
+ style="--stuic-tabbed-menu-radius: 9999px; --stuic-tabbed-menu-padding-x: 2rem;"
91
+ />
92
+ ```
82
93
 
83
94
  ## Keyboard Navigation
84
95
 
@@ -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-(--stuic-tabbed-menu-border)
73
- bg-(--stuic-tabbed-menu-tab-bg)
74
- text-(--stuic-tabbed-menu-tab-text)
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-(--stuic-tabbed-menu-tab-bg-active)
86
- text-(--stuic-tabbed-menu-tab-text-active)
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;