zabi-components 4.0.2 → 5.0.1
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 +2 -2
- package/dist/SSRSafe.svelte +47 -0
- package/dist/SSRSafe.svelte.d.ts +11 -0
- package/dist/atoms/Badge.svelte +76 -0
- package/dist/atoms/Badge.svelte.d.ts +9 -0
- package/dist/atoms/Button.svelte +72 -0
- package/dist/atoms/Button.svelte.d.ts +13 -0
- package/dist/atoms/Card.svelte +59 -0
- package/dist/atoms/Card.svelte.d.ts +12 -0
- package/dist/atoms/Checkbox.svelte +60 -0
- package/dist/atoms/Checkbox.svelte.d.ts +10 -0
- package/dist/atoms/CodeBlock.svelte +125 -0
- package/dist/atoms/CodeBlock.svelte.d.ts +8 -0
- package/dist/atoms/ColorPicker.svelte +171 -0
- package/dist/atoms/ColorPicker.svelte.d.ts +9 -0
- package/dist/atoms/FeatureCard.svelte +74 -0
- package/dist/atoms/FeatureCard.svelte.d.ts +17 -0
- package/dist/atoms/Heading.svelte +5 -0
- package/dist/atoms/Heading.svelte.d.ts +7 -0
- package/dist/atoms/Input.svelte +98 -0
- package/dist/atoms/Input.svelte.d.ts +14 -0
- package/dist/atoms/OptimizedImage.svelte +32 -0
- package/dist/atoms/OptimizedImage.svelte.d.ts +13 -0
- package/dist/atoms/Progress.svelte +61 -0
- package/dist/atoms/Progress.svelte.d.ts +9 -0
- package/dist/atoms/Select.svelte +85 -0
- package/dist/atoms/Select.svelte.d.ts +16 -0
- package/dist/atoms/Skeleton.svelte +20 -0
- package/dist/atoms/Skeleton.svelte.d.ts +8 -0
- package/dist/atoms/Textarea.svelte +116 -0
- package/dist/atoms/Textarea.svelte.d.ts +14 -0
- package/dist/atoms/ThemeToggle.svelte +89 -0
- package/dist/atoms/ThemeToggle.svelte.d.ts +7 -0
- package/dist/atoms/Toast.svelte +58 -0
- package/dist/atoms/Toast.svelte.d.ts +9 -0
- package/dist/atoms/Toggle.svelte +94 -0
- package/dist/atoms/Toggle.svelte.d.ts +12 -0
- package/dist/atoms/Tooltip.svelte +188 -0
- package/dist/atoms/Tooltip.svelte.d.ts +12 -0
- package/dist/atoms/index.d.ts +4 -15
- package/dist/atoms/index.js +10 -23
- package/dist/components/atoms/index.d.ts +10 -0
- package/dist/components/atoms/index.d.ts.map +1 -0
- package/dist/components/index.d.ts +31 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/molecules/index.d.ts +1 -0
- package/dist/components/molecules/index.d.ts.map +1 -0
- package/dist/components/organisms/index.d.ts +1 -0
- package/dist/components/organisms/index.d.ts.map +1 -0
- package/dist/index.d.ts +30 -15
- package/dist/index.js +74 -0
- package/dist/molecules/Alert.svelte +114 -0
- package/dist/molecules/Alert.svelte.d.ts +14 -0
- package/dist/molecules/ComponentDemo.svelte +96 -0
- package/dist/molecules/ComponentDemo.svelte.d.ts +13 -0
- package/dist/molecules/ContactForm.svelte +98 -0
- package/dist/molecules/ContactForm.svelte.d.ts +10 -0
- package/dist/molecules/Dropdown.svelte +128 -0
- package/dist/molecules/Dropdown.svelte.d.ts +11 -0
- package/dist/molecules/Form.svelte +37 -0
- package/dist/molecules/Form.svelte.d.ts +12 -0
- package/dist/molecules/ImageUpload.svelte +112 -0
- package/dist/molecules/ImageUpload.svelte.d.ts +14 -0
- package/dist/molecules/Modal.svelte +85 -0
- package/dist/molecules/Modal.svelte.d.ts +13 -0
- package/dist/molecules/SlideUp.svelte +73 -0
- package/dist/molecules/SlideUp.svelte.d.ts +12 -0
- package/dist/molecules/Tabs.svelte +68 -0
- package/dist/molecules/Tabs.svelte.d.ts +17 -0
- package/dist/molecules/index.d.ts +0 -11
- package/dist/molecules/index.js +2 -13
- package/dist/organisms/Navbar.svelte +99 -0
- package/dist/organisms/Navbar.svelte.d.ts +14 -0
- package/dist/organisms/Navigation.svelte +48 -0
- package/dist/organisms/Navigation.svelte.d.ts +15 -0
- package/dist/organisms/index.d.ts +0 -4
- package/dist/organisms/index.js +2 -5
- package/dist/routes/lib/ssr-safe.ts +57 -0
- package/dist/routes/lib/variant-utils.ts +98 -0
- package/dist/zabi-components.css +399 -1
- package/package.json +41 -17
- package/dist/CodeBlock-pT5-FgZd.js +0 -431
- package/dist/ContactForm-CHI_jeFY.js +0 -580
- package/dist/FeatureCard-iBFGbgVE.js +0 -656
- package/dist/Navigation-BOT-UHg0.js +0 -104
- package/dist/ThemeToggle-BlNPkAQy.js +0 -48
- package/dist/index/index.js +0 -63
- package/dist/molecules/KeyValueForm.types.d.ts +0 -11
- package/dist/ssr-safe-bsWGK4V4.js +0 -2232
- package/dist/types/index.d.ts +0 -4
- package/dist/types/index.js +0 -1
- package/dist/vite.svg +0 -1
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// SSR-safe ID generation
|
|
3
|
+
function generateId(prefix: string = "id"): string {
|
|
4
|
+
if (typeof window !== "undefined") {
|
|
5
|
+
return `${prefix}-${Math.random().toString(36).substr(2, 9)}`;
|
|
6
|
+
} else {
|
|
7
|
+
return `${prefix}-ssr-${Date.now()}`;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
value?: string;
|
|
13
|
+
label?: string;
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
onclick?: (event: Event) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let {
|
|
19
|
+
value = "",
|
|
20
|
+
label = "",
|
|
21
|
+
disabled = false,
|
|
22
|
+
onclick,
|
|
23
|
+
...restProps
|
|
24
|
+
}: Props = $props();
|
|
25
|
+
|
|
26
|
+
const colors = [
|
|
27
|
+
{ label: "Red", value: "#ef4444" },
|
|
28
|
+
{ label: "Blue", value: "#3b82f6" },
|
|
29
|
+
{ label: "Green", value: "#10b981" },
|
|
30
|
+
{ label: "Yellow", value: "#f59e0b" },
|
|
31
|
+
{ label: "Purple", value: "#8b5cf6" },
|
|
32
|
+
{ label: "Pink", value: "#ec4899" },
|
|
33
|
+
{ label: "Gray", value: "#6b7280" },
|
|
34
|
+
{ label: "Black", value: "#000000" },
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
// Generate unique IDs - SSR safe (call directly, not in onMount)
|
|
38
|
+
const groupId = generateId("color-picker");
|
|
39
|
+
const inputId = generateId("color-input");
|
|
40
|
+
|
|
41
|
+
// Hex color validation
|
|
42
|
+
function isValidHex(hex: string): boolean {
|
|
43
|
+
const hexPattern = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
|
|
44
|
+
return hexPattern.test(hex);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Handle hex input changes
|
|
48
|
+
function handleHexInput(event: Event) {
|
|
49
|
+
const target = event.target as HTMLInputElement;
|
|
50
|
+
const hexValue = target.value;
|
|
51
|
+
|
|
52
|
+
if (hexValue === "" || isValidHex(hexValue)) {
|
|
53
|
+
value = hexValue;
|
|
54
|
+
if (onclick) onclick(event);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Handle hex input blur - validate and format
|
|
59
|
+
function handleHexBlur(event: Event) {
|
|
60
|
+
const target = event.target as HTMLInputElement;
|
|
61
|
+
let hexValue = target.value.trim();
|
|
62
|
+
|
|
63
|
+
// Add # if missing
|
|
64
|
+
if (hexValue && !hexValue.startsWith("#")) {
|
|
65
|
+
hexValue = "#" + hexValue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Validate and update
|
|
69
|
+
if (hexValue === "" || isValidHex(hexValue)) {
|
|
70
|
+
value = hexValue;
|
|
71
|
+
target.value = hexValue;
|
|
72
|
+
} else {
|
|
73
|
+
// Reset to current value if invalid
|
|
74
|
+
target.value = value;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Color button classes using full class names
|
|
79
|
+
const colorButtonClasses = $derived(() => {
|
|
80
|
+
return (colorValue: string) => {
|
|
81
|
+
const baseClasses =
|
|
82
|
+
"w-10 h-10 sm:w-12 sm:h-12 rounded-lg border-2 transition-all";
|
|
83
|
+
const stateClasses =
|
|
84
|
+
value === colorValue
|
|
85
|
+
? "border-blue-500 ring-2 ring-blue-200"
|
|
86
|
+
: "border-gray-300 hover:border-gray-400";
|
|
87
|
+
|
|
88
|
+
return `${baseClasses} ${stateClasses}`.trim();
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Input classes
|
|
93
|
+
const inputClasses = $derived(() => {
|
|
94
|
+
const baseClasses =
|
|
95
|
+
"w-full px-3 py-2 border rounded-lg text-sm transition-colors focus:outline-none focus:ring-2 focus:ring-blue-200";
|
|
96
|
+
const stateClasses =
|
|
97
|
+
isValidHex(value) || value === ""
|
|
98
|
+
? "border-gray-300 focus:border-blue-500"
|
|
99
|
+
: "border-red-300 focus:border-red-500 focus:ring-red-200";
|
|
100
|
+
|
|
101
|
+
return `${baseClasses} ${stateClasses}`.trim();
|
|
102
|
+
});
|
|
103
|
+
</script>
|
|
104
|
+
|
|
105
|
+
<div class="space-y-3 sm:space-y-4">
|
|
106
|
+
{#if label}
|
|
107
|
+
<label for={groupId} class="block text-sm font-medium text-label"
|
|
108
|
+
>{label}</label
|
|
109
|
+
>
|
|
110
|
+
{/if}
|
|
111
|
+
|
|
112
|
+
<!-- Predefined Colors -->
|
|
113
|
+
<div
|
|
114
|
+
id={groupId}
|
|
115
|
+
class="grid grid-cols-4 sm:grid-cols-4 md:grid-cols-4 lg:grid-cols-4 gap-2 sm:gap-3"
|
|
116
|
+
role="radiogroup"
|
|
117
|
+
{...restProps}
|
|
118
|
+
>
|
|
119
|
+
{#each colors as color}
|
|
120
|
+
<button
|
|
121
|
+
type="button"
|
|
122
|
+
class={colorButtonClasses()(color.value)}
|
|
123
|
+
style="background-color: {color.value};"
|
|
124
|
+
onclick={(e) => {
|
|
125
|
+
value = color.value;
|
|
126
|
+
if (onclick) onclick(e);
|
|
127
|
+
}}
|
|
128
|
+
{disabled}
|
|
129
|
+
role="radio"
|
|
130
|
+
aria-checked={value === color.value}
|
|
131
|
+
aria-label={color.label}
|
|
132
|
+
>
|
|
133
|
+
{#if value === color.value}
|
|
134
|
+
<span class="text-white text-lg">✓</span>
|
|
135
|
+
{/if}
|
|
136
|
+
</button>
|
|
137
|
+
{/each}
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
<!-- Custom Hex Input -->
|
|
141
|
+
<div class="space-y-1">
|
|
142
|
+
<label for={inputId} class="block text-xs font-medium text-label">
|
|
143
|
+
Or enter custom color
|
|
144
|
+
</label>
|
|
145
|
+
<div class="flex items-center space-x-2">
|
|
146
|
+
<input
|
|
147
|
+
id={inputId}
|
|
148
|
+
type="text"
|
|
149
|
+
placeholder="#000000"
|
|
150
|
+
{value}
|
|
151
|
+
oninput={handleHexInput}
|
|
152
|
+
onblur={handleHexBlur}
|
|
153
|
+
{disabled}
|
|
154
|
+
class={inputClasses()}
|
|
155
|
+
aria-label="Custom hex color input"
|
|
156
|
+
/>
|
|
157
|
+
{#if value && isValidHex(value)}
|
|
158
|
+
<div
|
|
159
|
+
class="w-6 h-6 sm:w-8 sm:h-8 rounded border-2 border-gray-300 shrink-0"
|
|
160
|
+
style="background-color: {value};"
|
|
161
|
+
aria-label="Color preview"
|
|
162
|
+
></div>
|
|
163
|
+
{/if}
|
|
164
|
+
</div>
|
|
165
|
+
{#if value && !isValidHex(value)}
|
|
166
|
+
<p class="text-xs text-red-500">
|
|
167
|
+
Please enter a valid hex color (e.g., #ff0000)
|
|
168
|
+
</p>
|
|
169
|
+
{/if}
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
value?: string;
|
|
3
|
+
label?: string;
|
|
4
|
+
disabled?: boolean;
|
|
5
|
+
onclick?: (event: Event) => void;
|
|
6
|
+
}
|
|
7
|
+
declare const ColorPicker: import("svelte").Component<Props, {}, "">;
|
|
8
|
+
type ColorPicker = ReturnType<typeof ColorPicker>;
|
|
9
|
+
export default ColorPicker;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount } from "svelte";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* FeatureCard component - SSR-safe implementation
|
|
6
|
+
* @component
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
/** Icon to display */
|
|
11
|
+
icon?: string;
|
|
12
|
+
/** Title of the feature */
|
|
13
|
+
title: string;
|
|
14
|
+
/** Description of the feature */
|
|
15
|
+
description: string;
|
|
16
|
+
/** Additional CSS classes */
|
|
17
|
+
className?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let {
|
|
21
|
+
icon = "✨",
|
|
22
|
+
title,
|
|
23
|
+
description,
|
|
24
|
+
className = "",
|
|
25
|
+
...restProps
|
|
26
|
+
}: Props = $props();
|
|
27
|
+
|
|
28
|
+
let mounted = $state(false);
|
|
29
|
+
|
|
30
|
+
onMount(() => {
|
|
31
|
+
mounted = true;
|
|
32
|
+
});
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
{#if mounted}
|
|
36
|
+
<div
|
|
37
|
+
class="p-6 rounded-lg bg-surface border border-border hover:border-primary/20 hover:shadow-sm transition-colors duration-200 {className}"
|
|
38
|
+
{...restProps}
|
|
39
|
+
>
|
|
40
|
+
<div class="flex items-start gap-4">
|
|
41
|
+
<div class="shrink-0 text-2xl" aria-hidden="true">
|
|
42
|
+
{icon}
|
|
43
|
+
</div>
|
|
44
|
+
<div class="flex-1 min-w-0">
|
|
45
|
+
<h3 class="text-lg font-semibold text-headline mb-2">
|
|
46
|
+
{title}
|
|
47
|
+
</h3>
|
|
48
|
+
<p class="text-description leading-relaxed">
|
|
49
|
+
{description}
|
|
50
|
+
</p>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
{:else}
|
|
55
|
+
<!-- SSR fallback -->
|
|
56
|
+
<div
|
|
57
|
+
class="p-6 rounded-lg bg-gray-100 border border-gray-300 hover:border-gray-400 hover:shadow-sm transition-colors duration-200 {className}"
|
|
58
|
+
{...restProps}
|
|
59
|
+
>
|
|
60
|
+
<div class="flex items-start gap-4">
|
|
61
|
+
<div class="shrink-0 text-2xl" aria-hidden="true">
|
|
62
|
+
{icon}
|
|
63
|
+
</div>
|
|
64
|
+
<div class="flex-1 min-w-0">
|
|
65
|
+
<h3 class="text-lg font-semibold text-headline mb-2">
|
|
66
|
+
{title}
|
|
67
|
+
</h3>
|
|
68
|
+
<p class="text-description leading-relaxed">
|
|
69
|
+
{description}
|
|
70
|
+
</p>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
{/if}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FeatureCard component - SSR-safe implementation
|
|
3
|
+
* @component
|
|
4
|
+
*/
|
|
5
|
+
interface Props {
|
|
6
|
+
/** Icon to display */
|
|
7
|
+
icon?: string;
|
|
8
|
+
/** Title of the feature */
|
|
9
|
+
title: string;
|
|
10
|
+
/** Description of the feature */
|
|
11
|
+
description: string;
|
|
12
|
+
/** Additional CSS classes */
|
|
13
|
+
className?: string;
|
|
14
|
+
}
|
|
15
|
+
declare const FeatureCard: import("svelte").Component<Props, {}, "">;
|
|
16
|
+
type FeatureCard = ReturnType<typeof FeatureCard>;
|
|
17
|
+
export default FeatureCard;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// SSR-safe ID generation
|
|
3
|
+
function generateId(prefix: string = "id"): string {
|
|
4
|
+
if (typeof window !== "undefined") {
|
|
5
|
+
return `${prefix}-${Math.random().toString(36).substr(2, 9)}`;
|
|
6
|
+
} else {
|
|
7
|
+
return `${prefix}-ssr-${Date.now()}`;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
value?: string;
|
|
13
|
+
type?: string;
|
|
14
|
+
name?: string;
|
|
15
|
+
label?: string;
|
|
16
|
+
placeholder?: string;
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
size?: "sm" | "md" | "lg";
|
|
19
|
+
variant?: "default" | "success" | "warning" | "error";
|
|
20
|
+
oninput?: (event: Event) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let {
|
|
24
|
+
value = "",
|
|
25
|
+
type = "text",
|
|
26
|
+
name = "",
|
|
27
|
+
label = "",
|
|
28
|
+
placeholder = "",
|
|
29
|
+
disabled = false,
|
|
30
|
+
size = "md",
|
|
31
|
+
variant = "default",
|
|
32
|
+
oninput,
|
|
33
|
+
...restProps
|
|
34
|
+
}: Props = $props();
|
|
35
|
+
|
|
36
|
+
// Generate unique ID - SSR safe (call directly, not in $state)
|
|
37
|
+
const inputId = generateId("input");
|
|
38
|
+
|
|
39
|
+
// Size classes using full class names
|
|
40
|
+
const sizeClass = $derived(() => {
|
|
41
|
+
return size === "sm"
|
|
42
|
+
? "px-3 py-1.5 text-sm"
|
|
43
|
+
: size === "lg"
|
|
44
|
+
? "px-5 py-3 text-base"
|
|
45
|
+
: "px-4 py-2 text-sm"; // default md
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Variant classes using full class names
|
|
49
|
+
const variantClass = $derived(() => {
|
|
50
|
+
return variant === "success"
|
|
51
|
+
? "border-green-300 focus:border-green-500 focus:ring-green-500"
|
|
52
|
+
: variant === "warning"
|
|
53
|
+
? "border-yellow-300 focus:border-yellow-500 focus:ring-yellow-500"
|
|
54
|
+
: variant === "error"
|
|
55
|
+
? "border-red-300 focus:border-red-500 focus:ring-red-500"
|
|
56
|
+
: "border-gray-300 focus:border-blue-500 focus:ring-blue-500"; // default
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Input classes using Badge pattern
|
|
60
|
+
const inputClasses = $derived(() => {
|
|
61
|
+
const baseClasses =
|
|
62
|
+
"w-full rounded-md transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-surface disabled:opacity-50 disabled:cursor-not-allowed disabled:bg-surface-disabled";
|
|
63
|
+
|
|
64
|
+
return `${baseClasses} ${sizeClass()} ${variantClass()}`.trim();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Label classes using semantic text colors
|
|
68
|
+
const labelClasses = $derived(
|
|
69
|
+
() => "block text-sm font-medium text-label mb-1",
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
function handleInput(event: Event) {
|
|
73
|
+
const target = event.target as HTMLInputElement;
|
|
74
|
+
value = target.value;
|
|
75
|
+
|
|
76
|
+
// Call the parent's oninput handler if provided
|
|
77
|
+
if (oninput) {
|
|
78
|
+
oninput(event);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
</script>
|
|
82
|
+
|
|
83
|
+
<div>
|
|
84
|
+
{#if label}
|
|
85
|
+
<label for={inputId} class={labelClasses()}>{label}</label>
|
|
86
|
+
{/if}
|
|
87
|
+
<input
|
|
88
|
+
id={inputId}
|
|
89
|
+
{type}
|
|
90
|
+
{name}
|
|
91
|
+
{value}
|
|
92
|
+
{placeholder}
|
|
93
|
+
{disabled}
|
|
94
|
+
class={inputClasses()}
|
|
95
|
+
oninput={handleInput}
|
|
96
|
+
{...restProps}
|
|
97
|
+
/>
|
|
98
|
+
</div>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
value?: string;
|
|
3
|
+
type?: string;
|
|
4
|
+
name?: string;
|
|
5
|
+
label?: string;
|
|
6
|
+
placeholder?: string;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
size?: "sm" | "md" | "lg";
|
|
9
|
+
variant?: "default" | "success" | "warning" | "error";
|
|
10
|
+
oninput?: (event: Event) => void;
|
|
11
|
+
}
|
|
12
|
+
declare const Input: import("svelte").Component<Props, {}, "">;
|
|
13
|
+
type Input = ReturnType<typeof Input>;
|
|
14
|
+
export default Input;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
src: string;
|
|
4
|
+
alt?: string;
|
|
5
|
+
width?: number | string;
|
|
6
|
+
height?: number | string;
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let {
|
|
11
|
+
src,
|
|
12
|
+
alt = "",
|
|
13
|
+
width = "100%",
|
|
14
|
+
height = "auto",
|
|
15
|
+
className = "",
|
|
16
|
+
children,
|
|
17
|
+
...restProps
|
|
18
|
+
} = $props<Props & { children?: any }>();
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<img
|
|
22
|
+
{src}
|
|
23
|
+
{alt}
|
|
24
|
+
class="w-full h-auto object-cover {className}"
|
|
25
|
+
style="width: {typeof width === 'number'
|
|
26
|
+
? width + 'px'
|
|
27
|
+
: width}; height: {typeof height === 'number'
|
|
28
|
+
? height + 'px'
|
|
29
|
+
: height};"
|
|
30
|
+
loading="lazy"
|
|
31
|
+
{...restProps}
|
|
32
|
+
/>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
src: string;
|
|
3
|
+
alt?: string;
|
|
4
|
+
width?: number | string;
|
|
5
|
+
height?: number | string;
|
|
6
|
+
className?: string;
|
|
7
|
+
}
|
|
8
|
+
type $$ComponentProps = Props & {
|
|
9
|
+
children?: any;
|
|
10
|
+
};
|
|
11
|
+
declare const OptimizedImage: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
12
|
+
type OptimizedImage = ReturnType<typeof OptimizedImage>;
|
|
13
|
+
export default OptimizedImage;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
value?: number;
|
|
4
|
+
max?: number;
|
|
5
|
+
size?: "sm" | "md" | "lg";
|
|
6
|
+
label?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let {
|
|
10
|
+
value = 0,
|
|
11
|
+
max = 100,
|
|
12
|
+
size = "md",
|
|
13
|
+
label = "",
|
|
14
|
+
...restProps
|
|
15
|
+
}: Props = $props();
|
|
16
|
+
|
|
17
|
+
// Generate unique ID - SSR safe
|
|
18
|
+
let progressId = $state(
|
|
19
|
+
typeof window !== "undefined"
|
|
20
|
+
? `progress-${Math.random().toString(36).substr(2, 9)}`
|
|
21
|
+
: `progress-ssr-${Date.now()}`,
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
// Calculate percentage
|
|
25
|
+
let percentage = $derived(Math.min(Math.max((value / max) * 100, 0), 100));
|
|
26
|
+
|
|
27
|
+
// Simple size classes
|
|
28
|
+
let sizeClasses = $derived({
|
|
29
|
+
sm: "h-1",
|
|
30
|
+
md: "h-2",
|
|
31
|
+
lg: "h-3",
|
|
32
|
+
});
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<div>
|
|
36
|
+
{#if label}
|
|
37
|
+
<div class="flex justify-between items-center mb-2">
|
|
38
|
+
<label for={progressId} class="text-sm font-medium text-label"
|
|
39
|
+
>{label}</label
|
|
40
|
+
>
|
|
41
|
+
<span class="text-sm text-caption">{Math.round(percentage)}%</span>
|
|
42
|
+
</div>
|
|
43
|
+
{/if}
|
|
44
|
+
|
|
45
|
+
<div
|
|
46
|
+
id={progressId}
|
|
47
|
+
class="w-full bg-gray-200 rounded-full overflow-hidden {sizeClasses[
|
|
48
|
+
size
|
|
49
|
+
]}"
|
|
50
|
+
role="progressbar"
|
|
51
|
+
aria-valuenow={value}
|
|
52
|
+
aria-valuemin="0"
|
|
53
|
+
aria-valuemax={max}
|
|
54
|
+
{...restProps}
|
|
55
|
+
>
|
|
56
|
+
<div
|
|
57
|
+
class="h-full bg-blue-600 rounded-full"
|
|
58
|
+
style="width: {percentage}%"
|
|
59
|
+
></div>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// SSR-safe ID generation
|
|
3
|
+
function generateId(prefix: string = "id"): string {
|
|
4
|
+
if (typeof window !== "undefined") {
|
|
5
|
+
return `${prefix}-${Math.random().toString(36).substr(2, 9)}`;
|
|
6
|
+
} else {
|
|
7
|
+
return `${prefix}-ssr-${Date.now()}`;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
value?: string | number | undefined;
|
|
13
|
+
options?: Array<{
|
|
14
|
+
value: string | number;
|
|
15
|
+
label: string;
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
}>;
|
|
18
|
+
placeholder?: string;
|
|
19
|
+
label?: string;
|
|
20
|
+
disabled?: boolean;
|
|
21
|
+
size?: "sm" | "md" | "lg";
|
|
22
|
+
onchange?: (event: Event) => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let {
|
|
26
|
+
value = undefined,
|
|
27
|
+
options = [],
|
|
28
|
+
placeholder = "Select an option",
|
|
29
|
+
label = "",
|
|
30
|
+
disabled = false,
|
|
31
|
+
size = "md",
|
|
32
|
+
...restProps
|
|
33
|
+
}: Props = $props();
|
|
34
|
+
|
|
35
|
+
// Generate unique ID - SSR safe (call directly, not in $state)
|
|
36
|
+
const selectId = generateId("select");
|
|
37
|
+
|
|
38
|
+
// Size classes using full class names
|
|
39
|
+
const sizeClass = $derived(() => {
|
|
40
|
+
return size === "sm"
|
|
41
|
+
? "px-3 py-1.5 text-sm"
|
|
42
|
+
: size === "lg"
|
|
43
|
+
? "px-5 py-3 text-base"
|
|
44
|
+
: "px-4 py-2 text-sm"; // default md
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Select classes using Badge pattern
|
|
48
|
+
const selectClasses = $derived(() => {
|
|
49
|
+
const baseClasses =
|
|
50
|
+
"w-full border border-gray-300 rounded-md bg-white focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:opacity-50 disabled:cursor-not-allowed";
|
|
51
|
+
|
|
52
|
+
return `${baseClasses} ${sizeClass()}`.trim();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
function handleChange(event: Event) {
|
|
56
|
+
const target = event.target as HTMLSelectElement;
|
|
57
|
+
value = target.value;
|
|
58
|
+
}
|
|
59
|
+
</script>
|
|
60
|
+
|
|
61
|
+
<div>
|
|
62
|
+
{#if label}
|
|
63
|
+
<label for={selectId} class="block text-sm font-medium text-label mb-1"
|
|
64
|
+
>{label}</label
|
|
65
|
+
>
|
|
66
|
+
{/if}
|
|
67
|
+
|
|
68
|
+
<select
|
|
69
|
+
id={selectId}
|
|
70
|
+
{value}
|
|
71
|
+
{disabled}
|
|
72
|
+
class={selectClasses()}
|
|
73
|
+
onchange={handleChange}
|
|
74
|
+
{...restProps}
|
|
75
|
+
>
|
|
76
|
+
{#if placeholder && !value}
|
|
77
|
+
<option value="" disabled>{placeholder}</option>
|
|
78
|
+
{/if}
|
|
79
|
+
{#each options as option (option.value)}
|
|
80
|
+
<option value={option.value} disabled={option.disabled}>
|
|
81
|
+
{option.label}
|
|
82
|
+
</option>
|
|
83
|
+
{/each}
|
|
84
|
+
</select>
|
|
85
|
+
</div>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
value?: string | number | undefined;
|
|
3
|
+
options?: Array<{
|
|
4
|
+
value: string | number;
|
|
5
|
+
label: string;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
}>;
|
|
8
|
+
placeholder?: string;
|
|
9
|
+
label?: string;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
size?: "sm" | "md" | "lg";
|
|
12
|
+
onchange?: (event: Event) => void;
|
|
13
|
+
}
|
|
14
|
+
declare const Select: import("svelte").Component<Props, {}, "">;
|
|
15
|
+
type Select = ReturnType<typeof Select>;
|
|
16
|
+
export default Select;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
width?: string;
|
|
4
|
+
height?: string;
|
|
5
|
+
className?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
let {
|
|
9
|
+
width = "100%",
|
|
10
|
+
height = "1rem",
|
|
11
|
+
className = "",
|
|
12
|
+
...restProps
|
|
13
|
+
}: Props = $props();
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<div
|
|
17
|
+
class="animate-pulse bg-gray-200 rounded {className}"
|
|
18
|
+
style="width: {width}; height: {height};"
|
|
19
|
+
{...restProps}
|
|
20
|
+
></div>
|