@marianmeres/stuic 2.65.0 → 3.0.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 (171) hide show
  1. package/README.md +292 -4
  2. package/dist/README.md +41 -18
  3. package/dist/actions/popover/README.md +19 -0
  4. package/dist/actions/popover/index.css +6 -9
  5. package/dist/actions/popover/popover.svelte.js +2 -2
  6. package/dist/actions/tooltip/README.md +18 -0
  7. package/dist/actions/tooltip/index.css +5 -8
  8. package/dist/actions/tooltip/tooltip.svelte.js +1 -1
  9. package/dist/components/AlertConfirmPrompt/AlertConfirmPrompt.svelte +9 -10
  10. package/dist/components/AlertConfirmPrompt/AlertConfirmPrompt.svelte.d.ts +3 -3
  11. package/dist/components/AlertConfirmPrompt/Current.svelte +15 -17
  12. package/dist/components/AlertConfirmPrompt/Current.svelte.d.ts +5 -3
  13. package/dist/components/AlertConfirmPrompt/acp-icons.js +5 -4
  14. package/dist/components/AlertConfirmPrompt/index.css +62 -0
  15. package/dist/components/AssetsPreview/AssetsPreview.svelte +92 -73
  16. package/dist/components/AssetsPreview/AssetsPreview.svelte.d.ts +1 -0
  17. package/dist/components/AssetsPreview/index.css +59 -0
  18. package/dist/components/Avatar/Avatar.svelte +32 -18
  19. package/dist/components/Avatar/Avatar.svelte.d.ts +1 -0
  20. package/dist/components/Avatar/README.md +166 -0
  21. package/dist/components/Avatar/index.css +128 -0
  22. package/dist/components/Backdrop/Backdrop.svelte +8 -2
  23. package/dist/components/Backdrop/Backdrop.svelte.d.ts +1 -0
  24. package/dist/components/Backdrop/README.md +71 -6
  25. package/dist/components/Backdrop/index.css +29 -0
  26. package/dist/components/Button/Button.svelte +117 -124
  27. package/dist/components/Button/Button.svelte.d.ts +35 -23
  28. package/dist/components/Button/README.md +87 -21
  29. package/dist/components/Button/index.css +473 -9
  30. package/dist/components/Button/index.d.ts +1 -1
  31. package/dist/components/Button/index.js +1 -1
  32. package/dist/components/ButtonGroupRadio/ButtonGroupRadio.svelte +7 -38
  33. package/dist/components/ButtonGroupRadio/README.md +82 -4
  34. package/dist/components/ButtonGroupRadio/index.css +152 -14
  35. package/dist/components/Collapsible/Collapsible.svelte +7 -7
  36. package/dist/components/Collapsible/Collapsible.svelte.d.ts +2 -2
  37. package/dist/components/Collapsible/README.md +34 -2
  38. package/dist/components/Collapsible/index.css +38 -0
  39. package/dist/components/CommandMenu/CommandMenu.svelte +13 -24
  40. package/dist/components/CommandMenu/README.md +39 -0
  41. package/dist/components/CommandMenu/index.css +45 -2
  42. package/dist/components/DismissibleMessage/DismissibleMessage.svelte +53 -50
  43. package/dist/components/DismissibleMessage/DismissibleMessage.svelte.d.ts +6 -5
  44. package/dist/components/DismissibleMessage/README.md +93 -11
  45. package/dist/components/DismissibleMessage/index.css +122 -8
  46. package/dist/components/DismissibleMessage/index.d.ts +1 -1
  47. package/dist/components/DropdownMenu/DropdownMenu.svelte +14 -50
  48. package/dist/components/DropdownMenu/DropdownMenu.svelte.d.ts +6 -6
  49. package/dist/components/DropdownMenu/README.md +132 -0
  50. package/dist/components/DropdownMenu/index.css +231 -27
  51. package/dist/components/Input/FieldAssets.svelte +8 -5
  52. package/dist/components/Input/FieldCheckbox.svelte +7 -44
  53. package/dist/components/Input/FieldFile.svelte +1 -6
  54. package/dist/components/Input/FieldInput.svelte +1 -1
  55. package/dist/components/Input/FieldOptions.svelte +41 -38
  56. package/dist/components/Input/FieldRadios.svelte +7 -16
  57. package/dist/components/Input/FieldSelect.svelte +1 -1
  58. package/dist/components/Input/FieldSwitch.svelte +1 -5
  59. package/dist/components/Input/FieldTextarea.svelte +1 -1
  60. package/dist/components/Input/README.md +194 -0
  61. package/dist/components/Input/_internal/FieldRadioInternal.svelte +2 -40
  62. package/dist/components/Input/_internal/InputWrap.svelte +8 -48
  63. package/dist/components/Input/index.css +522 -127
  64. package/dist/components/ListItemButton/ListItemButton.svelte +37 -73
  65. package/dist/components/ListItemButton/ListItemButton.svelte.d.ts +1 -9
  66. package/dist/components/ListItemButton/README.md +100 -45
  67. package/dist/components/ListItemButton/index.css +175 -56
  68. package/dist/components/ListItemButton/index.d.ts +1 -1
  69. package/dist/components/ListItemButton/index.js +1 -1
  70. package/dist/components/Modal/Modal.svelte +2 -8
  71. package/dist/components/Modal/Modal.svelte.d.ts +1 -0
  72. package/dist/components/Modal/README.md +29 -0
  73. package/dist/components/Modal/index.css +36 -0
  74. package/dist/components/ModalDialog/ModalDialog.svelte +2 -21
  75. package/dist/components/ModalDialog/README.md +35 -0
  76. package/dist/components/ModalDialog/index.css +57 -0
  77. package/dist/components/Notifications/Notifications.svelte +44 -128
  78. package/dist/components/Notifications/Notifications.svelte.d.ts +9 -17
  79. package/dist/components/Notifications/README.md +186 -70
  80. package/dist/components/Notifications/index.css +212 -15
  81. package/dist/components/Progress/README.md +15 -0
  82. package/dist/components/Progress/_internal/Bar.svelte +2 -2
  83. package/dist/components/Progress/index.css +4 -4
  84. package/dist/components/Skeleton/Skeleton.svelte +3 -2
  85. package/dist/components/Skeleton/index.css +11 -14
  86. package/dist/components/Spinner/Spinner.svelte +2 -2
  87. package/dist/components/Spinner/SpinnerCircle.svelte +1 -1
  88. package/dist/components/Switch/README.md +15 -0
  89. package/dist/components/Switch/Switch.svelte +4 -7
  90. package/dist/components/Switch/Switch.svelte.d.ts +1 -1
  91. package/dist/components/Switch/SwitchButton.svelte +4 -5
  92. package/dist/components/Switch/index.css +3 -4
  93. package/dist/components/TabbedMenu/README.md +26 -21
  94. package/dist/components/TabbedMenu/TabbedMenu.svelte +5 -5
  95. package/dist/components/TabbedMenu/index.css +7 -22
  96. package/dist/components/ThemePreview/README.md +289 -0
  97. package/dist/components/ThemePreview/ThemePreview.svelte +341 -0
  98. package/dist/components/ThemePreview/ThemePreview.svelte.d.ts +33 -0
  99. package/dist/components/ThemePreview/index.css +493 -0
  100. package/dist/components/ThemePreview/index.d.ts +1 -0
  101. package/dist/components/ThemePreview/index.js +1 -0
  102. package/dist/components/TwCheck/TwCheck.svelte +4 -4
  103. package/dist/components/TwCheck/index.css +3 -2
  104. package/dist/components/TypeaheadInput/TypeaheadInput.svelte +1 -1
  105. package/dist/components/X/X.svelte +16 -4
  106. package/dist/components/X/X.svelte.d.ts +1 -0
  107. package/dist/icons/index.d.ts +1 -0
  108. package/dist/icons/index.js +1 -0
  109. package/dist/index.css +31 -16
  110. package/dist/index.d.ts +1 -0
  111. package/dist/index.js +1 -0
  112. package/dist/themes/blue-orange.css +163 -0
  113. package/dist/themes/blue-orange.d.ts +6 -0
  114. package/dist/themes/blue-orange.js +151 -0
  115. package/dist/themes/cyan-red.css +163 -0
  116. package/dist/themes/cyan-red.d.ts +6 -0
  117. package/dist/themes/cyan-red.js +151 -0
  118. package/dist/themes/cyan-slate.css +163 -0
  119. package/dist/themes/cyan-slate.d.ts +6 -0
  120. package/dist/themes/cyan-slate.js +151 -0
  121. package/dist/themes/emerald-pink.css +163 -0
  122. package/dist/themes/emerald-pink.d.ts +6 -0
  123. package/dist/themes/emerald-pink.js +151 -0
  124. package/dist/themes/fuchsia-emerald.css +163 -0
  125. package/dist/themes/fuchsia-emerald.d.ts +6 -0
  126. package/dist/themes/fuchsia-emerald.js +151 -0
  127. package/dist/themes/gray.css +163 -0
  128. package/dist/themes/gray.d.ts +6 -0
  129. package/dist/themes/gray.js +151 -0
  130. package/dist/themes/indigo-amber.css +163 -0
  131. package/dist/themes/indigo-amber.d.ts +6 -0
  132. package/dist/themes/indigo-amber.js +151 -0
  133. package/dist/themes/neutral.css +163 -0
  134. package/dist/themes/neutral.d.ts +6 -0
  135. package/dist/themes/neutral.js +151 -0
  136. package/dist/themes/pink-emerald.css +163 -0
  137. package/dist/themes/pink-emerald.d.ts +6 -0
  138. package/dist/themes/pink-emerald.js +151 -0
  139. package/dist/themes/purple-yellow.css +163 -0
  140. package/dist/themes/purple-yellow.d.ts +6 -0
  141. package/dist/themes/purple-yellow.js +151 -0
  142. package/dist/themes/rainbow.css +163 -0
  143. package/dist/themes/rainbow.d.ts +6 -0
  144. package/dist/themes/rainbow.js +156 -0
  145. package/dist/themes/red-blue.css +163 -0
  146. package/dist/themes/red-blue.d.ts +6 -0
  147. package/dist/themes/red-blue.js +151 -0
  148. package/dist/themes/red-cyan.css +163 -0
  149. package/dist/themes/red-cyan.d.ts +6 -0
  150. package/dist/themes/red-cyan.js +151 -0
  151. package/dist/themes/rose-teal.css +163 -0
  152. package/dist/themes/rose-teal.d.ts +6 -0
  153. package/dist/themes/rose-teal.js +151 -0
  154. package/dist/themes/sky-amber.css +163 -0
  155. package/dist/themes/sky-amber.d.ts +6 -0
  156. package/dist/themes/sky-amber.js +151 -0
  157. package/dist/themes/slate-cyan.css +163 -0
  158. package/dist/themes/slate-cyan.d.ts +6 -0
  159. package/dist/themes/slate-cyan.js +151 -0
  160. package/dist/themes/tailwind-color-pairs.md +31 -0
  161. package/dist/themes/teal-rose.css +163 -0
  162. package/dist/themes/teal-rose.d.ts +6 -0
  163. package/dist/themes/teal-rose.js +151 -0
  164. package/dist/themes/violet-lime.css +163 -0
  165. package/dist/themes/violet-lime.d.ts +6 -0
  166. package/dist/themes/violet-lime.js +151 -0
  167. package/dist/utils/design-tokens.d.ts +43 -0
  168. package/dist/utils/design-tokens.js +100 -0
  169. package/dist/utils/index.d.ts +1 -0
  170. package/dist/utils/index.js +1 -0
  171. package/package.json +22 -2
