zabi-components 7.0.1 → 8.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.
- package/README.md +46 -3
- package/THEMING.md +255 -15
- package/dist/atoms/ActionPanel.svelte +12 -7
- package/dist/atoms/ActionPanel.svelte.d.ts +2 -0
- package/dist/atoms/Badge.svelte +93 -56
- package/dist/atoms/Badge.svelte.d.ts +15 -3
- package/dist/atoms/Button.svelte +49 -49
- package/dist/atoms/Button.svelte.d.ts +3 -0
- package/dist/atoms/Card.svelte +31 -28
- package/dist/atoms/Card.svelte.d.ts +5 -2
- package/dist/atoms/CardContent.svelte +10 -2
- package/dist/atoms/CardContent.svelte.d.ts +2 -0
- package/dist/atoms/CardFooter.svelte +10 -2
- package/dist/atoms/CardFooter.svelte.d.ts +2 -0
- package/dist/atoms/CardHeader.svelte +10 -2
- package/dist/atoms/CardHeader.svelte.d.ts +2 -0
- package/dist/atoms/Checkbox.svelte +4 -0
- package/dist/atoms/Checkbox.svelte.d.ts +2 -0
- package/dist/atoms/CodeBlock.svelte +15 -7
- package/dist/atoms/CodeBlock.svelte.d.ts +9 -3
- package/dist/atoms/ColorPicker.svelte +19 -9
- package/dist/atoms/ColorPicker.svelte.d.ts +3 -1
- package/dist/atoms/Container.svelte +2 -3
- package/dist/atoms/Divider.svelte +4 -4
- package/dist/atoms/FeatureCard.svelte +4 -3
- package/dist/atoms/Heading.svelte +56 -27
- package/dist/atoms/Heading.svelte.d.ts +17 -4
- package/dist/atoms/IconButton.svelte +30 -37
- package/dist/atoms/Input.svelte +26 -34
- package/dist/atoms/List.svelte +10 -2
- package/dist/atoms/List.svelte.d.ts +2 -0
- package/dist/atoms/ListItem.svelte +2 -2
- package/dist/atoms/ListItemLeading.svelte +4 -2
- package/dist/atoms/ListItemLeading.svelte.d.ts +1 -1
- package/dist/atoms/OptimizedImage.svelte +11 -2
- package/dist/atoms/OptimizedImage.svelte.d.ts +2 -0
- package/dist/atoms/Progress.svelte +4 -1
- package/dist/atoms/Progress.svelte.d.ts +2 -0
- package/dist/atoms/Radio.svelte +4 -0
- package/dist/atoms/Radio.svelte.d.ts +2 -0
- package/dist/atoms/Select.svelte +65 -67
- package/dist/atoms/Select.svelte.d.ts +4 -0
- package/dist/atoms/SelectionControl.svelte +5 -1
- package/dist/atoms/SelectionControl.svelte.d.ts +2 -0
- package/dist/atoms/Skeleton.svelte +5 -3
- package/dist/atoms/Table.svelte +2 -1
- package/dist/atoms/Text.svelte +34 -5
- package/dist/atoms/Text.svelte.d.ts +7 -1
- package/dist/atoms/Textarea.svelte +7 -6
- package/dist/atoms/ThemeToggle.svelte +17 -14
- package/dist/atoms/ThemeToggle.svelte.d.ts +2 -0
- package/dist/atoms/Toast.svelte +10 -8
- package/dist/atoms/Toggle.svelte +21 -13
- package/dist/atoms/Toggle.svelte.d.ts +7 -0
- package/dist/atoms/Tooltip.svelte +89 -57
- package/dist/atoms/Tooltip.svelte.d.ts +15 -0
- package/dist/components/index.d.ts +3 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/organisms/index.d.ts +1 -0
- package/dist/components/organisms/index.d.ts.map +1 -1
- package/dist/components/util/cn.d.ts +8 -0
- package/dist/components/util/cn.d.ts.map +1 -0
- package/dist/components/util/ssr-safe.d.ts +4 -4
- package/dist/components/util/ssr-safe.d.ts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -6
- package/dist/molecules/Alert.svelte +43 -64
- package/dist/molecules/Alert.svelte.d.ts +7 -1
- package/dist/molecules/ComponentDemo.svelte +10 -2
- package/dist/molecules/ComponentDemo.svelte.d.ts +2 -0
- package/dist/molecules/ContactForm.svelte +12 -2
- package/dist/molecules/ContactForm.svelte.d.ts +2 -0
- package/dist/molecules/Dropdown.svelte +40 -11
- package/dist/molecules/Dropdown.svelte.d.ts +4 -0
- package/dist/molecules/EmptyState.svelte +2 -1
- package/dist/molecules/Form.svelte +25 -14
- package/dist/molecules/Form.svelte.d.ts +13 -5
- package/dist/molecules/FormField.svelte +2 -1
- package/dist/molecules/Header.svelte +13 -3
- package/dist/molecules/Header.svelte.d.ts +2 -0
- package/dist/molecules/ImageUpload.svelte +54 -15
- package/dist/molecules/ImageUpload.svelte.d.ts +16 -5
- package/dist/molecules/Modal.svelte +110 -31
- package/dist/molecules/Modal.svelte.d.ts +9 -6
- package/dist/molecules/NavigationMenu.svelte +15 -3
- package/dist/molecules/NavigationMenu.svelte.d.ts +4 -0
- package/dist/molecules/NavigationMenuContent.svelte +36 -38
- package/dist/molecules/NavigationMenuContent.svelte.d.ts +2 -0
- package/dist/molecules/NavigationMenuItem.svelte +10 -2
- package/dist/molecules/NavigationMenuItem.svelte.d.ts +2 -0
- package/dist/molecules/NavigationMenuLink.svelte +10 -2
- package/dist/molecules/NavigationMenuLink.svelte.d.ts +2 -0
- package/dist/molecules/NavigationMenuList.svelte +10 -2
- package/dist/molecules/NavigationMenuList.svelte.d.ts +2 -0
- package/dist/molecules/NavigationMenuTrigger.svelte +16 -3
- package/dist/molecules/NavigationMenuTrigger.svelte.d.ts +2 -0
- package/dist/molecules/Page.svelte +8 -2
- package/dist/molecules/Page.svelte.d.ts +2 -0
- package/dist/molecules/PropsTable.svelte +6 -3
- package/dist/molecules/PropsTable.svelte.d.ts +2 -0
- package/dist/molecules/RadioGroup.svelte +5 -1
- package/dist/molecules/RadioGroup.svelte.d.ts +2 -0
- package/dist/molecules/Section.svelte +12 -4
- package/dist/molecules/Section.svelte.d.ts +3 -1
- package/dist/molecules/SidebarBrandHeader.svelte +13 -4
- package/dist/molecules/SidebarBrandHeader.svelte.d.ts +2 -0
- package/dist/molecules/SidebarFooter.svelte +12 -4
- package/dist/molecules/SidebarFooter.svelte.d.ts +2 -0
- package/dist/molecules/SidebarNavSection.svelte +12 -4
- package/dist/molecules/SidebarNavSection.svelte.d.ts +2 -0
- package/dist/molecules/SlideUp.svelte +85 -21
- package/dist/molecules/SlideUp.svelte.d.ts +5 -4
- package/dist/molecules/Tabs.svelte +25 -19
- package/dist/molecules/Tabs.svelte.d.ts +8 -4
- package/dist/molecules/Toaster.svelte +3 -2
- package/dist/molecules/ToasterToast.svelte +41 -10
- package/dist/molecules/ToasterToast.svelte.d.ts +2 -0
- package/dist/organisms/SidebarAccountPanel.svelte +4 -0
- package/dist/organisms/SidebarAccountPanel.svelte.d.ts +2 -0
- package/dist/organisms/SidebarNavigation.svelte +102 -59
- package/dist/organisms/SidebarNavigation.svelte.d.ts +2 -0
- package/dist/organisms/SidebarPanel.svelte +19 -11
- package/dist/organisms/SidebarPanel.svelte.d.ts +2 -0
- package/dist/organisms/SidebarShell.svelte +112 -0
- package/dist/organisms/SidebarShell.svelte.d.ts +52 -0
- package/dist/organisms/TopNavbar.svelte +88 -17
- package/dist/organisms/TopNavbar.svelte.d.ts +2 -0
- package/dist/organisms/index.d.ts +1 -0
- package/dist/organisms/index.js +1 -0
- package/dist/routes/lib/variant-utils.d.ts +26 -0
- package/dist/routes/lib/{variant-utils.ts → variant-utils.js} +15 -46
- package/dist/types/events.js +1 -0
- package/dist/types/index.js +3 -0
- package/dist/util/cn.d.ts +7 -0
- package/dist/util/cn.js +48 -0
- package/dist/util/ssr-safe.d.ts +4 -4
- package/dist/zabi-components-colors.css +3 -437
- package/dist/zabi-components-theme-dark-only.css +223 -103
- package/dist/zabi-components-theme-dark.css +223 -103
- package/dist/zabi-components-theme-only.css +347 -107
- package/dist/zabi-components-theme.css +347 -107
- package/dist/zabi-components.css +953 -843
- package/docs/lucide-icons.md +3 -1
- package/package.json +20 -25
- package/dist/lib/layout-width-tokens.d.ts +0 -17
- package/dist/lib/layout-width-tokens.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/Button.d.ts +0 -3
- package/dist/lib/showcase/component-docs/Button.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/Input.d.ts +0 -3
- package/dist/lib/showcase/component-docs/Input.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/List.d.ts +0 -3
- package/dist/lib/showcase/component-docs/List.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/Modal.d.ts +0 -3
- package/dist/lib/showcase/component-docs/Modal.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/Radio.d.ts +0 -3
- package/dist/lib/showcase/component-docs/Radio.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/SidebarNavigation.d.ts +0 -3
- package/dist/lib/showcase/component-docs/SidebarNavigation.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/Skeleton.d.ts +0 -3
- package/dist/lib/showcase/component-docs/Skeleton.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/_shared.d.ts +0 -12
- package/dist/lib/showcase/component-docs/_shared.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/index.d.ts +0 -4
- package/dist/lib/showcase/component-docs/index.d.ts.map +0 -1
- package/dist/lib/showcase/components-catalog.d.ts +0 -3
- package/dist/lib/showcase/components-catalog.d.ts.map +0 -1
- package/dist/lib/showcase/components-showcase-constants.d.ts +0 -89
- package/dist/lib/showcase/components-showcase-constants.d.ts.map +0 -1
- package/dist/lib/showcase/docs-sidebar-helpers.d.ts +0 -15
- package/dist/lib/showcase/docs-sidebar-helpers.d.ts.map +0 -1
- package/dist/routes/lib/focus-utils.d.ts +0 -12
- package/dist/routes/lib/focus-utils.ts +0 -110
- package/dist/routes/lib/ssr-safe.ts +0 -58
- package/scripts/fix-lucide-svelte-icon-dts.js +0 -35
package/dist/atoms/Select.svelte
CHANGED
|
@@ -8,8 +8,11 @@
|
|
|
8
8
|
AlertCircle,
|
|
9
9
|
} from "@lucide/svelte";
|
|
10
10
|
import { generateId } from "../util/ssr-safe.js";
|
|
11
|
+
import { cn } from "../util/cn.js";
|
|
11
12
|
|
|
12
13
|
interface Props {
|
|
14
|
+
/** Extra classes for the host element. */
|
|
15
|
+
class?: string;
|
|
13
16
|
value?: string | number | undefined;
|
|
14
17
|
options?: Array<{
|
|
15
18
|
value: string | number;
|
|
@@ -28,6 +31,8 @@
|
|
|
28
31
|
emptyStateActionLabel?: string;
|
|
29
32
|
placeholder?: string;
|
|
30
33
|
label?: string;
|
|
34
|
+
/** When set, a hidden input submits the selected value with native forms. */
|
|
35
|
+
name?: string;
|
|
31
36
|
required?: boolean;
|
|
32
37
|
disabled?: boolean;
|
|
33
38
|
size?: "sm" | "md" | "lg";
|
|
@@ -38,6 +43,7 @@
|
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
let {
|
|
46
|
+
class: className = "",
|
|
41
47
|
value = $bindable(undefined),
|
|
42
48
|
options = [],
|
|
43
49
|
searchable = true,
|
|
@@ -52,6 +58,7 @@
|
|
|
52
58
|
emptyStateActionLabel = "",
|
|
53
59
|
placeholder = "Select an option",
|
|
54
60
|
label = "",
|
|
61
|
+
name = "",
|
|
55
62
|
required = false,
|
|
56
63
|
disabled = false,
|
|
57
64
|
size = "md",
|
|
@@ -66,26 +73,11 @@
|
|
|
66
73
|
let selectContainer: HTMLDivElement;
|
|
67
74
|
let searchQuery = $state("");
|
|
68
75
|
|
|
76
|
+
// Same fixed height scale as Button, IconButton and Input (32 / 40 / 48).
|
|
69
77
|
const sizeClass = $derived(() => {
|
|
70
|
-
if (size === "sm") {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
text: "text-sm",
|
|
74
|
-
leading: "leading-5",
|
|
75
|
-
};
|
|
76
|
-
} else if (size === "lg") {
|
|
77
|
-
return {
|
|
78
|
-
padding: "px-4 py-3",
|
|
79
|
-
text: "text-base",
|
|
80
|
-
leading: "leading-6",
|
|
81
|
-
};
|
|
82
|
-
} else {
|
|
83
|
-
return {
|
|
84
|
-
padding: "px-4 py-2.5",
|
|
85
|
-
text: "text-base",
|
|
86
|
-
leading: "leading-6",
|
|
87
|
-
};
|
|
88
|
-
}
|
|
78
|
+
if (size === "sm") return { box: "h-8 px-3", text: "text-sm" };
|
|
79
|
+
if (size === "lg") return { box: "h-12 px-4", text: "text-base" };
|
|
80
|
+
return { box: "h-10 px-3", text: "text-sm" };
|
|
89
81
|
});
|
|
90
82
|
|
|
91
83
|
const variantClass = $derived(() => {
|
|
@@ -101,24 +93,24 @@
|
|
|
101
93
|
const triggerClasses = $derived(() => {
|
|
102
94
|
const sizeStyles = sizeClass();
|
|
103
95
|
const baseClasses =
|
|
104
|
-
"focus-ring flex w-full cursor-pointer items-center justify-between rounded-
|
|
96
|
+
"focus-ring flex w-full cursor-pointer items-center justify-between gap-2 rounded-control border bg-input text-body transition-colors duration-150 hover:bg-input-hover active:bg-input-focus focus-visible:bg-input-focus focus:outline-none focus-visible:outline-none disabled:cursor-not-allowed disabled:bg-input-disabled disabled:text-action-disabled-text";
|
|
105
97
|
|
|
106
|
-
return `${baseClasses} ${sizeStyles.
|
|
98
|
+
return cn(`${baseClasses} ${sizeStyles.box} ${sizeStyles.text} ${variantClass()}`);
|
|
107
99
|
});
|
|
108
100
|
|
|
109
101
|
const labelClasses = $derived(
|
|
110
|
-
() => "block text-sm font-medium text-label mb-
|
|
102
|
+
() => "block text-sm font-medium text-label mb-2",
|
|
111
103
|
);
|
|
112
104
|
|
|
113
105
|
const messageClasses = $derived(() => {
|
|
114
106
|
if (variant === "error") {
|
|
115
|
-
return "text-error text-sm mt-1 flex items-center gap-
|
|
107
|
+
return "text-error text-sm mt-1 flex items-center gap-2 w-full";
|
|
116
108
|
} else if (variant === "success") {
|
|
117
|
-
return "text-success text-sm mt-1 flex items-center gap-
|
|
109
|
+
return "text-success text-sm mt-1 flex items-center gap-2 w-full";
|
|
118
110
|
} else if (variant === "warning") {
|
|
119
|
-
return "text-warning text-sm mt-1 flex items-center gap-
|
|
111
|
+
return "text-warning text-sm mt-1 flex items-center gap-2 w-full";
|
|
120
112
|
}
|
|
121
|
-
return "text-description text-sm mt-1 flex items-center gap-
|
|
113
|
+
return "text-description text-sm mt-1 flex items-center gap-2 w-full";
|
|
122
114
|
});
|
|
123
115
|
|
|
124
116
|
const getIcon = $derived(() => {
|
|
@@ -130,25 +122,24 @@
|
|
|
130
122
|
|
|
131
123
|
const selectId = generateId("select");
|
|
132
124
|
|
|
133
|
-
const selectedLabel = $derived(() => {
|
|
134
|
-
if (isEmpty()) {
|
|
135
|
-
return String(placeholder || "Select an option");
|
|
136
|
-
}
|
|
137
|
-
const selected = options.find((opt) => opt.value === value);
|
|
138
|
-
return selected?.label
|
|
139
|
-
? String(selected.label)
|
|
140
|
-
: String(placeholder || "Select an option");
|
|
141
|
-
});
|
|
142
|
-
|
|
143
125
|
const isEmpty = $derived(() => {
|
|
144
126
|
return value === undefined || value === null || value === "";
|
|
145
127
|
});
|
|
146
128
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
129
|
+
/** String comparison so `"2"` and `2` match — same rule as Dropdown's `selectedValue`. */
|
|
130
|
+
function isSameValue(
|
|
131
|
+
a: string | number | null | undefined,
|
|
132
|
+
b: string | number | null | undefined,
|
|
133
|
+
): boolean {
|
|
134
|
+
if (a === undefined || a === null || b === undefined || b === null) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
return String(a) === String(b);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const selectedOption = $derived.by(() =>
|
|
141
|
+
options.find((opt) => isSameValue(opt.value, value)),
|
|
142
|
+
);
|
|
152
143
|
|
|
153
144
|
const hasSearchQuery = $derived(() => {
|
|
154
145
|
return searchQuery.trim().length > 0;
|
|
@@ -156,7 +147,7 @@
|
|
|
156
147
|
|
|
157
148
|
const filteredOptions = $derived(() => {
|
|
158
149
|
const query = searchQuery.trim().toLowerCase();
|
|
159
|
-
const availableOptions =
|
|
150
|
+
const availableOptions = options;
|
|
160
151
|
if (!searchable || query.length === 0) {
|
|
161
152
|
return availableOptions;
|
|
162
153
|
}
|
|
@@ -215,7 +206,7 @@
|
|
|
215
206
|
|
|
216
207
|
<svelte:window onclick={handleClickOutside} onkeydown={handleKeydown} />
|
|
217
208
|
|
|
218
|
-
<div bind:this={selectContainer} class="w-full select-container">
|
|
209
|
+
<div bind:this={selectContainer} class={cn("w-full select-container", className)}>
|
|
219
210
|
{#if label}
|
|
220
211
|
<label for={selectId} class={labelClasses()}>{label}</label>
|
|
221
212
|
{/if}
|
|
@@ -250,8 +241,7 @@
|
|
|
250
241
|
{:else}
|
|
251
242
|
{isEmpty()
|
|
252
243
|
? placeholder
|
|
253
|
-
:
|
|
254
|
-
placeholder}
|
|
244
|
+
: selectedOption?.label || placeholder}
|
|
255
245
|
{/if}
|
|
256
246
|
</span>
|
|
257
247
|
<ChevronDown
|
|
@@ -262,29 +252,32 @@
|
|
|
262
252
|
/>
|
|
263
253
|
</button>
|
|
264
254
|
{/snippet}
|
|
255
|
+
{#snippet header()}
|
|
256
|
+
{#if searchable && !isLoading}
|
|
257
|
+
<!-- Outside role="listbox": a textbox is not a valid listbox child. -->
|
|
258
|
+
<div class="px-3 pb-2 pt-1" style:width={menuWidth}>
|
|
259
|
+
<Input
|
|
260
|
+
type="text"
|
|
261
|
+
size="sm"
|
|
262
|
+
class="min-w-0"
|
|
263
|
+
placeholder={searchPlaceholder}
|
|
264
|
+
bind:value={searchQuery}
|
|
265
|
+
aria-label={searchPlaceholder}
|
|
266
|
+
{disabled}
|
|
267
|
+
/>
|
|
268
|
+
</div>
|
|
269
|
+
{/if}
|
|
270
|
+
{/snippet}
|
|
265
271
|
{#snippet children()}
|
|
266
272
|
<div class="px-2 pb-2 pt-1" style:width={menuWidth}>
|
|
267
|
-
{#if searchable && !isLoading}
|
|
268
|
-
<div class="px-1 pb-2">
|
|
269
|
-
<Input
|
|
270
|
-
type="text"
|
|
271
|
-
size="sm"
|
|
272
|
-
class="min-w-0"
|
|
273
|
-
placeholder={searchPlaceholder}
|
|
274
|
-
bind:value={searchQuery}
|
|
275
|
-
aria-label={searchPlaceholder}
|
|
276
|
-
{disabled}
|
|
277
|
-
/>
|
|
278
|
-
</div>
|
|
279
|
-
{/if}
|
|
280
273
|
<div
|
|
281
274
|
class="overflow-y-auto px-1"
|
|
282
275
|
style:max-height={maxMenuHeight}
|
|
283
276
|
>
|
|
284
277
|
{#if isLoading}
|
|
285
278
|
<div class="space-y-2 px-2 py-2" role="status" aria-live="polite">
|
|
286
|
-
<div class="h-8 w-full animate-pulse rounded-
|
|
287
|
-
<div class="h-8 w-full animate-pulse rounded-
|
|
279
|
+
<div class="h-8 w-full animate-pulse rounded-control bg-base-200"></div>
|
|
280
|
+
<div class="h-8 w-full animate-pulse rounded-control bg-base-200"></div>
|
|
288
281
|
<p class="text-xs text-description">{loadingText}</p>
|
|
289
282
|
</div>
|
|
290
283
|
{:else if filteredOptions().length > 0}
|
|
@@ -292,17 +285,19 @@
|
|
|
292
285
|
{@const buttonRestProps = {
|
|
293
286
|
"data-value": String(option.value),
|
|
294
287
|
} as Record<string, string>}
|
|
295
|
-
<div class="w-full my-
|
|
288
|
+
<div class="w-full my-1">
|
|
296
289
|
<button
|
|
297
290
|
type="button"
|
|
298
291
|
role="option"
|
|
299
|
-
aria-selected={value
|
|
292
|
+
aria-selected={isSameValue(value, option.value)
|
|
300
293
|
? true
|
|
301
294
|
: undefined}
|
|
302
|
-
class="focus-ring flex w-full items-center justify-start rounded-
|
|
303
|
-
|
|
295
|
+
class="focus-ring flex w-full items-center justify-start rounded-control border-2 px-3 py-2 text-left text-sm font-medium transition-colors focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 {isSameValue(
|
|
296
|
+
value,
|
|
297
|
+
option.value,
|
|
298
|
+
)
|
|
304
299
|
? 'border-action-primary bg-transparent text-headline'
|
|
305
|
-
: 'border-transparent bg-transparent text-body hover:bg-
|
|
300
|
+
: 'border-transparent bg-transparent text-body hover:bg-surface-overlay-hover'}"
|
|
306
301
|
disabled={option.disabled}
|
|
307
302
|
{...buttonRestProps}
|
|
308
303
|
onclick={() =>
|
|
@@ -317,13 +312,13 @@
|
|
|
317
312
|
{noResultsText}
|
|
318
313
|
</div>
|
|
319
314
|
{:else}
|
|
320
|
-
<div class="rounded-
|
|
315
|
+
<div class="rounded-control border border-border bg-surface-overlay-hover px-3 py-3 text-sm">
|
|
321
316
|
<p class="font-medium text-headline">{emptyStateTitle}</p>
|
|
322
317
|
<p class="mt-1 text-description">{emptyStateDescription}</p>
|
|
323
318
|
{#if emptyStateActionLabel && onEmptyStateAction}
|
|
324
319
|
<button
|
|
325
320
|
type="button"
|
|
326
|
-
class="mt-3 inline-flex min-h-11 cursor-pointer items-center rounded-
|
|
321
|
+
class="mt-3 inline-flex min-h-11 cursor-pointer items-center rounded-control bg-action-primary px-3 py-2 text-sm text-action-primary"
|
|
327
322
|
onclick={onEmptyStateAction}
|
|
328
323
|
>
|
|
329
324
|
{emptyStateActionLabel}
|
|
@@ -335,6 +330,9 @@
|
|
|
335
330
|
</div>
|
|
336
331
|
{/snippet}
|
|
337
332
|
</Dropdown>
|
|
333
|
+
{#if name}
|
|
334
|
+
<input type="hidden" {name} value={isEmpty() ? "" : String(value)} />
|
|
335
|
+
{/if}
|
|
338
336
|
{#if message && variant !== "default"}
|
|
339
337
|
<p
|
|
340
338
|
id={`${selectId}-message`}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
interface Props {
|
|
2
|
+
/** Extra classes for the host element. */
|
|
3
|
+
class?: string;
|
|
2
4
|
value?: string | number | undefined;
|
|
3
5
|
options?: Array<{
|
|
4
6
|
value: string | number;
|
|
@@ -17,6 +19,8 @@ interface Props {
|
|
|
17
19
|
emptyStateActionLabel?: string;
|
|
18
20
|
placeholder?: string;
|
|
19
21
|
label?: string;
|
|
22
|
+
/** When set, a hidden input submits the selected value with native forms. */
|
|
23
|
+
name?: string;
|
|
20
24
|
required?: boolean;
|
|
21
25
|
disabled?: boolean;
|
|
22
26
|
size?: "sm" | "md" | "lg";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from "svelte";
|
|
3
3
|
import { generateId } from "../util/ssr-safe.js";
|
|
4
|
+
import { cn } from "../util/cn.js";
|
|
4
5
|
import {
|
|
5
6
|
SELECTION_CONTROL_LABEL_ROW,
|
|
6
7
|
selectionControlRingOverlayClasses,
|
|
@@ -17,6 +18,8 @@
|
|
|
17
18
|
};
|
|
18
19
|
|
|
19
20
|
interface Props {
|
|
21
|
+
/** Extra classes for the host element. */
|
|
22
|
+
class?: string;
|
|
20
23
|
id?: string;
|
|
21
24
|
type: SelectionControlType;
|
|
22
25
|
shape: SelectionControlShape;
|
|
@@ -33,6 +36,7 @@
|
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
let {
|
|
39
|
+
class: className = "",
|
|
36
40
|
id: idProp,
|
|
37
41
|
type,
|
|
38
42
|
shape,
|
|
@@ -69,7 +73,7 @@
|
|
|
69
73
|
}
|
|
70
74
|
</script>
|
|
71
75
|
|
|
72
|
-
<div class="flex items-center gap-2">
|
|
76
|
+
<div class={cn("flex items-center gap-2", className)}>
|
|
73
77
|
<label for={controlId} class={labelWrapperClasses}>
|
|
74
78
|
<span class={controlContainerClasses}>
|
|
75
79
|
<input
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { cn } from "../util/cn.js";
|
|
3
|
+
|
|
2
4
|
type Variant = "text" | "circle" | "block";
|
|
3
5
|
|
|
4
6
|
interface Props {
|
|
@@ -20,9 +22,9 @@
|
|
|
20
22
|
}: Props = $props();
|
|
21
23
|
|
|
22
24
|
const radius: Record<Variant, string> = {
|
|
23
|
-
text: "rounded-
|
|
25
|
+
text: "rounded-control",
|
|
24
26
|
circle: "rounded-full",
|
|
25
|
-
block: "rounded-
|
|
27
|
+
block: "rounded-control",
|
|
26
28
|
};
|
|
27
29
|
|
|
28
30
|
const defaultShape: Record<Variant, string> = {
|
|
@@ -46,7 +48,7 @@
|
|
|
46
48
|
</script>
|
|
47
49
|
|
|
48
50
|
<div
|
|
49
|
-
class="animate-pulse bg-surface-2
|
|
51
|
+
class={cn("animate-pulse bg-surface-2", defaultShape[variant], radius[variant], className)}
|
|
50
52
|
style={inlineStyle || undefined}
|
|
51
53
|
role="status"
|
|
52
54
|
aria-busy="true"
|
package/dist/atoms/Table.svelte
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
|
+
import { cn } from "../util/cn.js";
|
|
3
4
|
|
|
4
5
|
interface Props {
|
|
5
6
|
/** Visible `<caption>` when set. */
|
|
@@ -12,7 +13,7 @@
|
|
|
12
13
|
</script>
|
|
13
14
|
|
|
14
15
|
<div
|
|
15
|
-
class="overflow-x-auto rounded-
|
|
16
|
+
class={cn("overflow-x-auto rounded-control border border-border shadow-sm", className)}
|
|
16
17
|
>
|
|
17
18
|
<table class="w-full min-w-[20rem] border-collapse text-left text-sm text-body">
|
|
18
19
|
{#if caption}
|
package/dist/atoms/Text.svelte
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
|
|
4
|
+
import { cn } from "../util/cn.js";
|
|
4
5
|
type Tone = 'body' | 'description' | 'caption' | 'headline' | 'label' | 'error';
|
|
5
|
-
type Size = 'sm' | 'md' | 'lg';
|
|
6
|
+
type Size = 'xs' | 'sm' | 'md' | 'lg';
|
|
7
|
+
type Weight = 'normal' | 'medium' | 'semibold' | 'bold';
|
|
6
8
|
|
|
7
9
|
interface Props {
|
|
8
10
|
as?: 'p' | 'span' | 'div';
|
|
9
11
|
tone?: Tone;
|
|
10
12
|
size?: Size;
|
|
13
|
+
/**
|
|
14
|
+
* Overrides the weight the tone implies — `label` sets `medium`,
|
|
15
|
+
* every other tone sets `normal`.
|
|
16
|
+
*/
|
|
17
|
+
weight?: Weight;
|
|
11
18
|
class?: string;
|
|
12
19
|
children?: Snippet;
|
|
13
20
|
}
|
|
@@ -16,6 +23,7 @@
|
|
|
16
23
|
as: Tag = 'p',
|
|
17
24
|
tone = 'body',
|
|
18
25
|
size = 'md',
|
|
26
|
+
weight,
|
|
19
27
|
class: className = '',
|
|
20
28
|
children,
|
|
21
29
|
}: Props = $props();
|
|
@@ -29,16 +37,37 @@
|
|
|
29
37
|
error: 'text-error',
|
|
30
38
|
};
|
|
31
39
|
|
|
40
|
+
/**
|
|
41
|
+
* The bottom of the same ramp `Heading` sits on, so the two components
|
|
42
|
+
* share one scale rather than each carrying its own. `md` matches the
|
|
43
|
+
* metrics of an `h6` (text-base/leading-6) and `lg` those of an `h5`
|
|
44
|
+
* (text-lg/leading-7) — a Text set at that size lands on the same line
|
|
45
|
+
* box as the heading above it. Leading is stated rather than inherited
|
|
46
|
+
* from Tailwind's defaults so the shared steps stay locked together if
|
|
47
|
+
* the type scale is retuned.
|
|
48
|
+
*/
|
|
32
49
|
const sizeClass: Record<Size, string> = {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
50
|
+
xs: 'text-xs leading-4',
|
|
51
|
+
sm: 'text-sm leading-5',
|
|
52
|
+
md: 'text-base leading-6',
|
|
53
|
+
lg: 'text-lg leading-7',
|
|
36
54
|
};
|
|
55
|
+
|
|
56
|
+
const weightClass: Record<Weight, string> = {
|
|
57
|
+
normal: 'font-normal',
|
|
58
|
+
medium: 'font-medium',
|
|
59
|
+
semibold: 'font-semibold',
|
|
60
|
+
bold: 'font-bold',
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/** Labels read as labels by default; callers can still override. */
|
|
64
|
+
const defaultWeight: Weight = $derived(tone === 'label' ? 'medium' : 'normal');
|
|
65
|
+
const resolvedWeight = $derived(weight ?? defaultWeight);
|
|
37
66
|
</script>
|
|
38
67
|
|
|
39
68
|
<svelte:element
|
|
40
69
|
this={Tag}
|
|
41
|
-
class={
|
|
70
|
+
class={cn(toneClass[tone], sizeClass[size], weightClass[resolvedWeight], className)}
|
|
42
71
|
>
|
|
43
72
|
{#if children}
|
|
44
73
|
{@render children()}
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
2
|
type Tone = 'body' | 'description' | 'caption' | 'headline' | 'label' | 'error';
|
|
3
|
-
type Size = 'sm' | 'md' | 'lg';
|
|
3
|
+
type Size = 'xs' | 'sm' | 'md' | 'lg';
|
|
4
|
+
type Weight = 'normal' | 'medium' | 'semibold' | 'bold';
|
|
4
5
|
interface Props {
|
|
5
6
|
as?: 'p' | 'span' | 'div';
|
|
6
7
|
tone?: Tone;
|
|
7
8
|
size?: Size;
|
|
9
|
+
/**
|
|
10
|
+
* Overrides the weight the tone implies — `label` sets `medium`,
|
|
11
|
+
* every other tone sets `normal`.
|
|
12
|
+
*/
|
|
13
|
+
weight?: Weight;
|
|
8
14
|
class?: string;
|
|
9
15
|
children?: Snippet;
|
|
10
16
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { CheckCircle, AlertTriangle, AlertCircle } from '@lucide/svelte';
|
|
3
3
|
import type { SemanticVariant, SizeVariant } from '../types/variants.js';
|
|
4
4
|
import { generateId } from "../util/ssr-safe.js";
|
|
5
|
+
import { cn } from "../util/cn.js";
|
|
5
6
|
|
|
6
7
|
interface Props {
|
|
7
8
|
id?: string;
|
|
@@ -56,9 +57,9 @@
|
|
|
56
57
|
|
|
57
58
|
const textareaClasses = $derived(() => {
|
|
58
59
|
const baseClasses =
|
|
59
|
-
'focus-ring w-full border bg-input hover:bg-input-hover focus-visible:bg-input-focus disabled:bg-input-disabled rounded-
|
|
60
|
+
'focus-ring w-full border bg-input hover:bg-input-hover focus-visible:bg-input-focus disabled:bg-input-disabled rounded-control transition-colors duration-150 placeholder:text-input-placeholder text-body focus:outline-none focus-visible:outline-none disabled:text-action-disabled-text disabled:cursor-not-allowed resize-y px-3 py-2 text-sm leading-6';
|
|
60
61
|
|
|
61
|
-
return `${baseClasses} ${variantClass()} ${className}
|
|
62
|
+
return cn(`${baseClasses} ${variantClass()} ${className}`);
|
|
62
63
|
});
|
|
63
64
|
|
|
64
65
|
const labelClasses = $derived(
|
|
@@ -67,13 +68,13 @@
|
|
|
67
68
|
|
|
68
69
|
const messageClasses = $derived(() => {
|
|
69
70
|
if (variant === 'error') {
|
|
70
|
-
return 'mt-1 flex items-center gap-
|
|
71
|
+
return 'mt-1 flex items-center gap-2 text-sm text-error';
|
|
71
72
|
} else if (variant === 'success') {
|
|
72
|
-
return 'mt-1 flex items-center gap-
|
|
73
|
+
return 'mt-1 flex items-center gap-2 text-sm text-success';
|
|
73
74
|
} else if (variant === 'warning') {
|
|
74
|
-
return 'mt-1 flex items-center gap-
|
|
75
|
+
return 'mt-1 flex items-center gap-2 text-sm text-warning';
|
|
75
76
|
}
|
|
76
|
-
return 'mt-1 flex items-center gap-
|
|
77
|
+
return 'mt-1 flex items-center gap-2 text-sm text-description';
|
|
77
78
|
});
|
|
78
79
|
|
|
79
80
|
const getIcon = $derived(() => {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { onMount } from "svelte";
|
|
3
3
|
import { Sun, Moon } from "@lucide/svelte";
|
|
4
4
|
import { FOCUS_BRAND_CLASS } from "../util/focus-utils.js";
|
|
5
|
+
import { cn } from "../util/cn.js";
|
|
5
6
|
|
|
6
7
|
function safeLocalStorage(): Storage | undefined {
|
|
7
8
|
return typeof window !== "undefined" ? localStorage : undefined;
|
|
@@ -12,6 +13,8 @@
|
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
interface Props {
|
|
16
|
+
/** Extra classes for the host element. */
|
|
17
|
+
class?: string;
|
|
15
18
|
size?: "sm" | "md" | "lg";
|
|
16
19
|
variant?: "default" | "ghost" | "outline";
|
|
17
20
|
disabled?: boolean;
|
|
@@ -19,9 +22,11 @@
|
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
let {
|
|
25
|
+
class: className = "",
|
|
22
26
|
size = "md",
|
|
23
27
|
variant = "default",
|
|
24
28
|
disabled = false,
|
|
29
|
+
onclick,
|
|
25
30
|
...restProps
|
|
26
31
|
}: Props = $props();
|
|
27
32
|
|
|
@@ -57,10 +62,8 @@
|
|
|
57
62
|
if (mounted && storage) {
|
|
58
63
|
storage.setItem("theme", isDark ? "dark" : "light");
|
|
59
64
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
(onclick as (event: Event) => void)(event);
|
|
63
|
-
}
|
|
65
|
+
|
|
66
|
+
onclick?.(event);
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
function updateTheme() {
|
|
@@ -97,20 +100,20 @@
|
|
|
97
100
|
|
|
98
101
|
const variantClass = $derived(() => {
|
|
99
102
|
if (variant === "ghost") {
|
|
100
|
-
return "bg-transparent hover:bg-
|
|
103
|
+
return "bg-transparent hover:bg-surface-hover active:bg-surface-active border-0";
|
|
101
104
|
} else if (variant === "outline") {
|
|
102
|
-
return "bg-
|
|
105
|
+
return "bg-action-secondary hover:bg-action-secondary-hover active:bg-action-secondary-active border border-border";
|
|
103
106
|
} else {
|
|
104
|
-
return "bg-
|
|
107
|
+
return "bg-action-secondary hover:bg-action-secondary-hover active:bg-action-secondary-active border-0";
|
|
105
108
|
}
|
|
106
109
|
});
|
|
107
110
|
|
|
108
111
|
const buttonClasses = $derived(() => {
|
|
109
112
|
const sizeStyles = sizeClass();
|
|
110
|
-
return `
|
|
113
|
+
return cn(`
|
|
111
114
|
${sizeStyles.button}
|
|
112
115
|
${variantClass()}
|
|
113
|
-
rounded-
|
|
116
|
+
rounded-control
|
|
114
117
|
flex
|
|
115
118
|
items-center
|
|
116
119
|
justify-center
|
|
@@ -122,16 +125,16 @@
|
|
|
122
125
|
disabled:opacity-50
|
|
123
126
|
disabled:cursor-not-allowed
|
|
124
127
|
disabled:active:scale-100
|
|
125
|
-
disabled:hover:bg-
|
|
128
|
+
disabled:hover:bg-action-disabled
|
|
126
129
|
${FOCUS_BRAND_CLASS}
|
|
127
|
-
|
|
130
|
+
`).replace(/\s+/g, " ");
|
|
128
131
|
});
|
|
129
132
|
</script>
|
|
130
133
|
|
|
131
134
|
{#if mounted}
|
|
132
135
|
<button
|
|
133
136
|
onclick={toggleTheme}
|
|
134
|
-
class={buttonClasses()}
|
|
137
|
+
class="{buttonClasses()} {className}"
|
|
135
138
|
aria-label={isDark ? "Switch to light mode" : "Switch to dark mode"}
|
|
136
139
|
aria-pressed={isDark}
|
|
137
140
|
type="button"
|
|
@@ -146,12 +149,12 @@
|
|
|
146
149
|
</button>
|
|
147
150
|
{:else}
|
|
148
151
|
<button
|
|
149
|
-
class="
|
|
152
|
+
class="{sizeClass().button} {variantClass()} rounded-control flex items-center justify-center text-label cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
|
|
150
153
|
aria-label="Theme toggle"
|
|
151
154
|
type="button"
|
|
152
155
|
{disabled}
|
|
153
156
|
{...restProps}
|
|
154
157
|
>
|
|
155
|
-
<Sun size={
|
|
158
|
+
<Sun size={sizeClass().icon} class="text-label" />
|
|
156
159
|
</button>
|
|
157
160
|
{/if}
|
package/dist/atoms/Toast.svelte
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { cn } from "../util/cn.js";
|
|
3
|
+
|
|
2
4
|
interface Props {
|
|
3
5
|
message?: string;
|
|
4
6
|
type?: 'success' | 'error' | 'warning' | 'info';
|
|
@@ -22,14 +24,14 @@
|
|
|
22
24
|
let isVisible = $state(true);
|
|
23
25
|
|
|
24
26
|
const typeClasses: Record<NonNullable<Props['type']>, string> = {
|
|
25
|
-
success: 'border-success bg-
|
|
26
|
-
error: 'border-error bg-
|
|
27
|
-
warning: 'border-warning bg-
|
|
28
|
-
info: 'border-border bg-
|
|
27
|
+
success: 'border-success bg-surface-overlay text-success',
|
|
28
|
+
error: 'border-error bg-surface-overlay text-error',
|
|
29
|
+
warning: 'border-warning bg-surface-overlay text-warning',
|
|
30
|
+
info: 'border-border bg-surface-overlay text-body',
|
|
29
31
|
};
|
|
30
32
|
|
|
31
33
|
const cardClasses =
|
|
32
|
-
'box-border w-full min-w-[18rem] max-w-[min(24rem,calc(100vw-2rem))] overflow-hidden rounded-
|
|
34
|
+
'box-border w-full min-w-[18rem] max-w-[min(24rem,calc(100vw-2rem))] overflow-hidden rounded-control border p-4 shadow-lg';
|
|
33
35
|
|
|
34
36
|
function closeToast(event: Event) {
|
|
35
37
|
isVisible = false;
|
|
@@ -46,7 +48,7 @@
|
|
|
46
48
|
{#if closable}
|
|
47
49
|
<button
|
|
48
50
|
type="button"
|
|
49
|
-
class="focus-ring shrink-0 cursor-pointer rounded-
|
|
51
|
+
class="focus-ring shrink-0 cursor-pointer rounded-control text-description hover:text-headline focus:outline-none"
|
|
50
52
|
onclick={closeToast}
|
|
51
53
|
aria-label="Close notification"
|
|
52
54
|
>
|
|
@@ -62,7 +64,7 @@
|
|
|
62
64
|
class="pointer-events-none fixed top-4 right-4 left-4 z-toast flex justify-end sm:left-auto"
|
|
63
65
|
>
|
|
64
66
|
<div
|
|
65
|
-
class="pointer-events-auto
|
|
67
|
+
class={cn("pointer-events-auto", cardClasses, typeClasses[type], className)}
|
|
66
68
|
role="alert"
|
|
67
69
|
{...restProps}
|
|
68
70
|
>
|
|
@@ -71,7 +73,7 @@
|
|
|
71
73
|
</div>
|
|
72
74
|
{:else}
|
|
73
75
|
<div
|
|
74
|
-
class="relative z-toast mx-auto
|
|
76
|
+
class={cn("relative z-toast mx-auto", cardClasses, typeClasses[type], className)}
|
|
75
77
|
role="alert"
|
|
76
78
|
{...restProps}
|
|
77
79
|
>
|