@marianmeres/stuic 3.4.2 → 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 +47 -194
- package/dist/actions/popover/popover.svelte.d.ts +0 -1
- package/dist/actions/popover/popover.svelte.js +4 -15
- package/dist/actions/tooltip/tooltip.svelte.d.ts +0 -1
- package/dist/actions/tooltip/tooltip.svelte.js +0 -2
- package/dist/components/Avatar/Avatar.svelte +4 -2
- package/dist/components/Avatar/index.css +1 -1
- package/dist/components/Button/Button.svelte +14 -2
- package/dist/components/Button/Button.svelte.d.ts +9 -1
- package/dist/components/DropdownMenu/DropdownMenu.svelte +8 -15
- package/dist/components/DropdownMenu/index.css +0 -25
- package/dist/components/Input/FieldLikeButton.svelte +9 -7
- package/dist/components/Input/FieldOptions.svelte +3 -3
- package/dist/components/Input/_internal/InputWrap.svelte +3 -14
- package/dist/components/ModalDialog/ModalDialog.svelte +5 -2
- package/dist/components/X/index.css +45 -0
- package/dist/index.css +5 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# @marianmeres/stuic
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@marianmeres/stuic)
|
|
4
|
+
[](LICENSE)
|
|
4
5
|
|
|
5
|
-
|
|
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
|
-
##
|
|
14
|
+
## Usage
|
|
14
15
|
|
|
15
16
|
```svelte
|
|
16
17
|
<script>
|
|
17
18
|
import { Button, Modal } from "@marianmeres/stuic";
|
|
18
19
|
|
|
19
|
-
let
|
|
20
|
+
let open = $state(false);
|
|
20
21
|
</script>
|
|
21
22
|
|
|
22
|
-
<Button onclick={() =>
|
|
23
|
+
<Button onclick={() => open = true}>Open Modal</Button>
|
|
23
24
|
|
|
24
|
-
<Modal bind:
|
|
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
|
|
32
|
-
|
|
33
|
-
### Architecture
|
|
32
|
+
STUIC uses a 3-layer CSS variable token system:
|
|
34
33
|
|
|
35
34
|
```
|
|
36
|
-
Layer 1:
|
|
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
|
|
44
|
+
Override theme tokens in your app's CSS:
|
|
46
45
|
|
|
47
46
|
```css
|
|
48
|
-
/* app.css */
|
|
49
47
|
:root {
|
|
50
|
-
|
|
51
|
-
--stuic-
|
|
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-
|
|
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
|
-
/*
|
|
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
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
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
|
-
|
|
85
|
+
Add `class="dark"` to the `<html>` element. All tokens switch automatically — no `dark:` Tailwind prefix needed.
|
|
187
86
|
|
|
188
|
-
|
|
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
|
-
|
|
89
|
+
26 pre-built themes available. Default: `stone`.
|
|
207
90
|
|
|
208
91
|
```css
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
99
|
+
AppShell, Backdrop, Modal, ModalDialog, Drawer, Collapsible, SlidingPanels, Nav
|
|
240
100
|
|
|
241
101
|
### Forms & Inputs
|
|
242
|
-
|
|
102
|
+
FieldInput, FieldTextarea, FieldSelect, FieldCheckbox, FieldRadios, FieldFile, FieldAssets, FieldOptions, FieldKeyValues, FieldSwitch, FieldInputLocalized, FieldLikeButton, Fieldset
|
|
243
103
|
|
|
244
104
|
### Buttons & Controls
|
|
245
|
-
|
|
105
|
+
Button, ButtonGroupRadio, Switch, TwCheck, ListItemButton, X
|
|
246
106
|
|
|
247
107
|
### Feedback & Notifications
|
|
248
|
-
|
|
108
|
+
Notifications, AlertConfirmPrompt, DismissibleMessage, Progress, Spinner, Skeleton
|
|
249
109
|
|
|
250
110
|
### Navigation & Menus
|
|
251
|
-
|
|
111
|
+
CommandMenu, DropdownMenu, TabbedMenu, TypeaheadInput, KbdShortcut
|
|
252
112
|
|
|
253
|
-
###
|
|
254
|
-
|
|
113
|
+
### Display & Utility
|
|
114
|
+
Avatar, Carousel, AnimatedElipsis, ThemePreview, ColorScheme, Thc, HoverExpandableWidth, AssetsPreview
|
|
255
115
|
|
|
256
116
|
## Actions
|
|
257
117
|
|
|
258
118
|
```svelte
|
|
259
|
-
<
|
|
260
|
-
<
|
|
261
|
-
<
|
|
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
|
-
|
|
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,
|
|
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)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { mount, unmount } from "svelte";
|
|
2
2
|
import { twMerge } from "../../utils/tw-merge.js";
|
|
3
3
|
import { addAnchorName, removeAnchorName } from "../../utils/anchor-name.js";
|
|
4
|
+
import { iconX } from "../../icons/index.js";
|
|
4
5
|
import { BodyScroll } from "../../utils/body-scroll-locker.js";
|
|
5
6
|
import PopoverContent from "./PopoverContent.svelte";
|
|
6
7
|
//
|
|
7
|
-
import "./index.css";
|
|
8
8
|
// Registry of open popover hide functions for closeOthers feature
|
|
9
9
|
const openPopovers = new Set();
|
|
10
10
|
// Reactive state tracking which popovers are open by ID
|
|
@@ -415,20 +415,9 @@ export function popover(anchorEl, fn) {
|
|
|
415
415
|
const closeBtn = document.createElement("button");
|
|
416
416
|
closeBtn.setAttribute("type", "button");
|
|
417
417
|
closeBtn.setAttribute("aria-label", "Close");
|
|
418
|
-
closeBtn.
|
|
419
|
-
closeBtn.style.cssText = `
|
|
420
|
-
|
|
421
|
-
top: 4px;
|
|
422
|
-
right: 4px;
|
|
423
|
-
background: black;
|
|
424
|
-
color: white;
|
|
425
|
-
border: none;
|
|
426
|
-
border-radius: 0.25rem;
|
|
427
|
-
cursor: pointer;
|
|
428
|
-
opacity: 0.6;
|
|
429
|
-
padding: 0.33rem;
|
|
430
|
-
line-height: 1;
|
|
431
|
-
`;
|
|
418
|
+
closeBtn.classList.add("stuic-close-button");
|
|
419
|
+
closeBtn.style.cssText = `position: absolute; top: 4px; right: 4px;`;
|
|
420
|
+
closeBtn.innerHTML = iconX();
|
|
432
421
|
closeBtn.addEventListener("click", hide);
|
|
433
422
|
popoverEl.appendChild(closeBtn);
|
|
434
423
|
}
|
|
@@ -60,10 +60,12 @@
|
|
|
60
60
|
el = $bindable(),
|
|
61
61
|
}: Props = $props();
|
|
62
62
|
|
|
63
|
+
const DEFAULT_ICON_SIZE = 24;
|
|
64
|
+
|
|
63
65
|
// Icon sizes for preset sizes (visual sizes are handled by CSS)
|
|
64
66
|
const ICON_SIZES: Record<string, number> = {
|
|
65
67
|
sm: 16,
|
|
66
|
-
md:
|
|
68
|
+
md: DEFAULT_ICON_SIZE,
|
|
67
69
|
lg: 28,
|
|
68
70
|
xl: 32,
|
|
69
71
|
"2xl": 36,
|
|
@@ -159,7 +161,7 @@
|
|
|
159
161
|
return parseInt(match[1]) * 2;
|
|
160
162
|
}
|
|
161
163
|
|
|
162
|
-
return
|
|
164
|
+
return DEFAULT_ICON_SIZE; // Default fallback
|
|
163
165
|
});
|
|
164
166
|
|
|
165
167
|
let colors = $derived(
|
|
@@ -37,7 +37,15 @@
|
|
|
37
37
|
el?: HTMLElement;
|
|
38
38
|
/** Optional tooltip configuration or direct content */
|
|
39
39
|
tooltip?: string | TooltipConfig;
|
|
40
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* Is this button a "X" button (this is a pragmatic convenience).
|
|
42
|
+
*
|
|
43
|
+
* Note: this does NOT use the shared `.stuic-close-button` styling (see X/index.css)
|
|
44
|
+
* because `<Button x>` is a general-purpose "button with an X icon" (tags, toolbars,
|
|
45
|
+
* dialogs...), styled by the Button system (intent/variant/size). The `.stuic-close-button`
|
|
46
|
+
* class is specifically for floating overlay dismiss controls (popover/dropdown fallback).
|
|
47
|
+
* For that look, use: `<Button x unstyled class="stuic-close-button" />`
|
|
48
|
+
*/
|
|
41
49
|
x?: boolean | XProps;
|
|
42
50
|
/** Optional out-of-the-box spinner support */
|
|
43
51
|
spinner?: boolean | THC;
|
|
@@ -92,9 +100,13 @@
|
|
|
92
100
|
return _tooltip ? _tooltip : () => ({ enabled: false });
|
|
93
101
|
});
|
|
94
102
|
|
|
103
|
+
const DEFAULT_X_CLS = "size-7 -m-2";
|
|
104
|
+
|
|
95
105
|
let _xProps: undefined | XProps = $derived.by(() => {
|
|
96
106
|
if (x) {
|
|
97
|
-
|
|
107
|
+
const props = typeof x === "boolean" ? { class: "" } : { ...x };
|
|
108
|
+
props.class = twMerge(DEFAULT_X_CLS, props.class);
|
|
109
|
+
return props;
|
|
98
110
|
}
|
|
99
111
|
});
|
|
100
112
|
|
|
@@ -36,7 +36,15 @@ export interface Props extends Omit<HTMLButtonAttributes, "children"> {
|
|
|
36
36
|
el?: HTMLElement;
|
|
37
37
|
/** Optional tooltip configuration or direct content */
|
|
38
38
|
tooltip?: string | TooltipConfig;
|
|
39
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* Is this button a "X" button (this is a pragmatic convenience).
|
|
41
|
+
*
|
|
42
|
+
* Note: this does NOT use the shared `.stuic-close-button` styling (see X/index.css)
|
|
43
|
+
* because `<Button x>` is a general-purpose "button with an X icon" (tags, toolbars,
|
|
44
|
+
* dialogs...), styled by the Button system (intent/variant/size). The `.stuic-close-button`
|
|
45
|
+
* class is specifically for floating overlay dismiss controls (popover/dropdown fallback).
|
|
46
|
+
* For that look, use: `<Button x unstyled class="stuic-close-button" />`
|
|
47
|
+
*/
|
|
40
48
|
x?: boolean | XProps;
|
|
41
49
|
/** Optional out-of-the-box spinner support */
|
|
42
50
|
spinner?: boolean | THC;
|
|
@@ -533,7 +533,12 @@
|
|
|
533
533
|
}
|
|
534
534
|
});
|
|
535
535
|
|
|
536
|
-
// Runtime viewport overflow detection
|
|
536
|
+
// Runtime viewport overflow detection.
|
|
537
|
+
// Known issue: Safari (as of 18.x) does not honor CSS position-try-fallbacks
|
|
538
|
+
// for anchor-positioned elements, so this JS check + centered modal fallback
|
|
539
|
+
// is the actual mechanism that handles overflow on Safari/iOS.
|
|
540
|
+
// On Chrome/Android, CSS position-try-fallbacks (defined in index.css) handles
|
|
541
|
+
// overflow natively and this check rarely fires.
|
|
537
542
|
$effect(() => {
|
|
538
543
|
if (!isOpen || !dropdownEl || forceFallback || runtimeFallback) return;
|
|
539
544
|
if (!isAnchorPositioningSupported()) return;
|
|
@@ -842,25 +847,13 @@
|
|
|
842
847
|
<button
|
|
843
848
|
type="button"
|
|
844
849
|
aria-label="Close"
|
|
845
|
-
class="stuic-
|
|
850
|
+
class="stuic-close-button absolute right-0 top-0 pointer-events-auto"
|
|
846
851
|
onclick={() => {
|
|
847
852
|
isOpen = false;
|
|
848
853
|
triggerEl?.focus();
|
|
849
854
|
}}
|
|
850
855
|
>
|
|
851
|
-
|
|
852
|
-
fill="none"
|
|
853
|
-
viewBox="0 0 24 24"
|
|
854
|
-
stroke-width="2.5"
|
|
855
|
-
stroke="currentColor"
|
|
856
|
-
class="w-5 h-5"
|
|
857
|
-
>
|
|
858
|
-
<path
|
|
859
|
-
stroke-linecap="round"
|
|
860
|
-
stroke-linejoin="round"
|
|
861
|
-
d="M6 18 18 6M6 6l12 12"
|
|
862
|
-
/>
|
|
863
|
-
</svg>
|
|
856
|
+
{@html iconX()}
|
|
864
857
|
</button>
|
|
865
858
|
</div>
|
|
866
859
|
{/if}
|
|
@@ -56,12 +56,6 @@
|
|
|
56
56
|
--stuic-dropdown-menu-backdrop-bg: rgb(0 0 0 / 0.25);
|
|
57
57
|
--stuic-dropdown-menu-backdrop-z-index: 40;
|
|
58
58
|
|
|
59
|
-
/* Close button (fallback mode) */
|
|
60
|
-
--stuic-dropdown-menu-close-bg: var(--stuic-color-foreground);
|
|
61
|
-
--stuic-dropdown-menu-close-text: var(--stuic-color-background);
|
|
62
|
-
--stuic-dropdown-menu-close-opacity: 0.6;
|
|
63
|
-
--stuic-dropdown-menu-close-opacity-hover: 1;
|
|
64
|
-
|
|
65
59
|
/* Expandable section indent */
|
|
66
60
|
--stuic-dropdown-menu-expandable-indent: calc(var(--spacing) * 4);
|
|
67
61
|
|
|
@@ -196,25 +190,6 @@
|
|
|
196
190
|
transition-property: opacity;
|
|
197
191
|
}
|
|
198
192
|
|
|
199
|
-
/* =============================================================================
|
|
200
|
-
CLOSE BUTTON (Fallback Mode)
|
|
201
|
-
============================================================================= */
|
|
202
|
-
|
|
203
|
-
.stuic-dropdown-menu-close {
|
|
204
|
-
background: var(--stuic-dropdown-menu-close-bg);
|
|
205
|
-
color: var(--stuic-dropdown-menu-close-text);
|
|
206
|
-
opacity: var(--stuic-dropdown-menu-close-opacity);
|
|
207
|
-
border-radius: var(--stuic-dropdown-menu-radius);
|
|
208
|
-
padding: calc(var(--spacing) * 2);
|
|
209
|
-
cursor: pointer;
|
|
210
|
-
line-height: 1;
|
|
211
|
-
transition: opacity var(--stuic-dropdown-menu-transition);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
.stuic-dropdown-menu-close:hover {
|
|
215
|
-
opacity: var(--stuic-dropdown-menu-close-opacity-hover);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
193
|
/* =============================================================================
|
|
219
194
|
EXPANDABLE SECTION
|
|
220
195
|
============================================================================= */
|
|
@@ -163,13 +163,15 @@
|
|
|
163
163
|
{...rest as any}
|
|
164
164
|
{tabindex}
|
|
165
165
|
>
|
|
166
|
-
|
|
167
|
-
{
|
|
168
|
-
|
|
169
|
-
{
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
166
|
+
<span class="block truncate">
|
|
167
|
+
{#if typeof rendered === "function"}
|
|
168
|
+
{@render rendered(value)}
|
|
169
|
+
{:else if rendered}
|
|
170
|
+
{@html rendered}
|
|
171
|
+
{:else}
|
|
172
|
+
|
|
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
|
|
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
|
|
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
|
|
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",
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/* =============================================================================
|
|
2
|
+
CLOSE BUTTON - Shared "floating X dismiss" for fallback/modal contexts
|
|
3
|
+
Override globally: :root { --stuic-close-button-radius: 0.25rem; }
|
|
4
|
+
Override locally: .my-component .stuic-close-button { --stuic-close-button-opacity: 0.8; }
|
|
5
|
+
============================================================================= */
|
|
6
|
+
|
|
7
|
+
:root {
|
|
8
|
+
--stuic-close-button-bg: var(--stuic-color-foreground);
|
|
9
|
+
--stuic-close-button-text: var(--stuic-color-background);
|
|
10
|
+
--stuic-close-button-opacity: 0.6;
|
|
11
|
+
--stuic-close-button-opacity-hover: 1;
|
|
12
|
+
--stuic-close-button-radius: 9999px;
|
|
13
|
+
--stuic-close-button-padding: calc(var(--spacing) * 2);
|
|
14
|
+
--stuic-close-button-min-size: 44px;
|
|
15
|
+
--stuic-close-button-transition: 150ms;
|
|
16
|
+
--stuic-close-button-icon-size: 1.75rem;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@layer components {
|
|
20
|
+
.stuic-close-button {
|
|
21
|
+
display: inline-flex;
|
|
22
|
+
align-items: center;
|
|
23
|
+
justify-content: center;
|
|
24
|
+
background: var(--stuic-close-button-bg);
|
|
25
|
+
color: var(--stuic-close-button-text);
|
|
26
|
+
opacity: var(--stuic-close-button-opacity);
|
|
27
|
+
border: none;
|
|
28
|
+
border-radius: var(--stuic-close-button-radius);
|
|
29
|
+
padding: var(--stuic-close-button-padding);
|
|
30
|
+
cursor: pointer;
|
|
31
|
+
line-height: 1;
|
|
32
|
+
min-height: var(--stuic-close-button-min-size);
|
|
33
|
+
min-width: var(--stuic-close-button-min-size);
|
|
34
|
+
transition: opacity var(--stuic-close-button-transition);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.stuic-close-button:hover {
|
|
38
|
+
opacity: var(--stuic-close-button-opacity-hover);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.stuic-close-button svg {
|
|
42
|
+
width: var(--stuic-close-button-icon-size);
|
|
43
|
+
height: var(--stuic-close-button-icon-size);
|
|
44
|
+
}
|
|
45
|
+
}
|
package/dist/index.css
CHANGED
|
@@ -48,6 +48,11 @@ In practice:
|
|
|
48
48
|
@import "./components/TabbedMenu/index.css";
|
|
49
49
|
@import "./components/ThemePreview/index.css";
|
|
50
50
|
@import "./components/TwCheck/index.css";
|
|
51
|
+
@import "./components/X/index.css";
|
|
52
|
+
|
|
53
|
+
/* Action CSS */
|
|
54
|
+
@import "./actions/popover/index.css";
|
|
55
|
+
@import "./actions/tooltip/index.css";
|
|
51
56
|
|
|
52
57
|
/* Base styles for STUIC components */
|
|
53
58
|
@layer base {
|