@marianmeres/stuic 2.66.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 +12 -5
  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
@@ -0,0 +1,128 @@
1
+ /* ============================================================================
2
+ AVATAR COMPONENT TOKENS
3
+ Override globally: :root { --stuic-avatar-radius: 0.5rem; }
4
+ Override locally: <Avatar style="--stuic-avatar-radius: 0;">
5
+ ============================================================================ */
6
+
7
+ :root {
8
+ /* Component-level customization tokens */
9
+ --stuic-avatar-radius: 9999px;
10
+ --stuic-avatar-font-weight: var(--font-weight-medium);
11
+ --stuic-avatar-transition: 150ms;
12
+
13
+ /* Default colors (uses muted role colors) */
14
+ --stuic-avatar-bg: var(--stuic-color-muted);
15
+ --stuic-avatar-fg: var(--stuic-color-muted-foreground);
16
+
17
+ /* Focus ring (for interactive/button mode) */
18
+ --stuic-avatar-ring-width: 3px;
19
+ --stuic-avatar-ring-offset: 0px;
20
+ --stuic-avatar-ring-color: var(--stuic-color-ring);
21
+
22
+ /* Size: sm */
23
+ --stuic-avatar-size-sm: calc(var(--spacing) * 8);
24
+ --stuic-avatar-font-size-sm: var(--text-xs);
25
+
26
+ /* Size: md */
27
+ --stuic-avatar-size-md: calc(var(--spacing) * 10);
28
+ --stuic-avatar-font-size-md: var(--text-base);
29
+
30
+ /* Size: lg */
31
+ --stuic-avatar-size-lg: calc(var(--spacing) * 12);
32
+ --stuic-avatar-font-size-lg: var(--text-lg);
33
+
34
+ /* Size: xl */
35
+ --stuic-avatar-size-xl: calc(var(--spacing) * 14);
36
+ --stuic-avatar-font-size-xl: var(--text-xl);
37
+
38
+ /* Size: 2xl */
39
+ --stuic-avatar-size-2xl: calc(var(--spacing) * 16);
40
+ --stuic-avatar-font-size-2xl: var(--text-2xl);
41
+ }
42
+
43
+ /* ============================================================================
44
+ BASE STYLES
45
+ ============================================================================ */
46
+
47
+ .stuic-avatar {
48
+ /* Layout */
49
+ display: inline-flex;
50
+ align-items: center;
51
+ justify-content: center;
52
+ flex-shrink: 0;
53
+ overflow: hidden;
54
+
55
+ /* Typography */
56
+ font-weight: var(--stuic-avatar-font-weight);
57
+
58
+ /* Box model */
59
+ border-radius: var(--stuic-avatar-radius);
60
+
61
+ /* Colors (can be overridden by autoColor inline styles) */
62
+ background: var(--stuic-avatar-bg);
63
+ color: var(--stuic-avatar-fg);
64
+
65
+ /* Transition for interactive mode */
66
+ transition:
67
+ background var(--stuic-avatar-transition),
68
+ color var(--stuic-avatar-transition),
69
+ opacity var(--stuic-avatar-transition);
70
+ }
71
+
72
+ /* ============================================================================
73
+ SIZE VARIANTS
74
+ ============================================================================ */
75
+
76
+ .stuic-avatar[data-size="sm"] {
77
+ width: var(--stuic-avatar-size-sm);
78
+ height: var(--stuic-avatar-size-sm);
79
+ font-size: var(--stuic-avatar-font-size-sm);
80
+ }
81
+
82
+ .stuic-avatar[data-size="md"] {
83
+ width: var(--stuic-avatar-size-md);
84
+ height: var(--stuic-avatar-size-md);
85
+ font-size: var(--stuic-avatar-font-size-md);
86
+ }
87
+
88
+ .stuic-avatar[data-size="lg"] {
89
+ width: var(--stuic-avatar-size-lg);
90
+ height: var(--stuic-avatar-size-lg);
91
+ font-size: var(--stuic-avatar-font-size-lg);
92
+ }
93
+
94
+ .stuic-avatar[data-size="xl"] {
95
+ width: var(--stuic-avatar-size-xl);
96
+ height: var(--stuic-avatar-size-xl);
97
+ font-size: var(--stuic-avatar-font-size-xl);
98
+ }
99
+
100
+ .stuic-avatar[data-size="2xl"] {
101
+ width: var(--stuic-avatar-size-2xl);
102
+ height: var(--stuic-avatar-size-2xl);
103
+ font-size: var(--stuic-avatar-font-size-2xl);
104
+ }
105
+
106
+ /* ============================================================================
107
+ INTERACTIVE MODE (button)
108
+ ============================================================================ */
109
+
110
+ .stuic-avatar[data-interactive] {
111
+ cursor: pointer;
112
+ user-select: none;
113
+ -webkit-tap-highlight-color: transparent;
114
+ }
115
+
116
+ .stuic-avatar[data-interactive]:hover {
117
+ opacity: 0.85;
118
+ }
119
+
120
+ .stuic-avatar[data-interactive]:active {
121
+ opacity: 0.75;
122
+ }
123
+
124
+ /* Focus styles */
125
+ .stuic-avatar[data-interactive]:focus-visible {
126
+ outline: var(--stuic-avatar-ring-width) solid var(--stuic-avatar-ring-color);
127
+ outline-offset: var(--stuic-avatar-ring-offset);
128
+ }
@@ -1,7 +1,7 @@
1
1
  <script lang="ts" module>
