@marianmeres/stuic 3.4.3 → 3.4.4

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.
package/README.md CHANGED
@@ -1,8 +1,9 @@
1
1
  # @marianmeres/stuic
2
2
 
3
- **S**velte **T**ailwind **UI** **C**omponents
3
+ [![NPM](https://img.shields.io/npm/v/@marianmeres/stuic)](https://www.npmjs.com/package/@marianmeres/stuic)
4
+ [![License](https://img.shields.io/npm/l/@marianmeres/stuic)](LICENSE)
4
5
 
5
- An opinionated Svelte 5 component library built with Tailwind CSS v4. Featuring a centralized design token system for consistent theming across all components.
6
+ **S**velte **T**ailwind **UI** **C**omponents — an opinionated Svelte 5 component library built with Tailwind CSS v4. Featuring a centralized design token system for consistent theming across all components.
6
7
 
7
8
  ## Installation
8
9
 
@@ -10,30 +11,28 @@ An opinionated Svelte 5 component library built with Tailwind CSS v4. Featuring
10
11
  npm install @marianmeres/stuic
11
12
  ```
12
13
 
13
- ## Quick Start
14
+ ## Usage
14
15
 
15
16
  ```svelte
16
17
  <script>
17
18
  import { Button, Modal } from "@marianmeres/stuic";
18
19
 
19
- let modal;
20
+ let open = $state(false);
20
21
  </script>
21
22
 
22
- <Button onclick={() => modal.open()}>Open Modal</Button>
23
+ <Button onclick={() => open = true}>Open Modal</Button>
23
24
 
24
- <Modal bind:this={modal}>
25
+ <Modal bind:open>
25
26
  <p>Hello from Modal!</p>
26
27
  </Modal>
27
28
  ```
28
29
 
29
30
  ## Theming System
30
31
 
31
- STUIC uses a 3-layer CSS variable token system that enables both global theming and per-component customization.
32
-
33
- ### Architecture
32
+ STUIC uses a 3-layer CSS variable token system:
34
33
 
35
34
  ```
36
- Layer 1: Global Semantic Tokens (--stuic-accent, --stuic-surface, etc.)
35
+ Layer 1: Theme Tokens (--stuic-color-*)
37
36
  ↓ (used as fallback defaults)
38
37
  Layer 2: Component Tokens (--stuic-button-bg, --stuic-input-accent, etc.)
39
38
  ↓ (Tailwind utility class references)
@@ -42,20 +41,16 @@ Layer 3: Instance Overrides (inline styles, class props)
42
41
 
43
42
  ### Global Theming
44
43
 
45
- Override global tokens in your app's CSS to change the entire library's appearance:
44
+ Override theme tokens in your app's CSS:
46
45
 
47
46
  ```css
48
- /* app.css */
49
47
  :root {
50
- /* Change the accent color globally */
51
- --stuic-accent: #6366f1; /* Indigo brand color */
52
- --stuic-accent-hover: #4f46e5;
53
- --stuic-accent-active: #4338ca;
48
+ --stuic-color-primary: #6366f1;
49
+ --stuic-color-primary-hover: #4f46e5;
54
50
  }
55
51
 
56
- .dark {
57
- --stuic-accent: #818cf8;
58
- --stuic-accent-hover: #a5b4fc;
52
+ :root.dark {
53
+ --stuic-color-primary: #818cf8;
59
54
  }
60
55
  ```
61
56
 
@@ -65,232 +60,90 @@ Override specific component tokens:
65
60
 
66
61
  ```css
67
62
  :root {
68
- /* Only change switches to green */
69
- --stuic-switch-accent: #10b981;
70
-
71
- /* Custom button colors */
72
- --stuic-button-bg: #f3f4f6;
73
- --stuic-button-bg-hover: #e5e7eb;
63
+ --stuic-button-radius: 9999px; /* Pill buttons */
64
+ --stuic-switch-accent: #10b981; /* Green switches */
74
65
  }
75
66
  ```
76
67
 
77
68
  ### Instance Overrides
78
69
 
79
- Use class props or inline styles for one-off customizations:
70
+ Use `class` props or inline styles:
80
71
 
81
72
  ```svelte
82
73
  <Button class="bg-purple-500 hover:bg-purple-600 text-white">
83
74
  Custom Button
84
75
  </Button>
85
76
 
86
- <div style="--stuic-list-item-button-bg-hover: var(--color-blue-500);">
87
- <ListItemButton>Blue hover</ListItemButton>
88
- </div>
89
- ```
90
-
91
- ## Global Design Tokens
92
-
93
- All tokens are defined in `src/lib/theme.css`. See the full reference below.
94
-
95
- ### Accent Colors
96
-
97
- | Token | Light Mode | Dark Mode | Description |
98
- |-------|------------|-----------|-------------|
99
- | `--stuic-accent` | `sky-600` | `sky-400` | Primary accent for interactive elements |
100
- | `--stuic-accent-hover` | `sky-700` | `sky-300` | Accent hover state |
101
- | `--stuic-accent-active` | `sky-800` | `sky-200` | Accent active/pressed state |
102
- | `--stuic-accent-destructive` | `red-600` | `red-400` | Destructive/error accent |
103
- | `--stuic-accent-destructive-hover` | `red-700` | `red-300` | Destructive hover state |
104
-
105
- ### Surface Colors
106
-
107
- | Token | Light Mode | Dark Mode | Description |
108
- |-------|------------|-----------|-------------|
109
- | `--stuic-surface` | `white` | `neutral-900` | Base page background |
110
- | `--stuic-surface-elevated` | `white` | `neutral-800` | Cards, modals, popovers |
111
- | `--stuic-surface-sunken` | `neutral-100` | `neutral-700` | Input backgrounds, wells |
112
- | `--stuic-surface-overlay` | `neutral-800` | `neutral-950` | Backdrops, tooltips |
113
- | `--stuic-surface-interactive` | `neutral-200` | `neutral-600` | Buttons, list items |
114
- | `--stuic-surface-interactive-hover` | `neutral-500` | `neutral-200` | Interactive hover |
115
- | `--stuic-surface-interactive-active` | `neutral-600` | `neutral-100` | Interactive active |
116
-
117
- ### Text Colors
118
-
119
- | Token | Light Mode | Dark Mode | Description |
120
- |-------|------------|-----------|-------------|
121
- | `--stuic-text` | `black` | `neutral-100` | Primary text |
122
- | `--stuic-text-muted` | `neutral-600` | `neutral-400` | Secondary/muted text |
123
- | `--stuic-text-inverse` | `white` | `neutral-900` | Text on dark backgrounds |
124
- | `--stuic-text-placeholder` | `neutral-400` | `neutral-500` | Placeholder text |
125
- | `--stuic-text-on-accent` | `white` | `neutral-950` | Text on accent backgrounds |
126
- | `--stuic-text-destructive` | `red-600` | `red-400` | Error/destructive text |
127
-
128
- ### Border Colors
129
-
130
- | Token | Light Mode | Dark Mode | Description |
131
- |-------|------------|-----------|-------------|
132
- | `--stuic-border` | `neutral-300` | `neutral-600` | Default border |
133
- | `--stuic-border-strong` | `neutral-400` | `neutral-500` | Emphasized border |
134
- | `--stuic-border-subtle` | `neutral-200` | `neutral-700` | Subtle/light border |
135
- | `--stuic-border-focus` | `sky-500` | `sky-400` | Focus ring border |
136
- | `--stuic-border-error` | `red-500` | `red-400` | Error state border |
137
-
138
- ### Other Tokens
139
-
140
- | Token | Default | Description |
141
- |-------|---------|-------------|
142
- | `--stuic-ring` | `sky-500` / `sky-400` | Focus ring color |
143
- | `--stuic-ring-offset` | `2px` | Focus ring offset |
144
- | `--stuic-ring-width` | `2px` | Focus ring width |
145
- | `--stuic-radius-sm` | `--radius-sm` | Small border radius |
146
- | `--stuic-radius` | `--radius-md` | Default border radius |
147
- | `--stuic-radius-lg` | `--radius-lg` | Large border radius |
148
- | `--stuic-radius-full` | `9999px` | Fully rounded |
149
- | `--stuic-transition-fast` | `100ms` | Fast transitions |
150
- | `--stuic-transition-normal` | `150ms` | Normal transitions |
151
- | `--stuic-transition-slow` | `300ms` | Slow transitions |
152
-
153
- ## Component Tokens
154
-
155
- Each component defines its own tokens that reference global tokens as defaults:
156
-
157
- | Component | Token Prefix | Key Tokens |
158
- |-----------|--------------|------------|
159
- | Button | `--stuic-button-*` | `bg`, `text`, `border`, `border-focus` |
160
- | Switch | `--stuic-switch-*` | `accent` |
161
- | Input | `--stuic-input-*` | `accent`, `accent-error` |
162
- | Progress | `--stuic-progress-*` | `bg`, `accent` |
163
- | ListItemButton | `--stuic-list-item-button-*` | `bg`, `text`, `border`, `bg-hover`, `text-hover`, etc. |
164
- | ButtonGroupRadio | `--stuic-button-group-*` | `bg`, `text`, `border`, `accent`, `bg-active`, `text-active` |
165
- | TabbedMenu | `--stuic-tabbed-menu-*` | `tab-bg`, `tab-text`, `tab-bg-active`, `tab-text-active`, `border` |
166
- | DismissibleMessage | `--stuic-dismissible-message-*` | `bg`, `text`, `border` |
167
- | Notifications | `--stuic-notification-*` | `bg`, `text`, `border` |
168
- | Tooltip | `--stuic-tooltip-*` | `bg`, `text` |
169
- | Popover | `--stuic-popover-*` | `bg`, `text`, `border` |
170
- | Skeleton | `--stuic-skeleton-*` | `bg`, `bg-highlight`, `duration` |
171
-
172
- ## CSS Variable Naming Convention
173
-
174
- **STRICT REQUIREMENT**: All CSS variables follow this pattern:
175
-
176
- ```
177
- --stuic-{component}-{element?}-{property}-{state?}
77
+ <!-- Or use unstyled mode for full control -->
78
+ <Button unstyled class="my-custom-button">
79
+ Fully Custom
80
+ </Button>
178
81
  ```
179
82
 
180
- - **Full component names** (no abbreviations): `list-item-button` not `lib`
181
- - **State at end**: `--stuic-button-bg-hover` not `--stuic-button-hover-bg`
182
- - **No `-dark` suffix**: Dark mode defined in `.dark {}` selector
183
- - **Properties**: `bg`, `text`, `border`, `ring`, `shadow`, `accent`
184
- - **States**: `hover`, `active`, `focus`, `disabled`, `error`
83
+ ### Dark Mode
185
84
 
186
- ### Examples
85
+ Add `class="dark"` to the `<html>` element. All tokens switch automatically — no `dark:` Tailwind prefix needed.
187
86
 
188
- ```css
189
- /* Correct */
190
- --stuic-button-bg
191
- --stuic-button-bg-hover
192
- --stuic-list-item-button-text-active
193
- --stuic-input-accent-error
194
-
195
- /* Incorrect */
196
- --stuic-btn-bg /* abbreviated component name */
197
- --stuic-button-hover-bg /* state not at end */
198
- --stuic-button-bg-dark /* -dark suffix */
199
- --color-lib-hover-bg /* old naming convention */
200
- ```
201
-
202
- ## Customization Approaches
203
-
204
- ### 1. CSS Variables (Recommended)
87
+ ### Themes
205
88
 
206
- Set variables in your CSS for theming:
89
+ 26 pre-built themes available. Default: `stone`.
207
90
 
208
91
  ```css
209
- :root {
210
- --stuic-accent: #6366f1;
211
- }
212
- ```
213
-
214
- ### 2. Class Props
215
-
216
- Pass Tailwind classes directly to components:
217
-
218
- ```svelte
219
- <Button class="bg-linear-to-r from-purple-500 to-pink-500">
220
- Gradient Button
221
- </Button>
222
- ```
223
-
224
- ### 3. Unstyled Mode
225
-
226
- Use `unstyled` prop to remove all default styling:
227
-
228
- ```svelte
229
- <Button unstyled class="my-custom-button-class">
230
- Fully Custom
231
- </Button>
92
+ /* Use a different theme */
93
+ @import "@marianmeres/stuic/dist/themes/css/blue-orange.css";
232
94
  ```
233
95
 
234
96
  ## Components
235
97
 
236
- See `src/lib/README.md` for the full component list and API documentation.
237
-
238
98
  ### Layout & Overlays
239
- - AppShell, Backdrop, Modal, ModalDialog, Drawer
99
+ AppShell, Backdrop, Modal, ModalDialog, Drawer, Collapsible, SlidingPanels, Nav
240
100
 
241
101
  ### Forms & Inputs
242
- - FieldInput, FieldTextarea, FieldSelect, FieldCheckbox, FieldRadios, FieldFile, FieldAssets, FieldOptions, FieldKeyValues, FieldSwitch, Fieldset
102
+ FieldInput, FieldTextarea, FieldSelect, FieldCheckbox, FieldRadios, FieldFile, FieldAssets, FieldOptions, FieldKeyValues, FieldSwitch, FieldInputLocalized, FieldLikeButton, Fieldset
243
103
 
244
104
  ### Buttons & Controls
245
- - Button, ButtonGroupRadio, Switch, ListItemButton, X
105
+ Button, ButtonGroupRadio, Switch, TwCheck, ListItemButton, X
246
106
 
247
107
  ### Feedback & Notifications
248
- - Notifications, AlertConfirmPrompt, DismissibleMessage, Progress, Spinner, Skeleton
108
+ Notifications, AlertConfirmPrompt, DismissibleMessage, Progress, Spinner, Skeleton
249
109
 
250
110
  ### Navigation & Menus
251
- - CommandMenu, DropdownMenu, TabbedMenu, TypeaheadInput, KbdShortcut
111
+ CommandMenu, DropdownMenu, TabbedMenu, TypeaheadInput, KbdShortcut
252
112
 
253
- ### Utilities
254
- - ColorScheme, Thc, SlidingPanels, HoverExpandableWidth, AnimatedElipsis
113
+ ### Display & Utility
114
+ Avatar, Carousel, AnimatedElipsis, ThemePreview, ColorScheme, Thc, HoverExpandableWidth, AssetsPreview
255
115
 
256
116
  ## Actions
257
117
 
258
118
  ```svelte
259
- <input use:autogrow use:validate={{ required: true }} />
260
- <button use:tooltip aria-label="Tooltip text">Hover me</button>
261
- <div use:popover={{ content: 'Popover content' }}>Anchor</div>
119
+ <textarea use:autogrow />
120
+ <input use:validate={() => ({ customValidator: (v) => !v && "Required" })} />
121
+ <input use:trim />
122
+ <button use:tooltip aria-label="Save">Save</button>
123
+ <div use:focusTrap>...</div>
124
+ <div use:fileDropzone={() => ({ onDrop: handleFiles })}>Drop here</div>
262
125
  ```
263
126
 
264
- - `autogrow` - Auto-expand textarea height
265
- - `validate` - Form validation with custom validators
266
- - `focusTrap` - Trap focus within element
267
- - `tooltip` - Tooltip from aria-label
268
- - `popover` - Anchored popover
269
- - `fileDropzone` - Drag-and-drop file upload
270
- - `highlightDragover` - Visual feedback for drag operations
127
+ `autogrow` · `validate` · `focusTrap` · `autoscroll` · `fileDropzone` · `highlightDragover` · `resizableWidth` · `trim` · `typeahead` · `onSubmitValidityCheck` · `popover` · `tooltip`
271
128
 
272
129
  ## TypeScript
273
130
 
274
131
  All components export their Props types:
275
132
 
276
133
  ```ts
277
- import type { ButtonProps, ModalProps, ListItemButtonProps } from "@marianmeres/stuic";
134
+ import type { ButtonProps, ModalProps, FieldInputProps } from "@marianmeres/stuic";
278
135
  ```
279
136
 
137
+ ## API
138
+
139
+ See [API.md](API.md) for complete API documentation including all component props, actions, utilities, icons, and design token reference.
140
+
280
141
  ## Requirements
281
142
 
282
143
  - Svelte 5 (runes mode)
283
144
  - Tailwind CSS v4
284
145
  - Modern browser with CSS custom properties support
285
146
 
286
- ## Breaking Changes in v2
287
-
288
- - All CSS variables renamed from `--color-*` to `--stuic-*` prefix
289
- - ListItemButton variables renamed from `--color-lib-*` to `--stuic-list-item-button-*`
290
- - State naming changed from `--*-hover-bg` to `--*-bg-hover` (state at end)
291
- - Removed `-dark` suffix from variables (use `.dark {}` selector instead)
292
- - Legacy variable names preserved as aliases for backwards compatibility
293
-
294
147
  ## License
295
148
 
296
- MIT
149
+ [MIT](LICENSE)
@@ -163,13 +163,15 @@
163
163
  {...rest as any}
164
164
  {tabindex}
165
165
  >
166
- {#if typeof rendered === "function"}
167
- {@render rendered(value)}
168
- {:else if rendered}
169
- {@html rendered}
170
- {:else}
171
- &nbsp;
172
- {/if}
166
+ <span class="block truncate">
167
+ {#if typeof rendered === "function"}
168
+ {@render rendered(value)}
169
+ {:else if rendered}
170
+ {@html rendered}
171
+ {:else}
172
+ &nbsp;
173
+ {/if}
174
+ </span>
173
175
  </Button>
174
176
 
175
177
  <input
@@ -94,7 +94,7 @@
94
94
  cardinality_of: "of max",
95
95
  cardinality_selected: "selected",
96
96
  submit: "Submit",
97
- select_all: "Select results",
97
+ select_all: "Select all",
98
98
  clear_all: "Clear selected",
99
99
  clear: "Clear",
100
100
  search_placeholder: "Type to search...",
@@ -561,7 +561,7 @@
561
561
  bind:this={modalDialog}
562
562
  preEscapeClose={escape}
563
563
  classDialog="items-start"
564
- class={twMerge("w-full max-w-2xl bg-transparent! shadow-none! pointer-events-none")}
564
+ class={twMerge("w-full max-w-2xl bg-transparent shadow-none pointer-events-none")}
565
565
  ariaLabelledby={id}
566
566
  {noScrollLock}
567
567
  >
@@ -569,7 +569,7 @@
569
569
  <div class="pointer-events-auto">
570
570
  <InputWrap
571
571
  size={renderSize}
572
- class={twMerge("m-2 mb-12 shadow-xl", classModalField)}
572
+ class={twMerge("m-1 sm:m-2 mb-12 shadow-xl", classModalField)}
573
573
  classInputBoxWrap={twMerge(
574
574
  // always look like focused
575
575
  `border border-(--stuic-input-accent)`,
@@ -88,7 +88,6 @@
88
88
  });
89
89
 
90
90
  let hasLabel = $derived(isTHCNotEmpty(label) || typeof label === "function");
91
-
92
91
  </script>
93
92
 
94
93
  {#snippet snippetOrThc({ id, value }: { id: string; value?: SnippetWithId | THC })}
@@ -140,7 +139,7 @@
140
139
 
141
140
  <div
142
141
  class={twMerge(
143
- "input-box",
142
+ "input-box min-w-0",
144
143
  hasLabel && labelLeft && labelLeftWidth === "normal" && "flex-3",
145
144
  hasLabel && labelLeft && labelLeftWidth === "wide" && "flex-2",
146
145
  classInputBox
@@ -164,24 +163,14 @@
164
163
  {#if validation && !validation?.valid}
165
164
  <div
166
165
  transition:slide={{ duration: 150 }}
167
- class={twMerge(
168
- "validation-box",
169
- "my-1 text-sm px-2",
170
- classValidationBox
171
- )}
166
+ class={twMerge("validation-box", "my-1 text-sm px-2", classValidationBox)}
172
167
  >
173
168
  {validation.message}
174
169
  </div>
175
170
  {/if}
176
171
 
177
172
  {#if description}
178
- <div
179
- class={twMerge(
180
- "desc-box",
181
- "mx-2 mt-1 text-sm opacity-50",
182
- classDescBox
183
- )}
184
- >
173
+ <div class={twMerge("desc-box", "mx-2 mt-1 text-sm opacity-50", classDescBox)}>
185
174
  {#if descriptionCollapsible}
186
175
  <Collapsible
187
176
  expanded={descriptionDefaultExpanded}
@@ -62,7 +62,7 @@
62
62
  setOpener(
63
63
  openerOrEvent instanceof MouseEvent
64
64
  ? (openerOrEvent.currentTarget as HTMLElement)
65
- : openerOrEvent ?? (document.activeElement as HTMLElement)
65
+ : (openerOrEvent ?? (document.activeElement as HTMLElement))
66
66
  );
67
67
  // dialog must be rendered in the DOM before it can be opened...
68
68
  waitForNextRepaint().then(() => {
@@ -134,7 +134,10 @@
134
134
  aria-describedby={ariaDescribedby}
135
135
  class={twMerge(
136
136
  "stuic-modal-dialog",
137
- "fixed inset-4 m-auto size-auto",
137
+ "fixed m-auto size-auto",
138
+ // Note that the default browser styling is (so we are not touching the edge):
139
+ // max-width/height: calc((100% - 6px) - 2em);
140
+ "p-0 inset-0",
138
141
  "flex justify-center items-center",
139
142
  "focus:outline-none focus-visible:outline-none",
140
143
  "bg-transparent",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marianmeres/stuic",
3
- "version": "3.4.3",
3
+ "version": "3.4.4",
4
4
  "files": [
5
5
  "dist",
6
6
  "!dist/**/*.test.*",