@@ -1,20 +1,158 @@
1
- /* prettier-ignore */
2
- @theme inline {
3
- --color-button-group-bg: var(--color-button-group-bg, var(--color-white));
4
- --color-button-group-bg-dark: var(--color-button-group-bg-dark, var(--color-neutral-600));
1
+ /* ============================================================================
2
+ BUTTON GROUP RADIO COMPONENT TOKENS
3
+ Override globally: :root { --stuic-button-group-radius: 0; }
4
+ Override locally: <ButtonGroupRadio style="--stuic-button-group-radius: 9999px;">
5
+ ============================================================================ */
5
6
 
6
- --color-button-group-text: var(--color-button-group-text, var(--color-black));
7
- --color-button-group-text-dark: var(--color-button-group-text-dark, var(--color-neutral-300));
7
+ :root {
8
+ /* Structure - reference Tailwind vars where applicable */
9
+ --stuic-button-group-radius: var(--radius-md);
10
+ --stuic-button-group-padding: 0.375rem;
11
+ --stuic-button-group-gap: 0.25rem;
12
+ --stuic-button-group-border-width: 1px;
13
+ --stuic-button-group-transition: 150ms;
8
14
 
9
- --color-button-group-border: var(--color-button-group-border, var(--color-neutral-300));
10
- --color-button-group-border-dark: var(--color-button-group-border-dark, var(--color-neutral-800));
15
+ /* Button sizing - 44px is Apple HIG minimum touch target */
16
+ --stuic-button-group-button-padding-x: 0.75rem;
17
+ --stuic-button-group-button-padding-y: 0.5rem;
18
+ --stuic-button-group-button-min-height: 2.75rem; /* 44px */
11
19
 
12
- --color-button-group-accent: var(--color-button-group-accent, var(--color-red-600));
13
- --color-button-group-accent-dark: var(--color-button-group-accent-dark, var(--color-red-400));
20
+ /* Focus ring */
21
+ --stuic-button-group-ring-width: 3px;
22
+ --stuic-button-group-ring-color: var(--stuic-color-ring);
14
23
 
15
- --color-button-group-bg-active: var(--color-button-group-bg-active, var(--color-neutral-500));
16
- --color-button-group-bg-active-dark: var(--color-button-group-bg-active-dark, var(--color-neutral-500));
24
+ /* Container colors - theme vars */
25
+ --stuic-button-group-bg: var(--stuic-color-surface);
26
+ --stuic-button-group-text: var(--stuic-color-foreground);
27
+ --stuic-button-group-border: var(--stuic-color-border);
17
28
 
18
- --color-button-group-text-active: var(--color-button-group-text-active, var(--color-white));
19
- --color-button-group-text-active-dark: var(--color-button-group-text-active-dark, var(--color-white));
29
+ /* Button colors (inactive) */
30
+ --stuic-button-group-button-bg: transparent;
31
+ --stuic-button-group-button-text: var(--stuic-color-foreground);
32
+ --stuic-button-group-button-bg-hover: var(--stuic-color-muted);
33
+ --stuic-button-group-button-text-hover: var(--stuic-color-foreground);
34
+
35
+ /* Button colors (active) */
36
+ --stuic-button-group-button-bg-active: var(--stuic-color-primary);
37
+ --stuic-button-group-button-text-active: var(--stuic-color-primary-foreground);
38
+ --stuic-button-group-button-bg-active-hover: var(--stuic-color-primary-hover);
39
+ --stuic-button-group-button-text-active-hover: var(--stuic-color-primary-foreground-hover);
40
+ }
41
+
42
+ /* ============================================================================
43
+ BASE STYLES
44
+ ============================================================================ */
45
+
46
+ .stuic-button-group {
47
+ /* Layout */
48
+ display: inline-flex;
49
+ align-items: center;
50
+ justify-content: space-between;
51
+ gap: var(--stuic-button-group-gap);
52
+ width: 100%;
53
+
54
+ /* Box model */
55
+ padding: var(--stuic-button-group-padding);
56
+ border-width: var(--stuic-button-group-border-width);
57
+ border-style: solid;
58
+ border-radius: var(--stuic-button-group-radius);
59
+
60
+ /* Colors */
61
+ background: var(--stuic-button-group-bg);
62
+ color: var(--stuic-button-group-text);
63
+ border-color: var(--stuic-button-group-border);
64
+ }
65
+
66
+ /* Focus-within state */
67
+ .stuic-button-group:focus-within {
68
+ outline: var(--stuic-button-group-ring-width) solid var(--stuic-button-group-ring-color);
69
+ outline-offset: 0;
70
+ }
71
+
72
+ /* ============================================================================
73
+ SIZE VARIANTS
74
+ ============================================================================ */
75
+
76
+ .stuic-button-group[data-size="sm"] {
77
+ --stuic-button-group-padding: 0.25rem;
78
+ --stuic-button-group-gap: 0.125rem;
79
+ --stuic-button-group-button-padding-x: 0.5rem;
80
+ --stuic-button-group-button-padding-y: 0.375rem;
81
+ --stuic-button-group-button-min-height: 2.25rem; /* 36px - still touch-friendly */
82
+ }
83
+
84
+ .stuic-button-group[data-size="lg"] {
85
+ --stuic-button-group-padding: 0.5rem;
86
+ --stuic-button-group-gap: 0.375rem;
87
+ --stuic-button-group-button-padding-x: 1rem;
88
+ --stuic-button-group-button-padding-y: 0.625rem;
89
+ --stuic-button-group-button-min-height: 3rem; /* 48px */
90
+ }
91
+
92
+ /* ============================================================================
93
+ BUTTON STYLES
94
+ ============================================================================ */
95
+
96
+ .stuic-button-group-button {
97
+ /* Layout */
98
+ display: inline-flex;
99
+ align-items: center;
100
+ justify-content: center;
101
+ flex: 1;
102
+
103
+ /* Box model - mobile-friendly touch targets */
104
+ padding: var(--stuic-button-group-button-padding-y) var(--stuic-button-group-button-padding-x);
105
+ min-height: var(--stuic-button-group-button-min-height);
106
+ border: none;
107
+ border-radius: var(--stuic-button-group-radius);
108
+
109
+ /* Typography */
110
+ white-space: nowrap;
111
+ line-height: 1;
112
+ text-align: center;
113
+
114
+ /* Colors */
115
+ background: var(--stuic-button-group-button-bg);
116
+ color: var(--stuic-button-group-button-text);
117
+
118
+ /* Interaction - mobile-friendly */
119
+ cursor: pointer;
120
+ user-select: none;
121
+ -webkit-tap-highlight-color: transparent;
122
+ touch-action: manipulation;
123
+
124
+ /* Transition */
125
+ transition:
126
+ background var(--stuic-button-group-transition),
127
+ color var(--stuic-button-group-transition);
128
+
129
+ /* Focus */
130
+ outline: none;
131
+ }
132
+
133
+ .stuic-button-group-button:hover:not(:disabled):not([aria-checked="true"]) {
134
+ background: var(--stuic-button-group-button-bg-hover);
135
+ color: var(--stuic-button-group-button-text-hover);
136
+ }
137
+
138
+ .stuic-button-group-button:focus-visible {
139
+ outline: none; /* Focus handled by container */
140
+ }
141
+
142
+ /* Active/selected button */
143
+ .stuic-button-group-button[aria-checked="true"] {
144
+ background: var(--stuic-button-group-button-bg-active);
145
+ color: var(--stuic-button-group-button-text-active);
146
+ box-shadow: none;
147
+ }
148
+
149
+ .stuic-button-group-button[aria-checked="true"]:hover:not(:disabled) {
150
+ background: var(--stuic-button-group-button-bg-active-hover);
151
+ color: var(--stuic-button-group-button-text-active-hover);
152
+ }
153
+
154
+ /* Disabled state */
155
+ .stuic-button-group-button:disabled {
156
+ opacity: 0.5;
157
+ cursor: not-allowed;
20
158
  }
@@ -23,8 +23,8 @@
23
23
  classContent?: string;
24
24
  /** Toggle button class */
25
25
  classToggle?: string;
26
- /** Opacity class for toggle button (default: "opacity-70") */
27
- toggleOpacity?: string;
26
+ /** Inline styles (for CSS variable overrides) */
27
+ style?: string;
28
28
  /** Bind reference to container element */
29
29
  el?: HTMLDivElement;
30
30
  /** Optional translate function */
@@ -39,8 +39,8 @@
39
39
  i18nSpanWrap: boolean = true
40
40
  ) {
41
41
  const m: Record<string, string> = {
42
- more: "Show more...",
43
- less: "Show less...",
42
+ more: "More...",
43
+ less: "Less...",
44
44
  };
45
45
  let out = m[k] ?? fallback ?? k;
46
46
 
@@ -58,7 +58,7 @@
58
58
  class: classProp,
59
59
  classContent,
60
60
  classToggle,
61
- toggleOpacity = "opacity-75",
61
+ style,
62
62
  el = $bindable(),
63
63
  t = t_default,
64
64
  }: Props = $props();
