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.
Files changed (92) hide show
  1. package/README.md +2 -2
  2. package/dist/SSRSafe.svelte +47 -0
  3. package/dist/SSRSafe.svelte.d.ts +11 -0
  4. package/dist/atoms/Badge.svelte +76 -0
  5. package/dist/atoms/Badge.svelte.d.ts +9 -0
  6. package/dist/atoms/Button.svelte +72 -0
  7. package/dist/atoms/Button.svelte.d.ts +13 -0
  8. package/dist/atoms/Card.svelte +59 -0
  9. package/dist/atoms/Card.svelte.d.ts +12 -0
  10. package/dist/atoms/Checkbox.svelte +60 -0
  11. package/dist/atoms/Checkbox.svelte.d.ts +10 -0
  12. package/dist/atoms/CodeBlock.svelte +125 -0
  13. package/dist/atoms/CodeBlock.svelte.d.ts +8 -0
  14. package/dist/atoms/ColorPicker.svelte +171 -0
  15. package/dist/atoms/ColorPicker.svelte.d.ts +9 -0
  16. package/dist/atoms/FeatureCard.svelte +74 -0
  17. package/dist/atoms/FeatureCard.svelte.d.ts +17 -0
  18. package/dist/atoms/Heading.svelte +5 -0
  19. package/dist/atoms/Heading.svelte.d.ts +7 -0
  20. package/dist/atoms/Input.svelte +98 -0
  21. package/dist/atoms/Input.svelte.d.ts +14 -0
  22. package/dist/atoms/OptimizedImage.svelte +32 -0
  23. package/dist/atoms/OptimizedImage.svelte.d.ts +13 -0
  24. package/dist/atoms/Progress.svelte +61 -0
  25. package/dist/atoms/Progress.svelte.d.ts +9 -0
  26. package/dist/atoms/Select.svelte +85 -0
  27. package/dist/atoms/Select.svelte.d.ts +16 -0
  28. package/dist/atoms/Skeleton.svelte +20 -0
  29. package/dist/atoms/Skeleton.svelte.d.ts +8 -0
  30. package/dist/atoms/Textarea.svelte +116 -0
  31. package/dist/atoms/Textarea.svelte.d.ts +14 -0
  32. package/dist/atoms/ThemeToggle.svelte +89 -0
  33. package/dist/atoms/ThemeToggle.svelte.d.ts +7 -0
  34. package/dist/atoms/Toast.svelte +58 -0
  35. package/dist/atoms/Toast.svelte.d.ts +9 -0
  36. package/dist/atoms/Toggle.svelte +94 -0
  37. package/dist/atoms/Toggle.svelte.d.ts +12 -0
  38. package/dist/atoms/Tooltip.svelte +188 -0
  39. package/dist/atoms/Tooltip.svelte.d.ts +12 -0
  40. package/dist/atoms/index.d.ts +4 -15
  41. package/dist/atoms/index.js +10 -23
  42. package/dist/components/atoms/index.d.ts +10 -0
  43. package/dist/components/atoms/index.d.ts.map +1 -0
  44. package/dist/components/index.d.ts +31 -0
  45. package/dist/components/index.d.ts.map +1 -0
  46. package/dist/components/molecules/index.d.ts +1 -0
  47. package/dist/components/molecules/index.d.ts.map +1 -0
  48. package/dist/components/organisms/index.d.ts +1 -0
  49. package/dist/components/organisms/index.d.ts.map +1 -0
  50. package/dist/index.d.ts +30 -15
  51. package/dist/index.js +74 -0
  52. package/dist/molecules/Alert.svelte +114 -0
  53. package/dist/molecules/Alert.svelte.d.ts +14 -0
  54. package/dist/molecules/ComponentDemo.svelte +96 -0
  55. package/dist/molecules/ComponentDemo.svelte.d.ts +13 -0
  56. package/dist/molecules/ContactForm.svelte +98 -0
  57. package/dist/molecules/ContactForm.svelte.d.ts +10 -0
  58. package/dist/molecules/Dropdown.svelte +128 -0
  59. package/dist/molecules/Dropdown.svelte.d.ts +11 -0
  60. package/dist/molecules/Form.svelte +37 -0
  61. package/dist/molecules/Form.svelte.d.ts +12 -0
  62. package/dist/molecules/ImageUpload.svelte +112 -0
  63. package/dist/molecules/ImageUpload.svelte.d.ts +14 -0
  64. package/dist/molecules/Modal.svelte +85 -0
  65. package/dist/molecules/Modal.svelte.d.ts +13 -0
  66. package/dist/molecules/SlideUp.svelte +73 -0
  67. package/dist/molecules/SlideUp.svelte.d.ts +12 -0
  68. package/dist/molecules/Tabs.svelte +68 -0
  69. package/dist/molecules/Tabs.svelte.d.ts +17 -0
  70. package/dist/molecules/index.d.ts +0 -11
  71. package/dist/molecules/index.js +2 -13
  72. package/dist/organisms/Navbar.svelte +99 -0
  73. package/dist/organisms/Navbar.svelte.d.ts +14 -0
  74. package/dist/organisms/Navigation.svelte +48 -0
  75. package/dist/organisms/Navigation.svelte.d.ts +15 -0
  76. package/dist/organisms/index.d.ts +0 -4
  77. package/dist/organisms/index.js +2 -5
  78. package/dist/routes/lib/ssr-safe.ts +57 -0
  79. package/dist/routes/lib/variant-utils.ts +98 -0
  80. package/dist/zabi-components.css +399 -1
  81. package/package.json +41 -17
  82. package/dist/CodeBlock-pT5-FgZd.js +0 -431
  83. package/dist/ContactForm-CHI_jeFY.js +0 -580
  84. package/dist/FeatureCard-iBFGbgVE.js +0 -656
  85. package/dist/Navigation-BOT-UHg0.js +0 -104
  86. package/dist/ThemeToggle-BlNPkAQy.js +0 -48
  87. package/dist/index/index.js +0 -63
  88. package/dist/molecules/KeyValueForm.types.d.ts +0 -11
  89. package/dist/ssr-safe-bsWGK4V4.js +0 -2232
  90. package/dist/types/index.d.ts +0 -4
  91. package/dist/types/index.js +0 -1
  92. 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,5 @@
1
+ <script lang="ts">
2
+ let { level = 1, text }: { level?: number; text: string } = $props();
3
+ </script>
4
+
5
+ <div class="text-headline" role="heading" aria-level={level}>{text}</div>
@@ -0,0 +1,7 @@
1
+ type $$ComponentProps = {
2
+ level?: number;
3
+ text: string;
4
+ };
5
+ declare const Heading: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type Heading = ReturnType<typeof Heading>;
7
+ export default Heading;
@@ -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,9 @@
1
+ interface Props {
2
+ value?: number;
3
+ max?: number;
4
+ size?: "sm" | "md" | "lg";
5
+ label?: string;
6
+ }
7
+ declare const Progress: import("svelte").Component<Props, {}, "">;
8
+ type Progress = ReturnType<typeof Progress>;
9
+ export default Progress;
@@ -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>
@@ -0,0 +1,8 @@
1
+ interface Props {
2
+ width?: string;
3
+ height?: string;
4
+ className?: string;
5
+ }
6
+ declare const Skeleton: import("svelte").Component<Props, {}, "">;
7
+ type Skeleton = ReturnType<typeof Skeleton>;
8
+ export default Skeleton;