2
2
  import type { Snippet } from "svelte";
3
3
  import type { FocusTrapOptions } from "../../actions/focus-trap.js";
4
- import { BodyScroll, focusTrap as focusTrapAction, twMerge } from "../../index.js";
4
+ import { BodyScroll, focusTrap as focusTrapAction } from "../../index.js";
5
5
  import { createClog } from "@marianmeres/clog";
6
6
  import { watch } from "runed";
7
7
  import { onDestroy } from "svelte";
@@ -26,6 +26,9 @@
26
26
  </script>
27
27
 
28
28
  <script lang="ts">
29
+ import "./index.css";
30
+ import { twMerge } from "../../utils/tw-merge.js";
31
+
29
32
  const clog = createClog("Backdrop", { color: "auto" });
30
33
 
31
34
  let {
@@ -142,6 +145,9 @@
142
145
  }
143
146
  return opts;
144
147
  });
148
+
149
+ // Build class string - add base class for CSS targeting, allow user overrides
150
+ let _class = $derived(twMerge("stuic-backdrop", classProp));
145
151
  </script>
146
152
 
147
153
  {#if visible}
@@ -149,7 +155,7 @@
149
155
  bind:this={el}
150
156
  role="presentation"
151
157
  tabindex="-1"
152
- class={twMerge("fixed inset-0 flex z-10 h-dvh", classProp)}
158
+ class={_class}
153
159
  in:fade={{ duration: fadeInDuration }}
154
160
  out:fade={{ duration: fadeOutDuration }}
155
161
  use:focusTrapAction={focusTrapOptions}
@@ -13,6 +13,7 @@ export interface Props extends Record<string, any> {
13
13
  visible?: boolean;
14
14
  noScrollLock?: boolean;
15
15
  }
16
+ import "./index.css";
16
17
  declare const Backdrop: import("svelte").Component<Props, {
17
18
  close: () => void;
18
19
  open: (openerOrEvent?: null | HTMLElement | MouseEvent) => void;
@@ -12,9 +12,10 @@ A semi-transparent overlay with focus trap and body scroll locking. Commonly use
12
12
  | `fadeOutDuration` | `number` | `150` | Fade out transition duration (ms) |
13
13
  | `transitionEnabled` | `boolean` | `true` | Enable/disable transitions |
14
14
  | `onEscape` | `() => void` | - | Callback when Escape key is pressed |
15
+ | `onBackdropClick` | `() => void` | - | Callback when backdrop area is clicked |
15
16
  | `noScrollLock` | `boolean` | `false` | Disable body scroll locking |
16
17
  | `el` | `HTMLDivElement` | - | Element reference (bindable) |
17
- | `class` | `string` | - | CSS classes for backdrop |
18
+ | `class` | `string` | - | Additional CSS classes |
18
19
 
19
20
  ## Methods
20
21
 
@@ -25,13 +26,52 @@ A semi-transparent overlay with focus trap and body scroll locking. Commonly use
25
26
  | `setOpener(el)` | Set element to refocus when closed |
26
27
  | `visibility()` | Returns object with `visible` getter |
27
28
 
29
+ ## CSS Variables
30
+
31
+ Override these tokens to customize appearance:
32
+
33
+ | Variable | Default | Description |
34
+ |----------|---------|-------------|
35
+ | `--stuic-backdrop-bg` | `rgb(0 0 0 / 0.5)` | Background color |
36
+ | `--stuic-backdrop-z-index` | `10` | Stacking order |
37
+
38
+ ### Global Override
39
+
40
+ ```css
41
+ :root {
42
+ --stuic-backdrop-bg: rgb(0 0 0 / 0.7);
43
+ --stuic-backdrop-z-index: 50;
44
+ }
45
+ ```
46
+
47
+ ### Local Override
48
+
49
+ ```svelte
50
+ <Backdrop
51
+ bind:visible
52
+ style="--stuic-backdrop-bg: rgb(0 0 0 / 0.8);"
53
+ >
54
+ ...
55
+ </Backdrop>
56
+ ```
57
+
58
+ ### Tailwind Class Override
59
+
60
+ You can still override styles using Tailwind classes via the `class` prop:
61
+
62
+ ```svelte
63
+ <Backdrop bind:visible class="bg-black/25">
64
+ ...
65
+ </Backdrop>
66
+ ```
67
+
28
68
  ## Usage
29
69
 
30
70
  ### Basic Backdrop
31
71
 
32
72
  ```svelte
33
73
  <script lang="ts">
34
- import { Backdrop } from 'stuic';
74
+ import { Backdrop } from '@marianmeres/stuic';
35
75
 
36
76
  let visible = $state(false);
37
77
  </script>
@@ -41,7 +81,7 @@ A semi-transparent overlay with focus trap and body scroll locking. Commonly use
41
81
  <Backdrop
42
82
  bind:visible
43
83
  onEscape={() => visible = false}
44
- class="bg-black/50"
84
+ onBackdropClick={() => visible = false}
45
85
  >
46
86
  <div class="m-auto p-8 bg-white rounded">
47
87
  Modal content
@@ -54,7 +94,7 @@ A semi-transparent overlay with focus trap and body scroll locking. Commonly use
54
94
 
55
95
  ```svelte
56
96
  <script lang="ts">
57
- import { Backdrop } from 'stuic';
97
+ import { Backdrop } from '@marianmeres/stuic';
58
98
 
59
99
  let backdrop: Backdrop;
60
100
  </script>
@@ -64,7 +104,6 @@ A semi-transparent overlay with focus trap and body scroll locking. Commonly use
64
104
  <Backdrop
65
105
  bind:this={backdrop}
66
106
  onEscape={() => backdrop.close()}
67
- class="bg-black/50"
68
107
  >
69
108
  <div class="m-auto p-8 bg-white rounded">
70
109
  Content here
@@ -72,10 +111,36 @@ A semi-transparent overlay with focus trap and body scroll locking. Commonly use
72
111
  </Backdrop>
73
112
  ```
74
113
 
114
+ ### Custom Styling
115
+
116
+ ```svelte
117
+ <!-- Lighter overlay -->
118
+ <Backdrop bind:visible class="bg-black/25">
119
+ <div>Overlay content</div>
120
+ </Backdrop>
121
+
122
+ <!-- Blur effect with CSS variable override -->
123
+ <Backdrop
124
+ bind:visible
125
+ class="backdrop-blur-sm"
126
+ style="--stuic-backdrop-bg: rgb(0 0 0 / 0.3);"
127
+ >
128
+ <div>Blurred backdrop content</div>
129
+ </Backdrop>
130
+
131
+ <!-- Higher z-index for stacking -->
132
+ <Backdrop
133
+ bind:visible
134
+ style="--stuic-backdrop-z-index: 100;"
135
+ >
136
+ <div>High priority overlay</div>
137
+ </Backdrop>
138
+ ```
139
+
75
140
  ### Without Scroll Lock
76
141
 
77
142
  ```svelte
78
- <Backdrop bind:visible noScrollLock class="bg-black/25">
143
+ <Backdrop bind:visible noScrollLock>
79
144
  <div>Overlay content - page still scrollable</div>
80
145
  </Backdrop>
81
146
  ```
@@ -0,0 +1,29 @@
1
+ /* ============================================================================
2
+ BACKDROP COMPONENT TOKENS
3
+ Override globally: :root { --stuic-backdrop-bg: rgb(0 0 0 / 0.3); }
4
+ Override locally: <Backdrop style="--stuic-backdrop-bg: rgb(0 0 0 / 0.8);">
5
+ ============================================================================ */
6
+
7
+ :root {
8
+ /* Component-level customization tokens */
9
+ --stuic-backdrop-bg: rgb(0 0 0 / 0.5);
10
+ --stuic-backdrop-z-index: 10;
11
+ }
12
+
13
+ /* ============================================================================
14
+ BASE STYLES
15
+ ============================================================================ */
16
+
17
+ .stuic-backdrop {
18
+ /* Layout */
19
+ position: fixed;
20
+ inset: 0;
21
+ display: flex;
22
+ height: 100dvh;
23
+
24
+ /* Stacking */
25
+ z-index: var(--stuic-backdrop-z-index);
26
+
27
+ /* Colors */
28
+ background: var(--stuic-backdrop-bg);
29
+ }
@@ -1,121 +1,80 @@
1
1
  <script lang="ts" module>
2
+ import type { HTMLButtonAttributes, HTMLAnchorAttributes } from "svelte/elements";
2
3
  import type { Snippet } from "svelte";
3
- import type { HTMLButtonAttributes } from "svelte/elements";
4
+ import type { IntentColorKey } from "../../utils/design-tokens.js";
5
+
6
+ export type ButtonVariant = "solid" | "outline" | "ghost" | "soft" | "link";
7
+ export type ButtonSize = "sm" | "md" | "lg" | "xl";
4
8
 
5
9
  export interface Props extends Omit<HTMLButtonAttributes, "children"> {
6
- /** Style variant (primary = accent colors, secondary = neutral) */
7
- variant?: "primary" | "secondary" | string;
8
- size?: "sm" | "md" | "lg" | string;
9
- /** Reduce text contrast for less emphasis */
10
+ /** Color intent (semantic meaning) */
11
+ intent?: IntentColorKey;
12
+ /** Visual variant (how colors are applied) */
13
+ variant?: ButtonVariant | string;
14
+ /** Size preset */
15
+ size?: ButtonSize | string;
16
+ /** Reduce emphasis */
10
17
  muted?: boolean;
11
- noshadow?: boolean;
12
- noborder?: boolean;
18
+ /** 3D push effect */
19
+ raised?: boolean;
13
20
  /** Skip all default styling, use only custom classes */
14
21
  unstyled?: boolean;
15
- /** Transparent bg, styled on hover */
16
- inverse?: boolean;
22
+ /** Render as rounded-full */
23
+ roundedFull?: boolean;
24
+ /** Render as aspect ratio 1 */
25
+ aspect1?: boolean;
26
+ /** Additional CSS classes */
17
27
  class?: string;
18
28
  /** Render as anchor tag instead of button */
19
29
  href?: string;
30
+ /** Content snippet */
20
31
  children?: Snippet<[{ checked?: boolean }]>;
21
- /** Enable switch/toggle behavior (uses aria-checked) */
22
- roleSwitch?: boolean;
32
+ /** Toggle state for switch behavior */
23
33
  checked?: boolean;
24
- el?: Element;
25
- /* */
26
- tooltip?: TooltipConfig;
27
- }
28
-
29
- export interface ButtonPresetClasses {
30
- size: Record<string, string>;
31
- variant: Record<string, string>;
32
- muted: string;
33
- shadow: string;
34
- inverse: string;
34
+ /** Enable switch/toggle behavior */
35
+ roleSwitch?: boolean;
36
+ /** Bindable element reference */
37
+ el?: HTMLElement;
38
+ /** Optional tooltip configuration or direct content */
39
+ tooltip?: string | TooltipConfig;
40
+ /** Is this button a "X" button (this is a pragmatic convenience) */
41
+ x?: boolean | XProps;
42
+ /** Optional out-of-the-box spinner support */
43
+ spinner?: boolean | THC;
44
+ /** Show only spinner when spinner? */
45
+ spinnerOnly?: boolean;
35
46
  }
36
-
37
- export const BUTTON_STUIC_BASE_CLASSES = `
38
- bg-button-bg text-button-text
39
- dark:bg-button-bg-dark dark:text-button-text-dark
40
- text-base text-center
41
- leading-none
42
- border-1
43
- border-button-border dark:border-button-border-dark
44
- rounded-lg
45
- inline-flex items-center justify-center gap-x-2
46
- px-4 py-3
47
- select-none
48
- min-h-[44px] min-w-[44px]
49
-
50
- hover:brightness-105
51
- active:brightness-95
52
- disabled:hover:brightness-100 disabled:opacity-50
53
-
54
- focus:brightness-105
55
- focus:border-button-border-focus focus:dark:border-button-border-focus-dark
56
-
57
- focus:outline-4 focus:outline-black/10 focus:dark:outline-white/20
58
- focus-visible:outline-4 focus-visible:outline-black/10 focus-visible:dark:outline-white/20
59
- `;
60
-
61
- export const BUTTON_STUIC_PRESET_CLASSES: ButtonPresetClasses = {
62
- size: {
63
- sm: `text-sm rounded-md px-3 py-2 min-h-none min-w-none`,
64
- lg: `text-lg rounded-xl`,
65
- },
66
- variant: {
67
- primary: `font-medium`,
68
- secondary: `
69
- bg-neutral-100 dark:bg-neutral-600
70
- text-black/60 dark:text-white/80
71
- shadow-[1px_1px_0_0_rgba(0_0_0_/_.2)]
72
- active:shadow-none active:translate-[1px]
73
- focus:shadow-black/30
74
- `,
75
- },
76
- muted: `text-black/70 dark:text-white/70`,
77
- shadow: `
78
- shadow-[1px_1px_0_0_rgba(0_0_0_/_.4)]
79
- active:shadow-none active:translate-[1px]
80
- disabled:shadow-none disabled:active:shadow-none disabled:active:translate-none
81
- `,
82
- inverse: `
83
- bg-transparent dark:bg-transparent
84
- hover:bg-button-bg hover:dark:bg-button-bg-dark
85
- hover:brightness-100
86
- `,
87
- };
88
47
  </script>
89
48
 
90
49
  <script lang="ts">
91
- import { twMerge } from "../../utils/tw-merge.js";
92
- //
93
50
  import "./index.css";
94
- import { tooltip, type TooltipConfig } from "../../actions/index.js";
95
-
51
+ import { twMerge } from "../../utils/tw-merge.js";
52
+ import { tooltip, type TooltipConfig } from "../../actions/tooltip/tooltip.svelte.js";
53
+ import { X, type XProps } from "../X/index.js";
54
+ import Thc, { type THC } from "../Thc/Thc.svelte";
55
+ import Spinner from "../Spinner/Spinner.svelte";
96
56
  let {
97
- variant,
98
- size,
99
57
  class: classProp,
100
- muted,
101
- noshadow,
102
- noborder,
103
- inverse,
104
- unstyled,
58
+ intent,
59
+ size = "md",
60
+ variant = "solid",
105
61
  href,
106
62
  children,
107
- //
108
- roleSwitch = false,
109
63
  checked = $bindable(false),
64
+ roleSwitch = false,
110
65
  el = $bindable(),
111
- //
112
- tooltip: tooltipConfig = () => ({ enabled: false }),
113
- //
66
+ muted = false,
67
+ raised = false,
68
+ unstyled = false,
69
+ roundedFull = false,
70
+ aspect1 = false,
71
+ tooltip: _tooltip,
72
+ x,
73
+ spinner,
74
+ spinnerOnly,
114
75
  ...rest
115
76
  }: Props = $props();
116
77
 
117
- // let button: HTMLButtonElement | undefined = $state();
118
-
119
78
  $effect(() => {
120
79
  const toggle = () => (checked = !checked);
121
80
  if (!href && roleSwitch && el) {
@@ -124,51 +83,85 @@
124
83
  return () => el?.removeEventListener("click", toggle);
125
84
  });
126
85
 
127
- const _base = BUTTON_STUIC_BASE_CLASSES;
128
- const _preset: any = BUTTON_STUIC_PRESET_CLASSES;
86
+ // Build class string - add base class for CSS targeting unless unstyled
87
+ let _class = $derived(unstyled ? classProp : twMerge("stuic-button", classProp));
88
+
89
+ let _tooltipConfig: TooltipConfig = $derived.by(() => {
90
+ if (typeof _tooltip === "string") {
91
+ return () => ({ enabled: true, content: _tooltip });
92
+ }
93
+ return _tooltip ? _tooltip : () => ({ enabled: false });
94
+ });
95
+
96
+ let _xProps: undefined | XProps = $derived.by(() => {
97
+ if (x) {
98
+ return typeof x === "boolean" ? {} : x;
99
+ }
100
+ });
129
101
 
130
- // see button.css
131
- let _class = $derived(
132
- [
133
- // "namespace" (so we can target it in css files when customizing)
134
- "stuic-button",
135
- // pass all styling props as classnames as well
136
- variant,
137
- size,
138
- muted && "muted",
139
- noshadow && "no-shadow",
140
- noborder && "border-none",
141
- inverse && "inverse",
142
- // now, attach the default tw classes (unless not explicitly forbidden)
143
- !unstyled && _base,
144
- !unstyled && size && _preset.size[size],
145
- !unstyled && variant && _preset.variant[variant],
146
- !unstyled && muted && _preset.muted,
147
- !unstyled && !noshadow && _preset.shadow,
148
- !unstyled && inverse && _preset.inverse,
149
- ]
150
- .filter(Boolean)
151
- .join(" ")
152
- );
102
+ // "x" implicitly set aspect1
103
+ let _isAspect1 = $derived(aspect1 || _xProps);
153
104
  </script>
154
105
 
155
106
  {#if href}
156
107
  <a
157
108
  {href}
158
109
  bind:this={el}
159
- class={twMerge(_class, classProp)}
160
- use:tooltip={tooltipConfig}
161
- {...rest as any}
110
+ class={_class}
111
+ data-intent={!unstyled ? intent : undefined}
112
+ data-variant={!unstyled ? variant : undefined}
113
+ data-size={!unstyled ? size : undefined}
114
+ data-muted={!unstyled && muted ? "true" : undefined}
115
+ data-raised={!unstyled && raised ? "true" : undefined}
116
+ data-checked={roleSwitch && checked ? "true" : undefined}
117
+ data-rounded-full={!unstyled && roundedFull ? "true" : undefined}
118
+ data-aspect1={!unstyled && _isAspect1 ? "true" : undefined}
119
+ use:tooltip={_tooltipConfig}
120
+ {...rest as HTMLAnchorAttributes}
162
121
  >
163
- {@render children?.({})}
122
+ {#if _xProps}
123
+ <X {..._xProps} />
124
+ {:else}
125
+ {#if spinner}
126
+ {#if typeof spinner === "boolean"}
127
+ <Spinner />
128
+ {:else}
129
+ <Thc thc={spinner} />
130
+ {/if}
131
+ {/if}
132
+ {#if !spinner || (spinner && !spinnerOnly)}
133
+ {@render children?.({ checked })}
134
+ {/if}
135
+ {/if}
164
136
  </a>
165
137
  {:else}
166
138
  <button
167
139
  bind:this={el}
168
- class={twMerge(_class, classProp)}
169
- use:tooltip={tooltipConfig}
140
+ class={_class}
141
+ data-intent={!unstyled ? intent : undefined}
142
+ data-variant={!unstyled ? variant : undefined}
143
+ data-size={!unstyled ? size : undefined}
144
+ data-muted={!unstyled && muted ? "true" : undefined}
145
+ data-raised={!unstyled && raised ? "true" : undefined}
146
+ data-checked={roleSwitch && checked ? "true" : undefined}
147
+ data-rounded-full={!unstyled && roundedFull ? "true" : undefined}
148
+ data-aspect1={!unstyled && _isAspect1 ? "true" : undefined}
149
+ use:tooltip={_tooltipConfig}
170
150
  {...rest}
171
151
  >
172
- {@render children?.({ checked })}
152
+ {#if _xProps}
153
+ <X {..._xProps} />
154
+ {:else}
155
+ {#if spinner}
156
+ {#if typeof spinner === "boolean"}
157
+ <Spinner />
158
+ {:else}
159
+ <Thc thc={spinner} />
160
+ {/if}
161
+ {/if}
162
+ {#if !spinner || (spinner && !spinnerOnly)}
163
+ {@render children?.({ checked })}
164
+ {/if}
165
+ {/if}
173
166
  </button>
174
167
  {/if}