@marianmeres/stuic 3.0.0 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/index.d.ts +1 -0
- package/dist/actions/index.js +1 -0
- package/dist/actions/typeahead.svelte.d.ts +53 -0
- package/dist/actions/typeahead.svelte.js +328 -0
- package/dist/base.css +17 -0
- package/dist/components/AlertConfirmPrompt/AlertConfirmPrompt.svelte +4 -3
- package/dist/components/AlertConfirmPrompt/AlertConfirmPrompt.svelte.d.ts +4 -3
- package/dist/components/AlertConfirmPrompt/Current.svelte +1 -2
- package/dist/components/AlertConfirmPrompt/Current.svelte.d.ts +0 -1
- package/dist/components/AlertConfirmPrompt/index.css +47 -43
- package/dist/components/AssetsPreview/AssetsPreview.svelte +0 -1
- package/dist/components/AssetsPreview/AssetsPreview.svelte.d.ts +0 -1
- package/dist/components/AssetsPreview/index.css +31 -29
- package/dist/components/Avatar/Avatar.svelte +0 -1
- package/dist/components/Avatar/Avatar.svelte.d.ts +0 -1
- package/dist/components/Avatar/index.css +87 -85
- package/dist/components/Backdrop/Backdrop.svelte +0 -1
- package/dist/components/Backdrop/Backdrop.svelte.d.ts +0 -1
- package/dist/components/Backdrop/index.css +15 -13
- package/dist/components/Button/Button.svelte +0 -1
- package/dist/components/Button/Button.svelte.d.ts +0 -1
- package/dist/components/Button/index.css +431 -429
- package/dist/components/ButtonGroupRadio/ButtonGroupRadio.svelte +0 -1
- package/dist/components/ButtonGroupRadio/ButtonGroupRadio.svelte.d.ts +0 -1
- package/dist/components/ButtonGroupRadio/index.css +123 -117
- package/dist/components/Collapsible/index.css +17 -15
- package/dist/components/CommandMenu/CommandMenu.svelte +7 -4
- package/dist/components/CommandMenu/CommandMenu.svelte.d.ts +0 -1
- package/dist/components/CommandMenu/index.css +27 -25
- package/dist/components/DismissibleMessage/DismissibleMessage.svelte +0 -2
- package/dist/components/DismissibleMessage/DismissibleMessage.svelte.d.ts +0 -1
- package/dist/components/DismissibleMessage/index.css +116 -110
- package/dist/components/DropdownMenu/DropdownMenu.svelte +317 -74
- package/dist/components/DropdownMenu/DropdownMenu.svelte.d.ts +19 -1
- package/dist/components/DropdownMenu/index.css +236 -170
- package/dist/components/DropdownMenu/index.d.ts +1 -1
- package/dist/components/HoverExpandableWidth/HoverExpandableWidth.svelte +3 -1
- package/dist/components/HoverExpandableWidth/HoverExpandableWidth.svelte.d.ts +1 -0
- package/dist/components/Input/FieldInput.svelte +8 -0
- package/dist/components/Input/FieldInput.svelte.d.ts +2 -0
- package/dist/components/Input/FieldOptions.svelte +1 -1
- package/dist/components/Input/index.css +411 -398
- package/dist/components/KbdShortcut/KbdShortcut.svelte +4 -12
- package/dist/components/KbdShortcut/README.md +34 -0
- package/dist/components/KbdShortcut/index.css +55 -0
- package/dist/components/ListItemButton/ListItemButton.svelte +0 -1
- package/dist/components/ListItemButton/ListItemButton.svelte.d.ts +0 -1
- package/dist/components/ListItemButton/index.css +118 -116
- package/dist/components/Modal/Modal.svelte +0 -1
- package/dist/components/Modal/Modal.svelte.d.ts +0 -1
- package/dist/components/Modal/index.css +18 -16
- package/dist/components/ModalDialog/index.css +29 -27
- package/dist/components/Nav/Nav.svelte +732 -0
- package/dist/components/Nav/Nav.svelte.d.ts +110 -0
- package/dist/components/Nav/README.md +334 -0
- package/dist/components/Nav/index.css +318 -0
- package/dist/components/Nav/index.d.ts +1 -0
- package/dist/components/Nav/index.js +1 -0
- package/dist/components/Notifications/Notifications.svelte +2 -3
- package/dist/components/Notifications/Notifications.svelte.d.ts +0 -1
- package/dist/components/Notifications/index.css +158 -158
- package/dist/components/Notifications/notifications-stack.svelte.d.ts +4 -0
- package/dist/components/Notifications/notifications-stack.svelte.js +8 -0
- package/dist/components/Progress/Progress.svelte +4 -2
- package/dist/components/Progress/Progress.svelte.d.ts +1 -0
- package/dist/components/Progress/README.md +86 -15
- package/dist/components/Progress/_internal/Bar.svelte +4 -15
- package/dist/components/Progress/_internal/Bar.svelte.d.ts +1 -1
- package/dist/components/Progress/_internal/Circle.svelte +30 -2
- package/dist/components/Progress/_internal/Circle.svelte.d.ts +1 -0
- package/dist/components/Progress/index.css +47 -1
- package/dist/components/Skeleton/README.md +152 -0
- package/dist/components/Skeleton/Skeleton.svelte +6 -7
- package/dist/components/Skeleton/Skeleton.svelte.d.ts +0 -1
- package/dist/components/Skeleton/index.css +73 -43
- package/dist/components/Spinner/README.md +149 -37
- package/dist/components/Spinner/Spinner.svelte +14 -38
- package/dist/components/Spinner/Spinner.svelte.d.ts +2 -1
- package/dist/components/Spinner/SpinnerCircle.svelte +6 -34
- package/dist/components/Spinner/SpinnerCircle.svelte.d.ts +1 -0
- package/dist/components/Spinner/SpinnerCircleOscillate.svelte +10 -5
- package/dist/components/Spinner/SpinnerUnicode.svelte +3 -1
- package/dist/components/Spinner/SpinnerUnicode.svelte.d.ts +1 -0
- package/dist/components/Spinner/index.css +104 -0
- package/dist/components/Switch/README.md +34 -18
- package/dist/components/Switch/Switch.svelte +24 -46
- package/dist/components/Switch/Switch.svelte.d.ts +4 -2
- package/dist/components/Switch/index.css +120 -2
- package/dist/components/Switch/index.d.ts +1 -2
- package/dist/components/Switch/index.js +1 -2
- package/dist/components/TabbedMenu/README.md +28 -17
- package/dist/components/TabbedMenu/TabbedMenu.svelte +5 -46
- package/dist/components/TabbedMenu/TabbedMenu.svelte.d.ts +0 -1
- package/dist/components/TabbedMenu/index.css +85 -3
- package/dist/components/ThemePreview/ThemePreview.svelte +86 -33
- package/dist/components/ThemePreview/ThemePreview.svelte.d.ts +3 -1
- package/dist/components/ThemePreview/index.css +24 -8
- package/dist/components/TwCheck/README.md +32 -13
- package/dist/components/TwCheck/TwCheck.svelte +11 -9
- package/dist/components/TwCheck/TwCheck.svelte.d.ts +0 -1
- package/dist/components/TwCheck/index.css +14 -0
- package/dist/components/TypeaheadInput/TypeaheadInput.svelte +19 -187
- package/dist/components/TypeaheadInput/TypeaheadInput.svelte.d.ts +4 -2
- package/dist/icons/index.d.ts +1 -0
- package/dist/icons/index.js +1 -0
- package/dist/index.css +44 -39
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/themes/blue-orange.css +246 -156
- package/dist/themes/blue-orange.js +24 -0
- package/dist/themes/cyan-red.css +246 -156
- package/dist/themes/cyan-red.js +24 -0
- package/dist/themes/cyan-slate.css +246 -156
- package/dist/themes/cyan-slate.js +25 -1
- package/dist/themes/emerald-pink.css +246 -156
- package/dist/themes/emerald-pink.js +25 -1
- package/dist/themes/fuchsia-emerald.css +246 -156
- package/dist/themes/fuchsia-emerald.js +25 -1
- package/dist/themes/gray.css +246 -156
- package/dist/themes/gray.js +24 -0
- package/dist/themes/indigo-amber.css +246 -156
- package/dist/themes/indigo-amber.js +26 -2
- package/dist/themes/neutral.css +246 -156
- package/dist/themes/neutral.js +24 -0
- package/dist/themes/pink-emerald.css +246 -156
- package/dist/themes/pink-emerald.js +25 -1
- package/dist/themes/pink-teal.css +253 -0
- package/dist/themes/pink-teal.d.ts +6 -0
- package/dist/themes/pink-teal.js +175 -0
- package/dist/themes/purple-yellow.css +246 -156
- package/dist/themes/purple-yellow.js +24 -0
- package/dist/themes/rainbow.css +246 -156
- package/dist/themes/rainbow.js +25 -1
- package/dist/themes/red-blue.css +246 -156
- package/dist/themes/red-blue.js +24 -0
- package/dist/themes/red-cyan.css +246 -156
- package/dist/themes/red-cyan.js +24 -0
- package/dist/themes/red-sky.css +253 -0
- package/dist/themes/red-sky.d.ts +6 -0
- package/dist/themes/red-sky.js +175 -0
- package/dist/themes/rose-teal.css +246 -156
- package/dist/themes/rose-teal.js +24 -0
- package/dist/themes/sky-amber.css +246 -156
- package/dist/themes/sky-amber.js +26 -2
- package/dist/themes/slate-cyan.css +246 -156
- package/dist/themes/slate-cyan.js +25 -1
- package/dist/themes/teal-rose.css +246 -156
- package/dist/themes/teal-rose.js +24 -0
- package/dist/themes/violet-lime.css +246 -156
- package/dist/themes/violet-lime.js +27 -3
- package/dist/utils/design-tokens.d.ts +1 -1
- package/dist/utils/design-tokens.js +44 -3
- package/dist/utils/storage-abstraction.js +1 -1
- package/package.json +11 -28
- package/dist/components/Switch/SwitchButton.svelte +0 -134
- package/dist/components/Switch/SwitchButton.svelte.d.ts +0 -21
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
/* Spinner (radial bars) */
|
|
3
|
+
--stuic-spinner-opacity: 0.8;
|
|
4
|
+
--stuic-spinner-fade-end-opacity: 0.12;
|
|
5
|
+
--stuic-spinner-duration: 750ms;
|
|
6
|
+
|
|
7
|
+
/* SpinnerCircle */
|
|
8
|
+
--stuic-spinner-circle-thickness-thin: 1px;
|
|
9
|
+
--stuic-spinner-circle-thickness-normal: 2px;
|
|
10
|
+
--stuic-spinner-circle-thickness-thick: 4px;
|
|
11
|
+
--stuic-spinner-circle-duration: 750ms;
|
|
12
|
+
|
|
13
|
+
/* SpinnerCircleOscillate */
|
|
14
|
+
--stuic-spinner-circle-oscillate-bg-stroke: var(
|
|
15
|
+
--stuic-color-border,
|
|
16
|
+
rgba(0 0 0 / 0.1)
|
|
17
|
+
);
|
|
18
|
+
--stuic-spinner-circle-oscillate-duration: 0.75s;
|
|
19
|
+
|
|
20
|
+
/* SpinnerUnicode */
|
|
21
|
+
--stuic-spinner-unicode-font-size: var(--text-xl);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@layer components {
|
|
25
|
+
/* ========================================================================
|
|
26
|
+
Spinner (radial bars)
|
|
27
|
+
======================================================================== */
|
|
28
|
+
|
|
29
|
+
.stuic-spinner {
|
|
30
|
+
position: relative;
|
|
31
|
+
opacity: var(--stuic-spinner-opacity);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.stuic-spinner-bar {
|
|
35
|
+
position: absolute;
|
|
36
|
+
left: 50%;
|
|
37
|
+
top: 0;
|
|
38
|
+
background: currentColor;
|
|
39
|
+
animation: stuic-spinner-fade var(--stuic-spinner-duration) linear infinite;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@keyframes stuic-spinner-fade {
|
|
43
|
+
from {
|
|
44
|
+
opacity: 1;
|
|
45
|
+
}
|
|
46
|
+
to {
|
|
47
|
+
opacity: var(--stuic-spinner-fade-end-opacity);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* ========================================================================
|
|
52
|
+
SpinnerCircle
|
|
53
|
+
======================================================================== */
|
|
54
|
+
|
|
55
|
+
.stuic-spinner-circle {
|
|
56
|
+
display: inline-block;
|
|
57
|
+
box-sizing: border-box;
|
|
58
|
+
border-radius: 50%;
|
|
59
|
+
border-style: solid;
|
|
60
|
+
border-color: currentColor;
|
|
61
|
+
border-top-color: transparent;
|
|
62
|
+
animation: stuic-spinner-circle-spin linear infinite;
|
|
63
|
+
animation-duration: var(--stuic-spinner-circle-duration);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.stuic-spinner-circle[data-thickness="thin"] {
|
|
67
|
+
border-width: var(--stuic-spinner-circle-thickness-thin);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.stuic-spinner-circle[data-thickness="normal"] {
|
|
71
|
+
border-width: var(--stuic-spinner-circle-thickness-normal);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.stuic-spinner-circle[data-thickness="thick"] {
|
|
75
|
+
border-width: var(--stuic-spinner-circle-thickness-thick);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@keyframes stuic-spinner-circle-spin {
|
|
79
|
+
to {
|
|
80
|
+
transform: rotate(360deg);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* ========================================================================
|
|
85
|
+
SpinnerCircleOscillate
|
|
86
|
+
======================================================================== */
|
|
87
|
+
|
|
88
|
+
.stuic-spinner-circle-oscillate {
|
|
89
|
+
animation: stuic-spinner-circle-spin linear infinite;
|
|
90
|
+
animation-duration: var(--stuic-spinner-circle-oscillate-duration);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* ========================================================================
|
|
94
|
+
SpinnerUnicode
|
|
95
|
+
======================================================================== */
|
|
96
|
+
|
|
97
|
+
.stuic-spinner-unicode {
|
|
98
|
+
display: inline-block;
|
|
99
|
+
font-family: var(--font-mono);
|
|
100
|
+
line-height: 1;
|
|
101
|
+
color: currentColor;
|
|
102
|
+
font-size: var(--stuic-spinner-unicode-font-size);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
# Switch
|
|
2
2
|
|
|
3
|
-
A toggle switch component with size variants, keyboard support, and optional async validation.
|
|
3
|
+
A toggle switch component with size variants, semantic intents, keyboard support, and optional async validation.
|
|
4
4
|
|
|
5
5
|
## Props
|
|
6
6
|
|
|
7
7
|
| Prop | Type | Default | Description |
|
|
8
8
|
|------|------|---------|-------------|
|
|
9
9
|
| `checked` | `boolean` | - | Toggle state (bindable) |
|
|
10
|
-
| `size` | `"
|
|
10
|
+
| `size` | `"sm" \| "md" \| "lg" \| "xl" \| string` | `"lg"` | Switch size |
|
|
11
|
+
| `intent` | `"primary" \| "accent" \| "success" \| "warning" \| "destructive" \| "info"` | - | Semantic color intent |
|
|
11
12
|
| `name` | `string` | - | Form field name for hidden checkbox |
|
|
12
13
|
| `label` | `string` | - | Screen reader label (visually hidden) |
|
|
13
14
|
| `required` | `boolean` | `false` | Mark as required |
|
|
@@ -17,7 +18,6 @@ A toggle switch component with size variants, keyboard support, and optional asy
|
|
|
17
18
|
| `validate` | `boolean \| ValidateOptions` | - | Enable validation |
|
|
18
19
|
| `class` | `string` | - | CSS for switch container |
|
|
19
20
|
| `dotClass` | `string` | - | CSS for toggle knob |
|
|
20
|
-
| `button` | `HTMLButtonElement` | - | Button element reference (bindable) |
|
|
21
21
|
|
|
22
22
|
## Snippets
|
|
23
23
|
|
|
@@ -32,34 +32,41 @@ A toggle switch component with size variants, keyboard support, and optional asy
|
|
|
32
32
|
|
|
33
33
|
```svelte
|
|
34
34
|
<script lang="ts">
|
|
35
|
-
import { Switch } from 'stuic';
|
|
35
|
+
import { Switch } from '@marianmeres/stuic';
|
|
36
36
|
|
|
37
37
|
let enabled = $state(false);
|
|
38
38
|
</script>
|
|
39
39
|
|
|
40
40
|
<Switch bind:checked={enabled} />
|
|
41
|
-
<span>{enabled ? 'On' : 'Off'}</span>
|
|
42
41
|
```
|
|
43
42
|
|
|
44
43
|
### Different Sizes
|
|
45
44
|
|
|
46
45
|
```svelte
|
|
47
|
-
<Switch size="xs" />
|
|
48
46
|
<Switch size="sm" />
|
|
49
47
|
<Switch size="md" />
|
|
50
48
|
<Switch size="lg" />
|
|
51
49
|
<Switch size="xl" />
|
|
52
50
|
```
|
|
53
51
|
|
|
52
|
+
### Semantic Intents
|
|
53
|
+
|
|
54
|
+
```svelte
|
|
55
|
+
<Switch intent="primary" checked />
|
|
56
|
+
<Switch intent="success" checked />
|
|
57
|
+
<Switch intent="warning" checked />
|
|
58
|
+
<Switch intent="destructive" checked />
|
|
59
|
+
```
|
|
60
|
+
|
|
54
61
|
### With Icons Inside
|
|
55
62
|
|
|
56
63
|
```svelte
|
|
57
64
|
<Switch bind:checked={darkMode}>
|
|
58
65
|
{#snippet on()}
|
|
59
|
-
<span class="text-xs"
|
|
66
|
+
<span class="text-xs">ON</span>
|
|
60
67
|
{/snippet}
|
|
61
68
|
{#snippet off()}
|
|
62
|
-
<span class="text-xs"
|
|
69
|
+
<span class="text-xs">OFF</span>
|
|
63
70
|
{/snippet}
|
|
64
71
|
</Switch>
|
|
65
72
|
```
|
|
@@ -72,21 +79,17 @@ A toggle switch component with size variants, keyboard support, and optional asy
|
|
|
72
79
|
|
|
73
80
|
async function checkPremium(current: boolean) {
|
|
74
81
|
if (!current) {
|
|
75
|
-
// Turning on - check if user can enable premium
|
|
76
82
|
const canEnable = await checkSubscription();
|
|
77
83
|
if (!canEnable) {
|
|
78
84
|
alert('Premium subscription required');
|
|
79
|
-
return false;
|
|
85
|
+
return false;
|
|
80
86
|
}
|
|
81
87
|
}
|
|
82
88
|
return true;
|
|
83
89
|
}
|
|
84
90
|
</script>
|
|
85
91
|
|
|
86
|
-
<Switch
|
|
87
|
-
bind:checked={premium}
|
|
88
|
-
preHook={checkPremium}
|
|
89
|
-
/>
|
|
92
|
+
<Switch bind:checked={premium} preHook={checkPremium} />
|
|
90
93
|
```
|
|
91
94
|
|
|
92
95
|
### In a Form
|
|
@@ -109,19 +112,32 @@ A toggle switch component with size variants, keyboard support, and optional asy
|
|
|
109
112
|
|
|
110
113
|
## CSS Variables
|
|
111
114
|
|
|
115
|
+
### Component Tokens
|
|
116
|
+
|
|
112
117
|
| Variable | Default | Description |
|
|
113
118
|
|----------|---------|-------------|
|
|
114
|
-
| `--stuic-switch-
|
|
119
|
+
| `--stuic-switch-track` | `--stuic-color-border` | Unchecked track color |
|
|
120
|
+
| `--stuic-switch-track-checked` | `--stuic-color-primary` | Checked track color |
|
|
121
|
+
| `--stuic-switch-dot` | `--color-white` | Knob background color |
|
|
122
|
+
| `--stuic-switch-dot-foreground` | `--stuic-color-foreground` | Knob text/icon color |
|
|
123
|
+
| `--stuic-switch-ring-width` | `4px` | Focus ring width |
|
|
124
|
+
| `--stuic-switch-ring-color` | `--stuic-color-ring` | Focus ring color |
|
|
125
|
+
| `--stuic-switch-transition` | `100ms` | Transition duration |
|
|
115
126
|
|
|
116
|
-
### Example
|
|
127
|
+
### Example Overrides
|
|
117
128
|
|
|
118
129
|
```css
|
|
130
|
+
/* Global: green switches */
|
|
119
131
|
:root {
|
|
120
|
-
|
|
121
|
-
--stuic-switch-accent: var(--color-green-500);
|
|
132
|
+
--stuic-switch-track-checked: var(--color-green-500);
|
|
122
133
|
}
|
|
123
134
|
```
|
|
124
135
|
|
|
136
|
+
```svelte
|
|
137
|
+
<!-- Local: orange switch -->
|
|
138
|
+
<Switch style="--stuic-switch-track-checked: var(--color-orange-500);" />
|
|
139
|
+
```
|
|
140
|
+
|
|
125
141
|
## Keyboard Support
|
|
126
142
|
|
|
127
143
|
- **Space**: Toggle switch
|
|
@@ -6,10 +6,20 @@
|
|
|
6
6
|
ValidationResult,
|
|
7
7
|
} from "../../actions/validate.svelte.js";
|
|
8
8
|
|
|
9
|
+
export type SwitchIntent =
|
|
10
|
+
| "primary"
|
|
11
|
+
| "accent"
|
|
12
|
+
| "success"
|
|
13
|
+
| "warning"
|
|
14
|
+
| "destructive"
|
|
15
|
+
| "info";
|
|
16
|
+
|
|
9
17
|
export interface Props extends Omit<HTMLLabelAttributes, "children" | "onchange"> {
|
|
10
18
|
button?: HTMLButtonElement;
|
|
11
19
|
checked?: boolean;
|
|
12
|
-
size?: "sm" | "md" | "lg" |
|
|
20
|
+
size?: "sm" | "md" | "lg" | string;
|
|
21
|
+
/** Semantic color intent */
|
|
22
|
+
intent?: SwitchIntent;
|
|
13
23
|
/** Form field name for the hidden checkbox */
|
|
14
24
|
name?: string;
|
|
15
25
|
class?: string;
|
|
@@ -38,11 +48,10 @@
|
|
|
38
48
|
import { twMerge } from "../../utils/tw-merge.js";
|
|
39
49
|
import { validate as validateAction } from "../../actions/validate.svelte.js";
|
|
40
50
|
|
|
41
|
-
import "./index.css";
|
|
42
|
-
|
|
43
51
|
let {
|
|
44
52
|
button = $bindable(),
|
|
45
|
-
size = "
|
|
53
|
+
size = "md",
|
|
54
|
+
intent,
|
|
46
55
|
name,
|
|
47
56
|
class: classProp,
|
|
48
57
|
dotClass,
|
|
@@ -62,17 +71,17 @@
|
|
|
62
71
|
|
|
63
72
|
const _preset: any = {
|
|
64
73
|
size: {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
74
|
+
xs: `h-5 w-9`,
|
|
75
|
+
sm: `h-6 w-11`,
|
|
76
|
+
md: `h-7 w-13`,
|
|
77
|
+
lg: `h-8 w-15`,
|
|
69
78
|
},
|
|
70
79
|
dot: {
|
|
71
80
|
size: {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
81
|
+
xs: `size-3 data-[checked=true]:translate-x-5`,
|
|
82
|
+
sm: `size-4 data-[checked=true]:translate-x-6`,
|
|
83
|
+
md: `size-5 data-[checked=true]:translate-x-7`,
|
|
84
|
+
lg: `size-6 data-[checked=true]:translate-x-8`,
|
|
76
85
|
},
|
|
77
86
|
},
|
|
78
87
|
};
|
|
@@ -92,30 +101,10 @@
|
|
|
92
101
|
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
93
102
|
<label
|
|
94
103
|
bind:this={wrap}
|
|
95
|
-
class={twMerge(
|
|
96
|
-
"stuic-switch",
|
|
97
|
-
`m-2
|
|
98
|
-
relative inline-flex shrink-0 items-center
|
|
99
|
-
rounded-full cursor-pointer
|
|
100
|
-
|
|
101
|
-
transition-colors duration-100
|
|
102
|
-
|
|
103
|
-
hover:brightness-105 active:brightness-95
|
|
104
|
-
data-[disabled=true]:cursor-not-allowed! data-[disabled=true]:opacity-50! data-[disabled=true]:hover:brightness-100
|
|
105
|
-
|
|
106
|
-
bg-neutral-400 dark:bg-neutral-400
|
|
107
|
-
|
|
108
|
-
data-[checked=true]:bg-(--stuic-switch-accent)
|
|
109
|
-
|
|
110
|
-
focus:outline-0
|
|
111
|
-
focus:ring-(--stuic-switch-accent)/20
|
|
112
|
-
focus:ring-4`,
|
|
113
|
-
size,
|
|
114
|
-
_preset.size[size],
|
|
115
|
-
classProp
|
|
116
|
-
)}
|
|
104
|
+
class={twMerge("stuic-switch m-2", _preset.size[size], classProp)}
|
|
117
105
|
data-checked={checked}
|
|
118
106
|
data-disabled={disabled}
|
|
107
|
+
data-intent={intent}
|
|
119
108
|
tabindex={disabled ? -1 : tabindex}
|
|
120
109
|
onkeydown={(e: KeyboardEvent) => {
|
|
121
110
|
if (!disabled && !e.metaKey && ["Space", "Enter"].includes(e.code)) {
|
|
@@ -139,18 +128,7 @@
|
|
|
139
128
|
{...rest as Record<string, unknown>}
|
|
140
129
|
>
|
|
141
130
|
<span
|
|
142
|
-
class={twMerge(
|
|
143
|
-
"dot",
|
|
144
|
-
`flex items-center justify-center
|
|
145
|
-
translate-x-1 rounded-full
|
|
146
|
-
transition-all duration-100
|
|
147
|
-
shadow
|
|
148
|
-
bg-neutral-50 dark:bg-neutral-50
|
|
149
|
-
text-neutral-950 dark:text-neutral-950`,
|
|
150
|
-
size,
|
|
151
|
-
_preset.dot.size[size],
|
|
152
|
-
dotClass
|
|
153
|
-
)}
|
|
131
|
+
class={twMerge("dot translate-x-1", _preset.dot.size[size], dotClass)}
|
|
154
132
|
data-checked={checked}
|
|
155
133
|
>
|
|
156
134
|
{#if checked}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import type { Snippet } from "svelte";
|
|
2
2
|
import type { FormEventHandler, HTMLLabelAttributes } from "svelte/elements";
|
|
3
3
|
import type { ValidateOptions, ValidationResult } from "../../actions/validate.svelte.js";
|
|
4
|
+
export type SwitchIntent = "primary" | "accent" | "success" | "warning" | "destructive" | "info";
|
|
4
5
|
export interface Props extends Omit<HTMLLabelAttributes, "children" | "onchange"> {
|
|
5
6
|
button?: HTMLButtonElement;
|
|
6
7
|
checked?: boolean;
|
|
7
|
-
size?: "sm" | "md" | "lg" |
|
|
8
|
+
size?: "sm" | "md" | "lg" | string;
|
|
9
|
+
/** Semantic color intent */
|
|
10
|
+
intent?: SwitchIntent;
|
|
8
11
|
/** Form field name for the hidden checkbox */
|
|
9
12
|
name?: string;
|
|
10
13
|
class?: string;
|
|
@@ -26,7 +29,6 @@ export interface Props extends Omit<HTMLLabelAttributes, "children" | "onchange"
|
|
|
26
29
|
validate?: boolean | Omit<ValidateOptions, "setValidationResult">;
|
|
27
30
|
setValidationResult?: (res: ValidationResult) => void;
|
|
28
31
|
}
|
|
29
|
-
import "./index.css";
|
|
30
32
|
declare const Switch: import("svelte").Component<Props, {}, "button" | "checked">;
|
|
31
33
|
type Switch = ReturnType<typeof Switch>;
|
|
32
34
|
export default Switch;
|
|
@@ -1,4 +1,122 @@
|
|
|
1
|
-
/*
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
SWITCH COMPONENT TOKENS
|
|
3
|
+
Override globally: :root { --stuic-switch-track-checked: var(--color-green-500); }
|
|
4
|
+
Override locally: <Switch style="--stuic-switch-track-checked: var(--color-green-500);">
|
|
5
|
+
============================================================================ */
|
|
6
|
+
|
|
2
7
|
:root {
|
|
3
|
-
|
|
8
|
+
/* Track (background) */
|
|
9
|
+
--stuic-switch-track: var(
|
|
10
|
+
--stuic-color-border
|
|
11
|
+
); /* intentionally border, to make it more contrast */
|
|
12
|
+
--stuic-switch-track-checked: var(--stuic-color-primary);
|
|
13
|
+
|
|
14
|
+
/* Dot (knob) */
|
|
15
|
+
--stuic-switch-dot: var(--color-white);
|
|
16
|
+
--stuic-switch-dot-foreground: var(--stuic-color-foreground);
|
|
17
|
+
|
|
18
|
+
/* Focus ring */
|
|
19
|
+
--stuic-switch-ring-width: 4px;
|
|
20
|
+
--stuic-switch-ring-color: var(--stuic-color-ring);
|
|
21
|
+
|
|
22
|
+
/* Transition */
|
|
23
|
+
--stuic-switch-transition: 100ms;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@layer components {
|
|
27
|
+
/* ============================================================================
|
|
28
|
+
BASE STYLES
|
|
29
|
+
============================================================================ */
|
|
30
|
+
|
|
31
|
+
.stuic-switch {
|
|
32
|
+
/* Use internal vars that can be set by intent */
|
|
33
|
+
--_track: var(--stuic-switch-track);
|
|
34
|
+
--_track-checked: var(--stuic-switch-track-checked);
|
|
35
|
+
--_dot: var(--stuic-switch-dot);
|
|
36
|
+
--_dot-fg: var(--stuic-switch-dot-foreground);
|
|
37
|
+
|
|
38
|
+
position: relative;
|
|
39
|
+
display: inline-flex;
|
|
40
|
+
flex-shrink: 0;
|
|
41
|
+
align-items: center;
|
|
42
|
+
border-radius: 9999px;
|
|
43
|
+
cursor: pointer;
|
|
44
|
+
|
|
45
|
+
background: var(--_track);
|
|
46
|
+
transition:
|
|
47
|
+
background var(--stuic-switch-transition),
|
|
48
|
+
box-shadow var(--stuic-switch-transition);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* Checked state */
|
|
52
|
+
.stuic-switch[data-checked="true"] {
|
|
53
|
+
background: var(--_track-checked);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* Hover */
|
|
57
|
+
.stuic-switch:hover:not([data-disabled="true"]) {
|
|
58
|
+
/* filter: brightness(1.05); */
|
|
59
|
+
filter: brightness(0.95);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/* Active */
|
|
63
|
+
.stuic-switch:active:not([data-disabled="true"]) {
|
|
64
|
+
/* filter: brightness(0.95); */
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* Focus */
|
|
68
|
+
.stuic-switch:focus {
|
|
69
|
+
outline: none;
|
|
70
|
+
box-shadow: 0 0 0 var(--stuic-switch-ring-width)
|
|
71
|
+
color-mix(in srgb, var(--stuic-switch-ring-color) 30%, var(--stuic-color-background));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* Disabled */
|
|
75
|
+
.stuic-switch[data-disabled="true"] {
|
|
76
|
+
cursor: not-allowed;
|
|
77
|
+
opacity: 0.8;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* ============================================================================
|
|
81
|
+
DOT (KNOB) STYLES
|
|
82
|
+
============================================================================ */
|
|
83
|
+
|
|
84
|
+
.stuic-switch > .dot {
|
|
85
|
+
display: flex;
|
|
86
|
+
align-items: center;
|
|
87
|
+
justify-content: center;
|
|
88
|
+
border-radius: 9999px;
|
|
89
|
+
background: var(--_dot);
|
|
90
|
+
color: var(--_dot-fg);
|
|
91
|
+
box-shadow: var(--shadow-sm);
|
|
92
|
+
transition: transform var(--stuic-switch-transition);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* ============================================================================
|
|
96
|
+
INTENT COLOR MAPPING
|
|
97
|
+
============================================================================ */
|
|
98
|
+
|
|
99
|
+
.stuic-switch[data-intent="primary"] {
|
|
100
|
+
--_track-checked: var(--stuic-color-primary);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.stuic-switch[data-intent="accent"] {
|
|
104
|
+
--_track-checked: var(--stuic-color-accent);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.stuic-switch[data-intent="success"] {
|
|
108
|
+
--_track-checked: var(--stuic-color-success);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.stuic-switch[data-intent="warning"] {
|
|
112
|
+
--_track-checked: var(--stuic-color-warning);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.stuic-switch[data-intent="destructive"] {
|
|
116
|
+
--_track-checked: var(--stuic-color-destructive);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.stuic-switch[data-intent="info"] {
|
|
120
|
+
--_track-checked: var(--stuic-color-info);
|
|
121
|
+
}
|
|
4
122
|
}
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
export { default as Switch, type Props as SwitchProps } from "./Switch.svelte";
|
|
2
|
-
export { default as SwitchButton, type Props as SwitchButtonProps, } from "./SwitchButton.svelte";
|
|
1
|
+
export { default as Switch, type Props as SwitchProps, type SwitchIntent, } from "./Switch.svelte";
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
export { default as Switch } from "./Switch.svelte";
|
|
2
|
-
export { default as SwitchButton, } from "./SwitchButton.svelte";
|
|
1
|
+
export { default as Switch, } from "./Switch.svelte";
|
|
@@ -46,39 +46,50 @@ interface TabbedMenuItem {
|
|
|
46
46
|
class?: string;
|
|
47
47
|
data?: Record<string, any>;
|
|
48
48
|
onSelect?: () => void | boolean; // Return false to prevent selection
|
|
49
|
+
href?: string; // Render as anchor instead of button
|
|
49
50
|
}
|
|
50
51
|
```
|
|
51
52
|
|
|
52
53
|
## CSS Variables
|
|
53
54
|
|
|
54
|
-
|
|
55
|
+
### Component Tokens
|
|
56
|
+
|
|
57
|
+
Override to customize appearance:
|
|
55
58
|
|
|
56
59
|
| Variable | Default | Description |
|
|
57
60
|
|----------|---------|-------------|
|
|
58
|
-
| `--stuic-tabbed-menu-tab-bg` | `--stuic-
|
|
59
|
-
| `--stuic-tabbed-menu-tab-text` | `--stuic-
|
|
60
|
-
| `--stuic-tabbed-menu-tab-bg-active` | `--stuic-surface` | Active tab background |
|
|
61
|
-
| `--stuic-tabbed-menu-tab-text-active` | `--stuic-
|
|
62
|
-
| `--stuic-tabbed-menu-border` | `--stuic-border` | Border color |
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
| `--stuic-tabbed-menu-tab-bg` | `--stuic-color-muted` | Tab background |
|
|
62
|
+
| `--stuic-tabbed-menu-tab-text` | `--stuic-color-muted-foreground` | Tab text color |
|
|
63
|
+
| `--stuic-tabbed-menu-tab-bg-active` | `--stuic-color-surface` | Active tab background |
|
|
64
|
+
| `--stuic-tabbed-menu-tab-text-active` | `--stuic-color-foreground` | Active tab text color |
|
|
65
|
+
| `--stuic-tabbed-menu-border` | `--stuic-color-border` | Border color |
|
|
66
|
+
| `--stuic-tabbed-menu-border-active` | `--stuic-color-primary` | Active tab border color |
|
|
67
|
+
| `--stuic-tabbed-menu-gap` | `calc(var(--spacing) * 1)` | Gap between tabs |
|
|
68
|
+
| `--stuic-tabbed-menu-radius` | `var(--radius-md)` | Tab border radius (top corners) |
|
|
69
|
+
| `--stuic-tabbed-menu-padding-x` | `calc(var(--spacing) * 4)` | Horizontal padding |
|
|
70
|
+
| `--stuic-tabbed-menu-padding-y` | `calc(var(--spacing) * 2)` | Vertical padding |
|
|
71
|
+
| `--stuic-tabbed-menu-transition` | `150ms` | Transition duration |
|
|
72
|
+
| `--stuic-tabbed-menu-font-weight-active` | `var(--font-weight-medium)` | Active tab font weight |
|
|
73
|
+
| `--stuic-tabbed-menu-item-max-width` | `10rem` | Max width per tab item |
|
|
74
|
+
|
|
75
|
+
### Example Overrides
|
|
65
76
|
|
|
66
77
|
```css
|
|
78
|
+
/* Global override */
|
|
67
79
|
:root {
|
|
68
80
|
--stuic-tabbed-menu-tab-bg-active: var(--color-indigo-100);
|
|
69
81
|
--stuic-tabbed-menu-tab-text-active: var(--color-indigo-900);
|
|
82
|
+
--stuic-tabbed-menu-radius: var(--radius-lg);
|
|
70
83
|
}
|
|
71
84
|
```
|
|
72
85
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
| `--color-tabbed-menu-tab-active-bg` | `--stuic-tabbed-menu-tab-bg-active` |
|
|
81
|
-
| `--color-tabbed-menu-border` | `--stuic-tabbed-menu-border` |
|
|
86
|
+
```svelte
|
|
87
|
+
<!-- Local override -->
|
|
88
|
+
<TabbedMenu
|
|
89
|
+
{items}
|
|
90
|
+
style="--stuic-tabbed-menu-radius: 9999px; --stuic-tabbed-menu-padding-x: 2rem;"
|
|
91
|
+
/>
|
|
92
|
+
```
|
|
82
93
|
|
|
83
94
|
## Keyboard Navigation
|
|
84
95
|
|
|
@@ -33,7 +33,6 @@
|
|
|
33
33
|
</script>
|
|
34
34
|
|
|
35
35
|
<script lang="ts">
|
|
36
|
-
import "./index.css";
|
|
37
36
|
|
|
38
37
|
//
|
|
39
38
|
let {
|
|
@@ -54,44 +53,6 @@
|
|
|
54
53
|
...rest
|
|
55
54
|
}: Props = $props();
|
|
56
55
|
|
|
57
|
-
const CLS = `
|
|
58
|
-
stuic-tabbed-menu
|
|
59
|
-
flex flex-row
|
|
60
|
-
gap-1
|
|
61
|
-
list-none m-0 p-0
|
|
62
|
-
`;
|
|
63
|
-
|
|
64
|
-
const CLS_ITEM = `
|
|
65
|
-
min-w-0 flex-1 max-w-40
|
|
66
|
-
`;
|
|
67
|
-
|
|
68
|
-
const CLS_BUTTON = `
|
|
69
|
-
px-4 py-2
|
|
70
|
-
rounded-t-md
|
|
71
|
-
border border-b-0
|
|
72
|
-
border-(--stuic-tabbed-menu-border)
|
|
73
|
-
bg-(--stuic-tabbed-menu-tab-bg)
|
|
74
|
-
text-(--stuic-tabbed-menu-tab-text)
|
|
75
|
-
cursor-pointer
|
|
76
|
-
transition-colors duration-150
|
|
77
|
-
hover:brightness-105
|
|
78
|
-
truncate w-full
|
|
79
|
-
block
|
|
80
|
-
text-center
|
|
81
|
-
`;
|
|
82
|
-
// focus-visible:outline-2 focus-visible:outline-offset-2
|
|
83
|
-
|
|
84
|
-
const CLS_BUTTON_ACTIVE = `
|
|
85
|
-
bg-(--stuic-tabbed-menu-tab-bg-active)
|
|
86
|
-
text-(--stuic-tabbed-menu-tab-text-active)
|
|
87
|
-
font-medium
|
|
88
|
-
`;
|
|
89
|
-
|
|
90
|
-
const CLS_BUTTON_DISABLED = `
|
|
91
|
-
opacity-50 cursor-not-allowed
|
|
92
|
-
pointer-events-none
|
|
93
|
-
`;
|
|
94
|
-
|
|
95
56
|
let buttonEls = $state<Record<string | number, HTMLButtonElement | HTMLAnchorElement>>(
|
|
96
57
|
{}
|
|
97
58
|
);
|
|
@@ -135,16 +96,14 @@
|
|
|
135
96
|
}
|
|
136
97
|
}
|
|
137
98
|
|
|
138
|
-
function
|
|
99
|
+
function getTabClass(item: TabbedMenuItem): string {
|
|
139
100
|
const isActive = value === item.id;
|
|
140
101
|
const isDisabled = item.disabled || disabled;
|
|
141
102
|
|
|
142
103
|
return twMerge(
|
|
143
|
-
!unstyled &&
|
|
104
|
+
!unstyled && "stuic-tabbed-menu-tab",
|
|
144
105
|
classButton,
|
|
145
|
-
isActive && !unstyled && CLS_BUTTON_ACTIVE,
|
|
146
106
|
isActive && classButtonActive,
|
|
147
|
-
isDisabled && !unstyled && CLS_BUTTON_DISABLED,
|
|
148
107
|
isDisabled && classButtonDisabled,
|
|
149
108
|
item.class
|
|
150
109
|
);
|
|
@@ -154,7 +113,7 @@
|
|
|
154
113
|
{#if items.length}
|
|
155
114
|
<ul
|
|
156
115
|
bind:this={el}
|
|
157
|
-
class={twMerge(!unstyled &&
|
|
116
|
+
class={twMerge(!unstyled && "stuic-tabbed-menu", classProp)}
|
|
158
117
|
role="tablist"
|
|
159
118
|
{...rest}
|
|
160
119
|
>
|
|
@@ -164,11 +123,11 @@
|
|
|
164
123
|
["aria-selected"]: value === item.id,
|
|
165
124
|
["aria-disabled"]: item.disabled || disabled || undefined,
|
|
166
125
|
tabindex: value === item.id ? 0 : -1,
|
|
167
|
-
class:
|
|
126
|
+
class: getTabClass(item),
|
|
168
127
|
onclick: () => selectItem(item),
|
|
169
128
|
onkeydown: (e: KeyboardEvent) => handleKeydown(e, item),
|
|
170
129
|
}}
|
|
171
|
-
<li class={twMerge(
|
|
130
|
+
<li class={twMerge(!unstyled && "stuic-tabbed-menu-item", classItem)} role="presentation">
|
|
172
131
|
{#if item.href}
|
|
173
132
|
<a href={item.href} {...props} bind:this={buttonEls[item.id]}>
|
|
174
133
|
<Thc thc={item.label} />
|
|
@@ -22,7 +22,6 @@ export interface Props extends Omit<HTMLAttributes<HTMLUListElement>, "children"
|
|
|
22
22
|
unstyled?: boolean;
|
|
23
23
|
el?: HTMLUListElement;
|
|
24
24
|
}
|
|
25
|
-
import "./index.css";
|
|
26
25
|
declare const TabbedMenu: import("svelte").Component<Props, {}, "value" | "el">;
|
|
27
26
|
type TabbedMenu = ReturnType<typeof TabbedMenu>;
|
|
28
27
|
export default TabbedMenu;
|