@@ -86,6 +86,7 @@
86
86
  bind:this={el}
87
87
  bind:clientWidth={containerWidth}
88
88
  class={twMerge("stuic-collapsible", classProp)}
89
+ {style}
89
90
  >
90
91
  <div class="flex items-end">
91
92
  <div
@@ -98,8 +99,7 @@
98
99
  <button
99
100
  type="button"
100
101
  class={twMerge(
101
- toggleOpacity,
102
- "hover:opacity-100 cursor-pointer px-2 py-1 -my-1 -mr-2",
102
+ "stuic-collapsible-toggle cursor-pointer -my-1 -mr-2",
103
103
  classToggle
104
104
  )}
105
105
  onclick={() => (expanded = !expanded)}
@@ -17,8 +17,8 @@ export interface Props {
17
17
  classContent?: string;
18
18
  /** Toggle button class */
19
19
  classToggle?: string;
20
- /** Opacity class for toggle button (default: "opacity-70") */
21
- toggleOpacity?: string;
20
+ /** Inline styles (for CSS variable overrides) */
21
+ style?: string;
22
22
  /** Bind reference to container element */
23
23
  el?: HTMLDivElement;
24
24
  /** Optional translate function */
@@ -14,8 +14,9 @@ A component that truncates content to a specified number of lines with an expand
14
14
  | `class` | `string` | - | Container element class |
15
15
  | `classContent` | `string` | - | Content wrapper class |
16
16
  | `classToggle` | `string` | - | Toggle button class |
17
- | `toggleOpacity` | `string` | `"opacity-70"`| Opacity class for toggle button |
17
+ | `style` | `string` | - | Inline styles (for CSS variable overrides) |
18
18
  | `el` | `HTMLDivElement`| - | Bind reference to container element |
19
+ | `t` | `TranslateFn` | - | Optional translate function |
19
20
 
20
21
  ## Usage
21
22
 
@@ -74,8 +75,39 @@ A component that truncates content to a specified number of lines with an expand
74
75
  class="bg-gray-100 p-4 rounded"
75
76
  classContent="text-sm text-gray-600"
76
77
  classToggle="text-blue-500 font-bold"
77
- toggleOpacity="opacity-100"
78
78
  >
79
79
  Styled collapsible content.
80
80
  </Collapsible>
81
81
  ```
82
+
83
+ ### CSS Variable Overrides
84
+
85
+ ```svelte
86
+ <!-- Local override via inline style -->
87
+ <Collapsible style="--stuic-collapsible-toggle-opacity: 1;">
88
+ Always fully visible toggle button.
89
+ </Collapsible>
90
+ ```
91
+
92
+ ## CSS Variables
93
+
94
+ Override to customize appearance:
95
+
96
+ | Variable | Default | Description |
97
+ |----------|---------|-------------|
98
+ | `--stuic-collapsible-toggle-opacity` | `0.7` | Toggle button opacity |
99
+ | `--stuic-collapsible-toggle-opacity-hover` | `1` | Hover opacity |
100
+ | `--stuic-collapsible-toggle-padding-x` | `calc(var(--spacing) * 2)` | Horizontal padding |
101
+ | `--stuic-collapsible-toggle-padding-y` | `0.25rem` | Vertical padding |
102
+ | `--stuic-collapsible-transition` | `150ms` | Transition duration |
103
+ | `--stuic-collapsible-ring-width` | `2px` | Focus ring width |
104
+ | `--stuic-collapsible-ring-color` | `--stuic-color-ring` | Focus ring color |
105
+
106
+ ### Global Override
107
+
108
+ ```css
109
+ :root {
110
+ --stuic-collapsible-toggle-opacity: 0.5;
111
+ --stuic-collapsible-toggle-opacity-hover: 0.8;
112
+ }
113
+ ```
@@ -0,0 +1,38 @@
1
+ /* ============================================================================
2
+ COLLAPSIBLE COMPONENT TOKENS
3
+ Override globally: :root { --stuic-collapsible-toggle-opacity: 0.5; }
4
+ Override locally: <Collapsible style="--stuic-collapsible-toggle-opacity: 1;">
5
+ ============================================================================ */
6
+
7
+ :root {
8
+ /* Toggle button styling */
9
+ --stuic-collapsible-toggle-opacity: 0.7;
10
+ --stuic-collapsible-toggle-opacity-hover: 1;
11
+ --stuic-collapsible-toggle-padding-x: calc(var(--spacing) * 2);
12
+ --stuic-collapsible-toggle-padding-y: calc(var(--spacing) * 1);
13
+ --stuic-collapsible-transition: 150ms;
14
+
15
+ /* Focus ring (uses theme token) */
16
+ --stuic-collapsible-ring-width: 2px;
17
+ --stuic-collapsible-ring-color: var(--stuic-color-ring);
18
+ }
19
+
20
+ /* ============================================================================
21
+ TOGGLE BUTTON STYLES
22
+ ============================================================================ */
23
+
24
+ .stuic-collapsible-toggle {
25
+ opacity: var(--stuic-collapsible-toggle-opacity);
26
+ padding: var(--stuic-collapsible-toggle-padding-y)
27
+ var(--stuic-collapsible-toggle-padding-x);
28
+ transition: opacity var(--stuic-collapsible-transition);
29
+ }
30
+
31
+ .stuic-collapsible-toggle:hover {
32
+ opacity: var(--stuic-collapsible-toggle-opacity-hover);
33
+ }
34
+
35
+ .stuic-collapsible-toggle:focus-visible {
36
+ outline: var(--stuic-collapsible-ring-width) solid var(--stuic-collapsible-ring-color);
37
+ outline-offset: 2px;
38
+ }
@@ -62,6 +62,7 @@
62
62
  </script>
63
63
 
64
64
  <script lang="ts">
65
+ import Button from "../Button/Button.svelte";
65
66
  import "./index.css";
66
67
 
67
68
  const clog = createClog("CommandMenu");
@@ -251,8 +252,8 @@
251
252
  placeholder={searchPlaceholder ?? t("search_placeholder")}
252
253
  classInputBoxWrap={twMerge(
253
254
  // always look like focused
254
- `border-primary border-input-accent dark:border-input-accent-dark`,
255
- `ring-input-accent/20 dark:ring-input-accent-dark/20 ring-4`
255
+ `border-primary border-(--stuic-input-accent)`,
256
+ `ring-(--stuic-input-accent)/20 ring-4`
256
257
  )}
257
258
  onkeydown={(e) => {
258
259
  if (e.key === "Enter") {
@@ -262,25 +263,22 @@
262
263
  }}
263
264
  >
264
265
  {#snippet inputBefore()}
265
- <div class="flex flex-col items-center justify-center pl-3 opacity-75">
266
+ <div class="flex flex-col items-center justify-center pl-3 stuic-command-menu-muted">
266
267
  {@html iconSearch({ size: 19, strokeWidth: 3 })}
267
268
  </div>
268
269
  {/snippet}
269
270
  {#snippet inputAfter()}
270
- <div class="flex pl-2 items-center justify-center opacity-50">
271
+ <div class="flex pl-2 items-center justify-center stuic-command-menu-placeholder">
271
272
  {#if isFetching}
272
273
  <Spinner class="w-4" />
273
274
  {/if}
274
275
  </div>
275
276
  <div class="flex items-center justify-center">
276
- <button
277
+ <Button
278
+ x
279
+ variant="ghost"
280
+ roundedFull
277
281
  type="button"
278
- class={twMerge(
279
- "rounded m-1 opacity-75",
280
- "hover:opacity-100 hover:bg-neutral-200 dark:hover:bg-neutral-800",
281
- "focus-visible:opacity-100 focus-visible:outline-0",
282
- "focus-visible:bg-neutral-200 dark:focus-visible:bg-neutral-800"
283
- )}
284
282
  onclick={(e) => {
285
283
  e.preventDefault();
286
284
  if (!`${q || ""}`.trim()) {
@@ -289,9 +287,7 @@
289
287
  q = "";
290
288
  input?.focus();
291
289
  }}
292
- >
293
- <X class="m-2 size-6" />
294
- </button>
290
+ />
295
291
  </div>
296
292
  {/snippet}
297
293
  {#snippet inputBelow()}
@@ -304,10 +300,9 @@
304
300
  <div
305
301
  class={twMerge(
306
302
  "stuic-command-menu-options",
307
- "options block space-y-1 p-1",
303
+ "block space-y-1 p-1",
308
304
  "overflow-y-auto overflow-x-hidden mb-1",
309
- "border-t border-black/20",
310
- "max-h-60"
305
+ "border-t"
311
306
  )}
312
307
  bind:this={optionsBox}
313
308
  tabindex="-1"
@@ -320,10 +315,7 @@
320
315
  <div class="p-1">
321
316
  {#if _optgroup}
322
317
  <div
323
- class={[
324
- "mb-1 p-1 text-xs font-semibold uppercase tracking-wide",
325
- "text-neutral-500 dark:text-neutral-400",
326
- ]}
318
+ class="stuic-command-menu-group-header mb-1 p-1 font-semibold uppercase tracking-wide"
327
319
  >
328
320
  {_optgroup}
329
321
  </div>
@@ -369,9 +361,6 @@
369
361
  </ModalDialog>
370
362
 
371
363
  <style>
372
- .options {
373
- scrollbar-width: thin;
374
- }
375
364
  .sr-only {
376
365
  position: absolute;
377
366
  width: 1px;
@@ -93,6 +93,45 @@ The command menu uses `ModalDialog` internally with top-aligned positioning:
93
93
  - **Mobile**: Input at top of screen with 1rem margins from edges
94
94
  - **Desktop (md+)**: Input positioned at ~20% from top, max-width 768px, centered horizontally
95
95
 
96
+ ## CSS Variables
97
+
98
+ All styling can be customized via CSS variables. Define them on a parent element or in `:root` to override defaults.
99
+
100
+ ### Structure Tokens
101
+
102
+ | Variable | Default | Description |
103
+ |----------|---------|-------------|
104
+ | `--stuic-command-menu-transition` | `150ms` | Transition duration for hover/focus states |
105
+ | `--stuic-command-menu-options-max-height` | `15rem` | Maximum height of options container |
106
+
107
+ ### Color Tokens
108
+
109
+ | Variable | Default | Description |
110
+ |----------|---------|-------------|
111
+ | `--stuic-command-menu-divider-color` | `var(--stuic-color-border)` | Border color between input and options |
112
+ | `--stuic-command-menu-group-header-color` | `var(--stuic-color-muted-foreground)` | Option group header text color |
113
+ | `--stuic-command-menu-group-header-font-size` | `var(--text-xs)` | Option group header font size |
114
+
115
+ ### Custom Theme Example
116
+
117
+ ```svelte
118
+ <div style="
119
+ --stuic-command-menu-divider-color: var(--color-blue-200);
120
+ ">
121
+ <CommandMenu ... />
122
+ </div>
123
+ ```
124
+
125
+ ### Global Theme Override
126
+
127
+ ```css
128
+ /* In your app.css */
129
+ :root {
130
+ --stuic-command-menu-options-max-height: 20rem;
131
+ --stuic-command-menu-group-header-color: var(--color-indigo-500);
132
+ }
133
+ ```
134
+
96
135
  ## Keyboard Navigation
97
136
 
98
137
  - **Arrow Up/Down**: Navigate options
@@ -1,3 +1,46 @@
1
- /* Override ListItemButton defaults for CommandMenu context */
2
- .stuic-command-menu-options .stuic-list-item-button {
1
+ /* =============================================================================
2
+ COMMAND MENU COMPONENT TOKENS
3
+ Override globally: :root { --stuic-command-menu-divider-color: red; }
4
+ Override locally: <div style="--stuic-command-menu-divider-color: blue;">
5
+ ============================================================================= */
6
+
7
+ :root {
8
+ /* Structure */
9
+ --stuic-command-menu-transition: 150ms;
10
+ --stuic-command-menu-options-max-height: 15rem;
11
+
12
+ /* Colors */
13
+ --stuic-command-menu-divider-color: var(--stuic-color-border);
14
+ --stuic-command-menu-group-header-color: var(--stuic-color-muted-foreground);
15
+ --stuic-command-menu-group-header-font-size: var(--text-xs);
16
+
17
+ /* Muted/placeholder states (align with FieldOptions) */
18
+ --stuic-command-menu-muted-text: var(--stuic-color-muted-foreground);
19
+ }
20
+
21
+ /* =============================================================================
22
+ BASE STYLES
23
+ ============================================================================= */
24
+
25
+ /* Options container */
26
+ .stuic-command-menu-options {
27
+ scrollbar-width: thin;
28
+ max-height: var(--stuic-command-menu-options-max-height);
29
+ border-top-color: var(--stuic-command-menu-divider-color);
30
+ }
31
+
32
+ /* Option group header */
33
+ .stuic-command-menu-group-header {
34
+ color: var(--stuic-command-menu-group-header-color);
35
+ font-size: var(--stuic-command-menu-group-header-font-size);
36
+ }
37
+
38
+ /* Muted elements (search icon) */
39
+ .stuic-command-menu-muted {
40
+ color: var(--stuic-command-menu-muted-text);
41
+ }
42
+
43
+ /* Placeholder/loading states (spinner) */
44
+ .stuic-command-menu-placeholder {
45
+ color: var(--stuic-command-menu-muted-text);
3
46
  }
@@ -1,92 +1,95 @@
1
1
  <script lang="ts" module>
2
- import type { TW_COLORS } from "../../types.js";
3
2
  import type { THC } from "../Thc/Thc.svelte";
4
3
 
4
+ export type MessageIntent = "destructive" | "warning" | "success" | "info";
5
+
5
6
  export interface Props {
6
7
  class?: string;
7
8
  classContent?: string;
8
- classDismiss?: string;
9
- classX?: string;
10
- message: THC | Error;
11
- theme?: TW_COLORS;
9
+ classIcon?: string;
10
+ message: THC | Error | undefined | null;
11
+ intent?: MessageIntent;
12
12
  forceAsHtml?: boolean;
13
13
  duration?: number;
14
14
  onDismiss?: (() => void) | null | false;
15
+ withIcon?: boolean;
16
+ iconFn?: (() => string) | false;
15
17
  }
16
18
  </script>
17
19
 
18
20
  <script lang="ts">
19
21
  import { slide } from "svelte/transition";
20
- import Thc, { isTHCNotEmpty } from "../Thc/Thc.svelte";
21
- import X from "../X/X.svelte";
22
22
  import { twMerge } from "../../utils/tw-merge.js";
23
+ import Thc, { isTHCNotEmpty } from "../Thc/Thc.svelte";
24
+ import Button from "../Button/Button.svelte";
25
+ import {
26
+ iconAlertWarning,
27
+ iconAlertSuccess,
28
+ iconAlertInfo,
29
+ iconAlertError,
30
+ } from "../../icons/index.js";
23
31
 
24
32
  import "./index.css";
25
33
 
34
+ const INTENT_ICONS: Record<MessageIntent, () => string> = {
35
+ destructive: () => iconAlertError({ size: 29 }),
36
+ warning: () => iconAlertWarning({ size: 29 }),
37
+ success: () => iconAlertSuccess({ size: 29 }),
38
+ info: () => iconAlertInfo({ size: 29 }),
39
+ };
40
+
26
41
  let {
27
42
  class: classProps,
28
43
  classContent,
29
- classDismiss,
30
- classX,
44
+ classIcon,
31
45
  message,
32
- theme,
46
+ intent,
33
47
  forceAsHtml = true,
34
48
  duration = 150,
35
49
  onDismiss = () => (message = ""),
50
+ withIcon,
51
+ iconFn,
36
52
  }: Props = $props();
37
53
 
38
- let _message = $derived(`${message}`);
54
+ let _message = $derived(message ? String(message) : "");
39
55
  let _show = $derived(isTHCNotEmpty(_message));
56
+
57
+ let _iconHtml = $derived.by(() => {
58
+ if (iconFn === false) return "";
59
+ if (typeof iconFn === "function") return iconFn();
60
+ if (withIcon && intent) return INTENT_ICONS[intent]?.();
61
+ return "";
62
+ });
40
63
  </script>
41
64
 
42
65
  {#if _show}
43
66
  <div
44
- class={twMerge(
45
- "stuic-dismissible-message",
46
- `mb-4 rounded flex text-sm
47
- bg-dismiss-bg dark:bg-dismiss-bg-dark
48
- border-dismiss-border dark:border-dismiss-border-dark
49
- text-dismiss-text dark:text-dismiss-text-dark`,
50
- classProps
51
- )}
52
- style={theme
53
- ? `
54
- --color-dismiss-bg: var(--color-${theme}-100);
55
- --color-dismiss-bg-dark: var(--color-${theme}-700);
56
-
57
- --color-dismiss-text: var(--color-${theme}-800);
58
- --color-dismiss-text-dark: var(--color-${theme}-50);
59
-
60
- --color-dismiss-border: var(--color-${theme}-500);
61
- --color-dismiss-border-dark: var(--color-${theme}-500);
62
- `
63
- : ``}
67
+ class={twMerge("stuic-dismissible-message", "mb-4", classProps)}
68
+ data-intent={intent}
64
69
  transition:slide={{ duration }}
65
70
  >
66
- <div class={twMerge("content", "flex-1 px-4 py-3", classContent)}>
71
+ {#if _iconHtml}
72
+ <div class={twMerge("icon", classIcon)}>
73
+ {@html _iconHtml}
74
+ </div>
75
+ {/if}
76
+
77
+ <div class={twMerge("content", classContent)}>
67
78
  <Thc thc={_message} {forceAsHtml} />
68
79
  </div>
69
80
 
70
81
  {#if typeof onDismiss === "function"}
71
- <button
72
- onclick={() => onDismiss()}
73
- class={twMerge(
74
- "dismiss",
75
- `hover:bg-neutral-950/5 dark:hover:bg-neutral-950/20
76
- focus-visible:bg-neutral-950/5 focus-visible:hover:bg-neutral-950/20 focus-visible:ring-0
77
- rounded rounded-l-none
78
- px-3
79
- flex items-center justify-center
80
- group`,
81
- classDismiss
82
- )}
83
- type="button"
84
- >
85
- <X
86
- class={twMerge("x", "opacity-75 group-hover:opacity-100", classX)}
87
- strokeWidth={1.5}
82
+ <div class="dismiss">
83
+ <Button
84
+ x
85
+ class="text-inherit!"
86
+ variant="ghost"
87
+ roundedFull
88
+ size="sm"
89
+ type="button"
90
+ onclick={() => onDismiss()}
88
91
  />
89
- </button>
92
+ </div>
90
93
  {/if}
91
94
  </div>
92
95
  {/if}
@@ -1,15 +1,16 @@
1
- import type { TW_COLORS } from "../../types.js";
2
1
  import type { THC } from "../Thc/Thc.svelte";
2
+ export type MessageIntent = "destructive" | "warning" | "success" | "info";
3
3
  export interface Props {
4
4
  class?: string;
5
5
  classContent?: string;
6
- classDismiss?: string;
7
- classX?: string;
8
- message: THC | Error;
9
- theme?: TW_COLORS;
6
+ classIcon?: string;
7
+ message: THC | Error | undefined | null;
8
+ intent?: MessageIntent;
10
9
  forceAsHtml?: boolean;
11
10
  duration?: number;
12
11
  onDismiss?: (() => void) | null | false;
12
+ withIcon?: boolean;
13
+ iconFn?: (() => string) | false;
13
14
  }
14
15
  import "./index.css";
15
16
  declare const DismissibleMessage: import("svelte").Component<Props, {}, "">;