zabi-components 5.0.21 → 6.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 +42 -44
- package/THEME.md +473 -0
- package/THEMING.md +248 -0
- package/dist/atoms/Card.svelte +40 -67
- package/dist/atoms/Card.svelte.d.ts +3 -3
- package/dist/atoms/CardContent.svelte +1 -1
- package/dist/atoms/CardHeader.svelte +10 -1
- package/dist/atoms/CardHeader.svelte.d.ts +3 -0
- package/dist/atoms/Checkbox.svelte +1 -7
- package/dist/atoms/CodeBlock.svelte +7 -7
- package/dist/atoms/ColorPicker.svelte +81 -73
- package/dist/atoms/Input.svelte +16 -12
- package/dist/atoms/Input.svelte.d.ts +2 -0
- package/dist/atoms/List.svelte +47 -0
- package/dist/atoms/List.svelte.d.ts +19 -0
- package/dist/atoms/ListItem.svelte +148 -0
- package/dist/atoms/ListItem.svelte.d.ts +35 -0
- package/dist/atoms/Progress.svelte +4 -6
- package/dist/atoms/Select.svelte +65 -20
- package/dist/atoms/Select.svelte.d.ts +13 -1
- package/dist/atoms/Textarea.svelte +13 -11
- package/dist/atoms/Textarea.svelte.d.ts +1 -0
- package/dist/atoms/Toast.svelte +1 -1
- package/dist/atoms/Toggle.svelte +1 -7
- package/dist/atoms/Tooltip.svelte +8 -15
- package/dist/atoms/index.d.ts +2 -0
- package/dist/atoms/index.js +2 -0
- package/dist/components/atoms/index.d.ts +2 -0
- package/dist/components/atoms/index.d.ts.map +1 -1
- package/dist/components/index.d.ts +4 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/molecules/index.d.ts +0 -1
- package/dist/components/molecules/index.d.ts.map +1 -1
- package/dist/components/molecules/navigation-menu-context.d.ts +7 -0
- package/dist/components/molecules/navigation-menu-context.d.ts.map +1 -0
- package/dist/components/organisms/index.d.ts +2 -1
- package/dist/components/organisms/index.d.ts.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +5 -9
- package/dist/lib/showcase/components-catalog.d.ts +3 -0
- package/dist/lib/showcase/components-catalog.d.ts.map +1 -0
- package/dist/lib/showcase/components-showcase-constants.d.ts +63 -0
- package/dist/lib/showcase/components-showcase-constants.d.ts.map +1 -0
- package/dist/lib/showcase/docs-sidebar-helpers.d.ts +15 -0
- package/dist/lib/showcase/docs-sidebar-helpers.d.ts.map +1 -0
- package/dist/molecules/Alert.svelte +9 -4
- package/dist/molecules/ComponentDemo.svelte +53 -50
- package/dist/molecules/ContactForm.svelte +113 -76
- package/dist/molecules/ContactForm.svelte.d.ts +1 -4
- package/dist/molecules/Dropdown.svelte +4 -2
- package/dist/molecules/ImageUpload.svelte +27 -4
- package/dist/molecules/ImageUpload.svelte.d.ts +1 -0
- package/dist/molecules/Modal.svelte +46 -45
- package/dist/molecules/NavigationMenu.svelte +7 -10
- package/dist/molecules/NavigationMenuContent.svelte +9 -9
- package/dist/molecules/NavigationMenuItem.svelte +7 -6
- package/dist/molecules/NavigationMenuLink.svelte +8 -6
- package/dist/molecules/NavigationMenuList.svelte +0 -7
- package/dist/molecules/NavigationMenuTrigger.svelte +8 -11
- package/dist/molecules/Section.svelte +12 -12
- package/dist/molecules/SlideUp.svelte +32 -2
- package/dist/molecules/Tabs.svelte +74 -7
- package/dist/molecules/index.d.ts +0 -1
- package/dist/molecules/index.js +0 -1
- package/dist/molecules/navigation-menu-context.d.ts +6 -0
- package/dist/molecules/navigation-menu-context.js +1 -0
- package/dist/organisms/Navbar.svelte +170 -53
- package/dist/organisms/Navbar.svelte.d.ts +31 -4
- package/dist/organisms/SidebarNavigation.svelte +464 -0
- package/dist/organisms/SidebarNavigation.svelte.d.ts +49 -0
- package/dist/organisms/SidebarPanel.svelte +175 -0
- package/dist/organisms/SidebarPanel.svelte.d.ts +32 -0
- package/dist/organisms/index.d.ts +2 -1
- package/dist/organisms/index.js +2 -1
- package/dist/types/page.types.d.ts +32 -0
- package/dist/types/page.types.d.ts.map +1 -0
- package/dist/types/variants.ts +76 -0
- package/dist/zabi-components-colors.css +347 -324
- package/dist/zabi-components-theme-dark-only.css +159 -151
- package/dist/zabi-components-theme-dark.css +159 -151
- package/dist/zabi-components-theme-only.css +245 -230
- package/dist/zabi-components-theme.css +245 -230
- package/dist/zabi-components.css +722 -84
- package/docs/theme-imports.md +112 -0
- package/package.json +20 -9
- package/dist/molecules/Sidebar.svelte +0 -324
- package/dist/molecules/Sidebar.svelte.d.ts +0 -30
- package/dist/organisms/Navigation.svelte +0 -116
- package/dist/organisms/Navigation.svelte.d.ts +0 -20
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import Input from "./Input.svelte";
|
|
3
|
-
import { onMount, onDestroy } from "svelte";
|
|
3
|
+
import { onMount, onDestroy, tick } from "svelte";
|
|
4
4
|
|
|
5
5
|
interface Props {
|
|
6
6
|
value?: string;
|
|
@@ -20,11 +20,10 @@
|
|
|
20
20
|
}: Props = $props();
|
|
21
21
|
|
|
22
22
|
let isOpen = $state(false);
|
|
23
|
-
let pickerContainer: HTMLDivElement;
|
|
24
|
-
let colorMap: HTMLCanvasElement;
|
|
25
|
-
let colorMapContainer: HTMLDivElement;
|
|
26
|
-
let hueSlider: HTMLInputElement;
|
|
27
23
|
let isDragging = $state(false);
|
|
24
|
+
let pickerContainer: HTMLDivElement | undefined;
|
|
25
|
+
let colorMap: HTMLCanvasElement | undefined;
|
|
26
|
+
let colorMapContainer: HTMLDivElement | undefined;
|
|
28
27
|
|
|
29
28
|
// HSL values (0-360, 0-100, 0-100)
|
|
30
29
|
let hue = $state(0);
|
|
@@ -96,46 +95,56 @@
|
|
|
96
95
|
}
|
|
97
96
|
}
|
|
98
97
|
|
|
99
|
-
const variant = $derived(()
|
|
100
|
-
if (value && !isValidHex(value)) {
|
|
101
|
-
return "error";
|
|
102
|
-
}
|
|
103
|
-
return "default";
|
|
104
|
-
});
|
|
98
|
+
const variant = $derived(value && !isValidHex(value) ? "error" : "default");
|
|
105
99
|
|
|
106
|
-
const message = $derived(
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
});
|
|
100
|
+
const message = $derived(
|
|
101
|
+
value && !isValidHex(value)
|
|
102
|
+
? "Please enter a valid hex color (e.g., #ff0000 or #f00)"
|
|
103
|
+
: "",
|
|
104
|
+
);
|
|
112
105
|
|
|
113
106
|
function handleInput(event: Event) {
|
|
114
107
|
if (onchange) onchange(event);
|
|
115
108
|
updateFromHex(value);
|
|
116
109
|
}
|
|
117
110
|
|
|
118
|
-
function
|
|
119
|
-
|
|
120
|
-
let hexValue = target.value.trim();
|
|
111
|
+
function normalizeHexValue(rawValue: string): string {
|
|
112
|
+
let hexValue = rawValue.trim();
|
|
121
113
|
|
|
122
114
|
if (hexValue && !hexValue.startsWith("#")) {
|
|
123
115
|
hexValue = "#" + hexValue;
|
|
124
116
|
}
|
|
125
117
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
118
|
+
return hexValue;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function commitCurrentValue(event: Event) {
|
|
122
|
+
const normalizedHex = normalizeHexValue(value);
|
|
123
|
+
|
|
124
|
+
if (normalizedHex === "" || isValidHex(normalizedHex)) {
|
|
125
|
+
value = normalizedHex;
|
|
126
|
+
updateFromHex(normalizedHex);
|
|
129
127
|
if (onchange) onchange(event);
|
|
130
128
|
}
|
|
131
129
|
}
|
|
132
130
|
|
|
133
|
-
function
|
|
131
|
+
function handleBlur(event: Event) {
|
|
132
|
+
commitCurrentValue(event);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function togglePicker() {
|
|
134
136
|
if (!disabled) {
|
|
137
|
+
if (isOpen) {
|
|
138
|
+
commitCurrentValue(new Event("change"));
|
|
139
|
+
}
|
|
135
140
|
isOpen = !isOpen;
|
|
136
141
|
if (isOpen && value && isValidHex(value)) {
|
|
137
142
|
updateFromHex(value);
|
|
138
143
|
}
|
|
144
|
+
if (isOpen) {
|
|
145
|
+
await tick();
|
|
146
|
+
drawColorMap();
|
|
147
|
+
}
|
|
139
148
|
}
|
|
140
149
|
}
|
|
141
150
|
|
|
@@ -178,6 +187,10 @@
|
|
|
178
187
|
}
|
|
179
188
|
}
|
|
180
189
|
|
|
190
|
+
function handleMouseUp() {
|
|
191
|
+
isDragging = false;
|
|
192
|
+
}
|
|
193
|
+
|
|
181
194
|
function handleHueChange(event: Event) {
|
|
182
195
|
const target = event.target as HTMLInputElement;
|
|
183
196
|
hue = parseInt(target.value);
|
|
@@ -185,26 +198,38 @@
|
|
|
185
198
|
drawColorMap();
|
|
186
199
|
}
|
|
187
200
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
201
|
+
function setPickerContainer(node: HTMLDivElement) {
|
|
202
|
+
pickerContainer = node;
|
|
203
|
+
return {
|
|
204
|
+
destroy() {
|
|
205
|
+
if (pickerContainer === node) {
|
|
206
|
+
pickerContainer = undefined;
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
}
|
|
196
211
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
212
|
+
function setColorMap(node: HTMLCanvasElement) {
|
|
213
|
+
colorMap = node;
|
|
214
|
+
return {
|
|
215
|
+
destroy() {
|
|
216
|
+
if (colorMap === node) {
|
|
217
|
+
colorMap = undefined;
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
}
|
|
202
222
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
223
|
+
function setColorMapContainer(node: HTMLDivElement) {
|
|
224
|
+
colorMapContainer = node;
|
|
225
|
+
return {
|
|
226
|
+
destroy() {
|
|
227
|
+
if (colorMapContainer === node) {
|
|
228
|
+
colorMapContainer = undefined;
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
}
|
|
208
233
|
|
|
209
234
|
function handleClickOutside(event: MouseEvent) {
|
|
210
235
|
if (
|
|
@@ -214,6 +239,7 @@
|
|
|
214
239
|
'[aria-label="Open color picker"]',
|
|
215
240
|
)
|
|
216
241
|
) {
|
|
242
|
+
commitCurrentValue(new Event("change"));
|
|
217
243
|
isOpen = false;
|
|
218
244
|
}
|
|
219
245
|
}
|
|
@@ -223,36 +249,19 @@
|
|
|
223
249
|
updateFromHex(value);
|
|
224
250
|
}
|
|
225
251
|
window.addEventListener("mousedown", handleClickOutside);
|
|
226
|
-
window.addEventListener("mouseup",
|
|
227
|
-
isDragging = false;
|
|
228
|
-
});
|
|
252
|
+
window.addEventListener("mouseup", handleMouseUp);
|
|
229
253
|
window.addEventListener("mousemove", handleColorMapDrag);
|
|
230
254
|
});
|
|
231
255
|
|
|
232
256
|
onDestroy(() => {
|
|
233
257
|
window.removeEventListener("mousedown", handleClickOutside);
|
|
234
|
-
window.removeEventListener("mouseup",
|
|
258
|
+
window.removeEventListener("mouseup", handleMouseUp);
|
|
235
259
|
window.removeEventListener("mousemove", handleColorMapDrag);
|
|
236
260
|
});
|
|
237
261
|
|
|
238
|
-
const displayColor = $derived(()
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
}
|
|
242
|
-
return placeholder;
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
const currentHslColor = $derived(() => {
|
|
246
|
-
return `hsl(${hue}, 100%, 50%)`;
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
const pickerIndicatorX = $derived(() => {
|
|
250
|
-
return `${(saturation / 100) * 100}%`;
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
const pickerIndicatorY = $derived(() => {
|
|
254
|
-
return `${100 - (lightness / 100) * 100}%`;
|
|
255
|
-
});
|
|
262
|
+
const displayColor = $derived(value && isValidHex(value) ? value : placeholder);
|
|
263
|
+
const pickerIndicatorX = $derived(`${saturation}%`);
|
|
264
|
+
const pickerIndicatorY = $derived(`${100 - lightness}%`);
|
|
256
265
|
</script>
|
|
257
266
|
|
|
258
267
|
<div {...restProps}>
|
|
@@ -263,8 +272,8 @@
|
|
|
263
272
|
{label}
|
|
264
273
|
{placeholder}
|
|
265
274
|
{disabled}
|
|
266
|
-
|
|
267
|
-
|
|
275
|
+
{variant}
|
|
276
|
+
{message}
|
|
268
277
|
oninput={handleInput}
|
|
269
278
|
onblur={handleBlur}
|
|
270
279
|
aria-label="Hex color input"
|
|
@@ -276,22 +285,22 @@
|
|
|
276
285
|
onclick={togglePicker}
|
|
277
286
|
{disabled}
|
|
278
287
|
class="w-11 h-11 rounded-lg border-2 border-card shrink-0 cursor-pointer hover:ring-2 hover:ring-brand-500 transition-colors disabled:cursor-not-allowed disabled:opacity-50"
|
|
279
|
-
style="background-color: {displayColor
|
|
288
|
+
style="background-color: {displayColor};"
|
|
280
289
|
aria-label="Open color picker"
|
|
281
290
|
aria-expanded={isOpen}
|
|
282
291
|
></button>
|
|
283
292
|
|
|
284
293
|
{#if isOpen}
|
|
285
294
|
<div
|
|
286
|
-
|
|
287
|
-
class="absolute top-12 right-0 z-50 bg-input rounded-2xl shadow-lg p-4 w-80"
|
|
295
|
+
use:setPickerContainer
|
|
296
|
+
class="absolute top-12 right-0 z-50 border border-input-border bg-input rounded-2xl shadow-lg p-4 w-80"
|
|
288
297
|
role="dialog"
|
|
289
298
|
aria-label="Color picker"
|
|
290
299
|
>
|
|
291
300
|
<div class="space-y-4">
|
|
292
301
|
<!-- Color Map -->
|
|
293
302
|
<div
|
|
294
|
-
|
|
303
|
+
use:setColorMapContainer
|
|
295
304
|
class="relative w-full h-48 rounded-xl overflow-hidden cursor-crosshair"
|
|
296
305
|
onmousedown={(e) => {
|
|
297
306
|
isDragging = true;
|
|
@@ -301,7 +310,7 @@
|
|
|
301
310
|
tabindex="0"
|
|
302
311
|
>
|
|
303
312
|
<canvas
|
|
304
|
-
|
|
313
|
+
use:setColorMap
|
|
305
314
|
width={256}
|
|
306
315
|
height={192}
|
|
307
316
|
class="w-full h-full"
|
|
@@ -309,7 +318,7 @@
|
|
|
309
318
|
<!-- Color indicator -->
|
|
310
319
|
<div
|
|
311
320
|
class="absolute w-4 h-4 border-2 border-white rounded-full shadow-lg pointer-events-none transform -translate-x-1/2 -translate-y-1/2"
|
|
312
|
-
style="left: {pickerIndicatorX
|
|
321
|
+
style="left: {pickerIndicatorX}; top: {pickerIndicatorY};"
|
|
313
322
|
></div>
|
|
314
323
|
</div>
|
|
315
324
|
|
|
@@ -323,7 +332,6 @@
|
|
|
323
332
|
style="background: linear-gradient(to right, hsl(0,100%,50%), hsl(60,100%,50%), hsl(120,100%,50%), hsl(180,100%,50%), hsl(240,100%,50%), hsl(300,100%,50%), hsl(360,100%,50%));"
|
|
324
333
|
></div>
|
|
325
334
|
<input
|
|
326
|
-
bind:this={hueSlider}
|
|
327
335
|
type="range"
|
|
328
336
|
min="0"
|
|
329
337
|
max="360"
|
package/dist/atoms/Input.svelte
CHANGED
|
@@ -1,21 +1,16 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { CheckCircle, AlertTriangle, AlertCircle } from "@lucide/svelte";
|
|
3
3
|
import type { SemanticVariant, SizeVariant } from "../../types/variants.js";
|
|
4
|
-
|
|
5
|
-
function generateId(prefix: string = "id"): string {
|
|
6
|
-
if (typeof window !== "undefined") {
|
|
7
|
-
return `${prefix}-${Math.random().toString(36).substr(2, 9)}`;
|
|
8
|
-
} else {
|
|
9
|
-
return `${prefix}-ssr-${Date.now()}`;
|
|
10
|
-
}
|
|
11
|
-
}
|
|
4
|
+
import { generateId } from "../../routes/lib/ssr-safe.js";
|
|
12
5
|
|
|
13
6
|
interface Props {
|
|
14
7
|
value?: string;
|
|
15
8
|
type?: string;
|
|
16
9
|
name?: string;
|
|
10
|
+
class?: string;
|
|
17
11
|
label?: string;
|
|
18
12
|
placeholder?: string;
|
|
13
|
+
required?: boolean;
|
|
19
14
|
disabled?: boolean;
|
|
20
15
|
size?: SizeVariant;
|
|
21
16
|
variant?: SemanticVariant;
|
|
@@ -29,8 +24,10 @@
|
|
|
29
24
|
value = $bindable(""),
|
|
30
25
|
type = "text",
|
|
31
26
|
name = "",
|
|
27
|
+
class: className = "",
|
|
32
28
|
label = "",
|
|
33
29
|
placeholder = "",
|
|
30
|
+
required = false,
|
|
34
31
|
disabled = false,
|
|
35
32
|
size = "md",
|
|
36
33
|
variant = "default",
|
|
@@ -71,15 +68,15 @@
|
|
|
71
68
|
? "border-warning focus:border-warning focus:ring-warning"
|
|
72
69
|
: variant === "error"
|
|
73
70
|
? "border-error focus:border-error focus:ring-error"
|
|
74
|
-
: "border-
|
|
71
|
+
: "border-input-border focus:border-input-border focus:ring-2 focus:ring-brand-500";
|
|
75
72
|
});
|
|
76
73
|
|
|
77
74
|
const inputClasses = $derived(() => {
|
|
78
75
|
const sizeStyles = sizeClass();
|
|
79
76
|
const baseClasses =
|
|
80
|
-
"w-full min-w-48 bg-input hover:bg-input-hover focus:bg-input-focus disabled:bg-input-disabled rounded-lg transition-all duration-200 placeholder:text-description text-body focus:outline-none focus:ring-offset-0 disabled:opacity-50 disabled:cursor-not-allowed";
|
|
77
|
+
"w-full min-w-48 border bg-input hover:bg-input-hover focus:bg-input-focus disabled:bg-input-disabled rounded-lg transition-all duration-200 placeholder:text-description text-body focus:outline-none focus:ring-offset-0 disabled:opacity-50 disabled:cursor-not-allowed";
|
|
81
78
|
|
|
82
|
-
return `${baseClasses} ${sizeStyles.padding} ${sizeStyles.text} ${sizeStyles.leading} ${variantClass()}`.trim();
|
|
79
|
+
return `${baseClasses} ${sizeStyles.padding} ${sizeStyles.text} ${sizeStyles.leading} ${variantClass()} ${className}`.trim();
|
|
83
80
|
});
|
|
84
81
|
|
|
85
82
|
const labelClasses = $derived(
|
|
@@ -121,16 +118,23 @@
|
|
|
121
118
|
{name}
|
|
122
119
|
bind:value
|
|
123
120
|
{placeholder}
|
|
121
|
+
{required}
|
|
124
122
|
{disabled}
|
|
125
123
|
class={inputClasses()}
|
|
126
124
|
oninput={handleInput}
|
|
127
125
|
{onblur}
|
|
128
126
|
aria-invalid={variant === "error" ? "true" : undefined}
|
|
127
|
+
aria-required={required ? "true" : undefined}
|
|
129
128
|
aria-describedby={message ? `${inputId}-message` : undefined}
|
|
130
129
|
{...restProps}
|
|
131
130
|
/>
|
|
132
131
|
{#if message && variant !== "default"}
|
|
133
|
-
<p
|
|
132
|
+
<p
|
|
133
|
+
id={`${inputId}-message`}
|
|
134
|
+
class={messageClasses()}
|
|
135
|
+
role={variant === "error" ? "alert" : "status"}
|
|
136
|
+
aria-live={variant === "error" ? "assertive" : "polite"}
|
|
137
|
+
>
|
|
134
138
|
{#if getIcon()}
|
|
135
139
|
{@const Icon = getIcon()}
|
|
136
140
|
<Icon size={14} class="shrink-0" />
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import ListItemRow, { type ListItemData } from "./ListItem.svelte";
|
|
3
|
+
|
|
4
|
+
export type ListItem = ListItemData;
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
/** Collection of items rendered in the list. */
|
|
8
|
+
items: ListItemData[];
|
|
9
|
+
/** Accessible label for the list container. */
|
|
10
|
+
ariaLabel?: string;
|
|
11
|
+
/** Optional active item id for current state styling. */
|
|
12
|
+
selectedId?: string;
|
|
13
|
+
/** Whether to show the arrow icon for each row. */
|
|
14
|
+
showArrow?: boolean;
|
|
15
|
+
/** Optional custom class names for the list container. */
|
|
16
|
+
className?: string;
|
|
17
|
+
/** Item click callback. */
|
|
18
|
+
onclick?: (item: ListItem, event: MouseEvent) => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let {
|
|
22
|
+
items,
|
|
23
|
+
ariaLabel = "List items",
|
|
24
|
+
selectedId = "",
|
|
25
|
+
showArrow = true,
|
|
26
|
+
className = "",
|
|
27
|
+
onclick,
|
|
28
|
+
...restProps
|
|
29
|
+
}: Props = $props();
|
|
30
|
+
|
|
31
|
+
const listClasses = $derived(
|
|
32
|
+
`space-y-1 overflow-hidden rounded-xl border border-border bg-surface p-1 ${className}`.trim(),
|
|
33
|
+
);
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<ul class={listClasses} role="list" aria-label={ariaLabel} {...restProps}>
|
|
37
|
+
{#each items as item (item.id)}
|
|
38
|
+
<li>
|
|
39
|
+
<ListItemRow
|
|
40
|
+
{item}
|
|
41
|
+
selected={item.id === selectedId}
|
|
42
|
+
{showArrow}
|
|
43
|
+
{onclick}
|
|
44
|
+
/>
|
|
45
|
+
</li>
|
|
46
|
+
{/each}
|
|
47
|
+
</ul>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ListItemData } from "./ListItem.svelte";
|
|
2
|
+
export type ListItem = ListItemData;
|
|
3
|
+
interface Props {
|
|
4
|
+
/** Collection of items rendered in the list. */
|
|
5
|
+
items: ListItemData[];
|
|
6
|
+
/** Accessible label for the list container. */
|
|
7
|
+
ariaLabel?: string;
|
|
8
|
+
/** Optional active item id for current state styling. */
|
|
9
|
+
selectedId?: string;
|
|
10
|
+
/** Whether to show the arrow icon for each row. */
|
|
11
|
+
showArrow?: boolean;
|
|
12
|
+
/** Optional custom class names for the list container. */
|
|
13
|
+
className?: string;
|
|
14
|
+
/** Item click callback. */
|
|
15
|
+
onclick?: (item: ListItem, event: MouseEvent) => void;
|
|
16
|
+
}
|
|
17
|
+
declare const List: import("svelte").Component<Props, {}, "">;
|
|
18
|
+
type List = ReturnType<typeof List>;
|
|
19
|
+
export default List;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { ArrowRight } from "@lucide/svelte";
|
|
3
|
+
import type { Component } from "svelte";
|
|
4
|
+
|
|
5
|
+
export interface ListItemData {
|
|
6
|
+
/** Unique identifier for stable rendering and active-state tracking. */
|
|
7
|
+
id: string;
|
|
8
|
+
/** Primary item label. */
|
|
9
|
+
label: string;
|
|
10
|
+
/** Optional secondary text shown under the label. */
|
|
11
|
+
description?: string;
|
|
12
|
+
/** Optional URL; when set, the item renders as an anchor. */
|
|
13
|
+
href?: string;
|
|
14
|
+
/** Optional leading icon rendered before text. */
|
|
15
|
+
icon?: Component<{ size?: number; class?: string }>;
|
|
16
|
+
/** Link target, only used when href is provided. */
|
|
17
|
+
target?: "_self" | "_blank" | "_parent" | "_top";
|
|
18
|
+
/** Link rel attribute, only used when href is provided. */
|
|
19
|
+
rel?: string;
|
|
20
|
+
/** Disabled state for the item. */
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface Props {
|
|
25
|
+
/** Item data to render. */
|
|
26
|
+
item: ListItemData;
|
|
27
|
+
/** Active-state styling flag. */
|
|
28
|
+
selected?: boolean;
|
|
29
|
+
/** Whether to show the right-arrow icon. */
|
|
30
|
+
showArrow?: boolean;
|
|
31
|
+
/** Item click callback. */
|
|
32
|
+
onclick?: (item: ListItemData, event: MouseEvent) => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let {
|
|
36
|
+
item,
|
|
37
|
+
selected = false,
|
|
38
|
+
showArrow = true,
|
|
39
|
+
onclick,
|
|
40
|
+
...restProps
|
|
41
|
+
}: Props = $props();
|
|
42
|
+
|
|
43
|
+
const itemClasses = $derived.by(() => {
|
|
44
|
+
const baseClasses =
|
|
45
|
+
"group flex w-full items-center gap-3 rounded-xl border border-border px-4 py-3 text-left transition-all duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:ring-inset";
|
|
46
|
+
const interactiveClasses = item.disabled
|
|
47
|
+
? "cursor-not-allowed opacity-50"
|
|
48
|
+
: "cursor-pointer hover:ring-1 hover:ring-focus focus-visible:bg-base-100";
|
|
49
|
+
const selectedClasses = selected
|
|
50
|
+
? "bg-action-primary-subtle border-brand-500"
|
|
51
|
+
: "";
|
|
52
|
+
return `${baseClasses} ${interactiveClasses} ${selectedClasses}`.trim();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const ariaCurrent = $derived(item.href && selected ? "page" : undefined);
|
|
56
|
+
|
|
57
|
+
const rel = $derived.by(() => {
|
|
58
|
+
if (item.rel) {
|
|
59
|
+
return item.rel;
|
|
60
|
+
}
|
|
61
|
+
if (item.target === "_blank") {
|
|
62
|
+
return "noopener noreferrer";
|
|
63
|
+
}
|
|
64
|
+
return undefined;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const handleItemClick = (event: MouseEvent): void => {
|
|
68
|
+
if (item.disabled) {
|
|
69
|
+
event.preventDefault();
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
onclick?.(item, event);
|
|
73
|
+
};
|
|
74
|
+
</script>
|
|
75
|
+
|
|
76
|
+
{#if item.href}
|
|
77
|
+
<a
|
|
78
|
+
href={item.href}
|
|
79
|
+
target={item.target}
|
|
80
|
+
{rel}
|
|
81
|
+
aria-current={ariaCurrent}
|
|
82
|
+
aria-disabled={item.disabled ? "true" : undefined}
|
|
83
|
+
tabindex={item.disabled ? -1 : undefined}
|
|
84
|
+
class={itemClasses}
|
|
85
|
+
onclick={handleItemClick}
|
|
86
|
+
{...restProps}
|
|
87
|
+
>
|
|
88
|
+
{#if item.icon}
|
|
89
|
+
<span
|
|
90
|
+
class="flex size-5 shrink-0 items-center justify-center text-description"
|
|
91
|
+
aria-hidden="true"
|
|
92
|
+
>
|
|
93
|
+
<item.icon size={16} />
|
|
94
|
+
</span>
|
|
95
|
+
{/if}
|
|
96
|
+
<span class="min-w-0 flex-1">
|
|
97
|
+
<span class="block text-sm font-medium text-headline"
|
|
98
|
+
>{item.label}</span
|
|
99
|
+
>
|
|
100
|
+
{#if item.description}
|
|
101
|
+
<span class="mt-0.5 block text-sm text-description"
|
|
102
|
+
>{item.description}</span
|
|
103
|
+
>
|
|
104
|
+
{/if}
|
|
105
|
+
</span>
|
|
106
|
+
{#if showArrow}
|
|
107
|
+
<ArrowRight
|
|
108
|
+
size={16}
|
|
109
|
+
class="shrink-0 text-description transition-transform duration-150 group-hover:translate-x-0.5 group-focus-visible:translate-x-0.5"
|
|
110
|
+
aria-hidden="true"
|
|
111
|
+
/>
|
|
112
|
+
{/if}
|
|
113
|
+
</a>
|
|
114
|
+
{:else}
|
|
115
|
+
<button
|
|
116
|
+
type="button"
|
|
117
|
+
class={itemClasses}
|
|
118
|
+
disabled={item.disabled}
|
|
119
|
+
onclick={handleItemClick}
|
|
120
|
+
{...restProps}
|
|
121
|
+
>
|
|
122
|
+
{#if item.icon}
|
|
123
|
+
<span
|
|
124
|
+
class="flex size-5 shrink-0 items-center justify-center text-description"
|
|
125
|
+
aria-hidden="true"
|
|
126
|
+
>
|
|
127
|
+
<item.icon size={16} />
|
|
128
|
+
</span>
|
|
129
|
+
{/if}
|
|
130
|
+
<span class="min-w-0 flex-1">
|
|
131
|
+
<span class="block text-sm font-medium text-headline"
|
|
132
|
+
>{item.label}</span
|
|
133
|
+
>
|
|
134
|
+
{#if item.description}
|
|
135
|
+
<span class="mt-0.5 block text-sm text-description"
|
|
136
|
+
>{item.description}</span
|
|
137
|
+
>
|
|
138
|
+
{/if}
|
|
139
|
+
</span>
|
|
140
|
+
{#if showArrow}
|
|
141
|
+
<ArrowRight
|
|
142
|
+
size={16}
|
|
143
|
+
class="shrink-0 text-description transition-transform duration-150 group-hover:translate-x-0.5 group-focus-visible:translate-x-0.5"
|
|
144
|
+
aria-hidden="true"
|
|
145
|
+
/>
|
|
146
|
+
{/if}
|
|
147
|
+
</button>
|
|
148
|
+
{/if}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Component } from "svelte";
|
|
2
|
+
export interface ListItemData {
|
|
3
|
+
/** Unique identifier for stable rendering and active-state tracking. */
|
|
4
|
+
id: string;
|
|
5
|
+
/** Primary item label. */
|
|
6
|
+
label: string;
|
|
7
|
+
/** Optional secondary text shown under the label. */
|
|
8
|
+
description?: string;
|
|
9
|
+
/** Optional URL; when set, the item renders as an anchor. */
|
|
10
|
+
href?: string;
|
|
11
|
+
/** Optional leading icon rendered before text. */
|
|
12
|
+
icon?: Component<{
|
|
13
|
+
size?: number;
|
|
14
|
+
class?: string;
|
|
15
|
+
}>;
|
|
16
|
+
/** Link target, only used when href is provided. */
|
|
17
|
+
target?: "_self" | "_blank" | "_parent" | "_top";
|
|
18
|
+
/** Link rel attribute, only used when href is provided. */
|
|
19
|
+
rel?: string;
|
|
20
|
+
/** Disabled state for the item. */
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
}
|
|
23
|
+
interface Props {
|
|
24
|
+
/** Item data to render. */
|
|
25
|
+
item: ListItemData;
|
|
26
|
+
/** Active-state styling flag. */
|
|
27
|
+
selected?: boolean;
|
|
28
|
+
/** Whether to show the right-arrow icon. */
|
|
29
|
+
showArrow?: boolean;
|
|
30
|
+
/** Item click callback. */
|
|
31
|
+
onclick?: (item: ListItemData, event: MouseEvent) => void;
|
|
32
|
+
}
|
|
33
|
+
declare const ListItem: Component<Props, {}, "">;
|
|
34
|
+
type ListItem = ReturnType<typeof ListItem>;
|
|
35
|
+
export default ListItem;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { generateId } from "../../routes/lib/ssr-safe.js";
|
|
3
|
+
|
|
2
4
|
interface Props {
|
|
3
5
|
value?: number;
|
|
4
6
|
max?: number;
|
|
@@ -14,11 +16,7 @@
|
|
|
14
16
|
...restProps
|
|
15
17
|
}: Props = $props();
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
typeof window !== "undefined"
|
|
19
|
-
? `progress-${Math.random().toString(36).substr(2, 9)}`
|
|
20
|
-
: `progress-ssr-${Date.now()}`,
|
|
21
|
-
);
|
|
19
|
+
const progressId = generateId("progress");
|
|
22
20
|
|
|
23
21
|
let percentage = $derived(Math.min(Math.max((value / max) * 100, 0), 100));
|
|
24
22
|
|
|
@@ -41,7 +39,7 @@
|
|
|
41
39
|
|
|
42
40
|
<div
|
|
43
41
|
id={progressId}
|
|
44
|
-
class="w-full bg-input rounded-full overflow-hidden {sizeClasses[size]}"
|
|
42
|
+
class="w-full border border-input-border bg-input rounded-full overflow-hidden {sizeClasses[size]}"
|
|
45
43
|
role="progressbar"
|
|
46
44
|
aria-valuenow={value}
|
|
47
45
|
aria-valuemin="0"
|