zabi-components 5.0.17 → 5.0.18
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 +124 -23
- package/dist/SSRSafe.svelte +0 -3
- package/dist/atoms/Badge.svelte +19 -34
- package/dist/atoms/Badge.svelte.d.ts +6 -5
- package/dist/atoms/Button.svelte +10 -9
- package/dist/atoms/Button.svelte.d.ts +3 -2
- package/dist/atoms/Card.svelte +74 -23
- package/dist/atoms/Card.svelte.d.ts +3 -1
- package/dist/atoms/CardContent.svelte +24 -0
- package/dist/atoms/CardContent.svelte.d.ts +8 -0
- package/dist/atoms/CardDescription.svelte +24 -0
- package/dist/atoms/CardDescription.svelte.d.ts +8 -0
- package/dist/atoms/CardFooter.svelte +24 -0
- package/dist/atoms/CardFooter.svelte.d.ts +8 -0
- package/dist/atoms/CardHeader.svelte +24 -0
- package/dist/atoms/CardHeader.svelte.d.ts +8 -0
- package/dist/atoms/CardTitle.svelte +33 -0
- package/dist/atoms/CardTitle.svelte.d.ts +9 -0
- package/dist/atoms/Checkbox.svelte +0 -4
- package/dist/atoms/CodeBlock.svelte +0 -3
- package/dist/atoms/ColorPicker.svelte +33 -65
- package/dist/atoms/FeatureCard.svelte +0 -10
- package/dist/atoms/FeatureCard.svelte.d.ts +0 -8
- package/dist/atoms/Heading.svelte +0 -2
- package/dist/atoms/Input.svelte +3 -12
- package/dist/atoms/Input.svelte.d.ts +3 -2
- package/dist/atoms/Progress.svelte +0 -3
- package/dist/atoms/Select.svelte +0 -14
- package/dist/atoms/Textarea.svelte +3 -9
- package/dist/atoms/Textarea.svelte.d.ts +3 -1
- package/dist/atoms/ThemeToggle.svelte +0 -7
- package/dist/atoms/Toast.svelte +0 -1
- package/dist/atoms/Toggle.svelte +0 -6
- package/dist/atoms/Tooltip.svelte +0 -12
- package/dist/atoms/index.d.ts +5 -0
- package/dist/atoms/index.js +5 -1
- package/dist/components/atoms/index.d.ts +5 -0
- package/dist/components/atoms/index.d.ts.map +1 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/molecules/index.d.ts.map +1 -1
- package/dist/components/organisms/index.d.ts.map +1 -1
- package/dist/index.js +0 -23
- package/dist/molecules/Alert.svelte +3 -13
- package/dist/molecules/Alert.svelte.d.ts +2 -1
- package/dist/molecules/ComponentDemo.svelte +0 -2
- package/dist/molecules/ContactForm.svelte +21 -12
- package/dist/molecules/Dropdown.svelte +100 -6
- package/dist/molecules/Dropdown.svelte.d.ts +1 -0
- package/dist/molecules/Form.svelte +0 -1
- package/dist/molecules/ImageUpload.svelte +0 -4
- package/dist/molecules/Modal.svelte +29 -5
- package/dist/molecules/SlideUp.svelte +0 -2
- package/dist/molecules/Tabs.svelte +0 -2
- package/dist/molecules/index.js +0 -1
- package/dist/organisms/Navbar.svelte +0 -7
- package/dist/organisms/Navigation.svelte +0 -6
- package/dist/organisms/index.js +0 -1
- package/dist/routes/lib/variant-utils.ts +3 -1
- package/dist/zabi-components-colors.css +246 -178
- package/dist/zabi-components-theme-dark-only.css +11 -2
- package/dist/zabi-components-theme-dark.css +11 -2
- package/dist/zabi-components-theme-only.css +32 -24
- package/dist/zabi-components-theme.css +32 -24
- package/dist/zabi-components.css +259 -83
- package/package.json +2 -2
package/dist/atoms/Toggle.svelte
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
// SSR-safe ID generation
|
|
3
2
|
function generateId(prefix: string = "id"): string {
|
|
4
3
|
if (typeof window !== "undefined") {
|
|
5
4
|
return `${prefix}-${Math.random().toString(36).substr(2, 9)}`;
|
|
@@ -8,7 +7,6 @@
|
|
|
8
7
|
}
|
|
9
8
|
}
|
|
10
9
|
|
|
11
|
-
// Props using Svelte 5 runes
|
|
12
10
|
interface Props {
|
|
13
11
|
checked?: boolean;
|
|
14
12
|
disabled?: boolean;
|
|
@@ -26,15 +24,12 @@
|
|
|
26
24
|
...restProps
|
|
27
25
|
}: Props = $props();
|
|
28
26
|
|
|
29
|
-
// SSR-safe ID generation (call directly, not in onMount)
|
|
30
27
|
const toggleId = generateId("toggle");
|
|
31
28
|
|
|
32
|
-
// Handle click
|
|
33
29
|
function handleClick(event: MouseEvent) {
|
|
34
30
|
if (disabled) return;
|
|
35
31
|
checked = !checked;
|
|
36
32
|
|
|
37
|
-
// Call parent handlers if provided
|
|
38
33
|
if (onclick) onclick(event);
|
|
39
34
|
if (onchange) onchange({ checked });
|
|
40
35
|
}
|
|
@@ -57,7 +52,6 @@
|
|
|
57
52
|
return `${baseClasses} ${positionClasses}`.trim();
|
|
58
53
|
});
|
|
59
54
|
|
|
60
|
-
// Handle keyboard
|
|
61
55
|
function handleKeydown(event: KeyboardEvent) {
|
|
62
56
|
if (disabled) return;
|
|
63
57
|
if (event.key === " " || event.key === "Enter") {
|
|
@@ -22,7 +22,6 @@
|
|
|
22
22
|
let isVisible = $state(false);
|
|
23
23
|
let triggerElement: HTMLElement | null = $state(null);
|
|
24
24
|
|
|
25
|
-
// Handle keyboard events
|
|
26
25
|
function handleKeydown(event: KeyboardEvent) {
|
|
27
26
|
if (event.key === "Escape" && isVisible) {
|
|
28
27
|
isVisible = false;
|
|
@@ -30,7 +29,6 @@
|
|
|
30
29
|
}
|
|
31
30
|
}
|
|
32
31
|
|
|
33
|
-
// Handle focus events for accessibility
|
|
34
32
|
function handleFocus() {
|
|
35
33
|
if (!disabled && content) {
|
|
36
34
|
isVisible = true;
|
|
@@ -38,7 +36,6 @@
|
|
|
38
36
|
}
|
|
39
37
|
|
|
40
38
|
function handleBlur() {
|
|
41
|
-
// Delay hiding to allow clicking on tooltip content if needed
|
|
42
39
|
setTimeout(() => {
|
|
43
40
|
isVisible = false;
|
|
44
41
|
}, 100);
|
|
@@ -136,7 +133,6 @@
|
|
|
136
133
|
pointer-events: none;
|
|
137
134
|
}
|
|
138
135
|
|
|
139
|
-
/* Simplified positioning using CSS custom properties and logical properties */
|
|
140
136
|
.tooltip-container[data-placement="top"] .tooltip {
|
|
141
137
|
inset-block-end: 100%;
|
|
142
138
|
inset-inline-start: 50%;
|
|
@@ -189,7 +185,6 @@
|
|
|
189
185
|
transform: translateY(-50%) translateX(0) scale(1);
|
|
190
186
|
}
|
|
191
187
|
|
|
192
|
-
/* Modern arrow implementation using CSS clip-path for better performance */
|
|
193
188
|
.tooltip::before {
|
|
194
189
|
content: "";
|
|
195
190
|
position: absolute;
|
|
@@ -226,12 +221,10 @@
|
|
|
226
221
|
clip-path: polygon(0 50%, 100% 0, 100% 100%);
|
|
227
222
|
}
|
|
228
223
|
|
|
229
|
-
/* Delay support using CSS custom properties */
|
|
230
224
|
.tooltip-container[data-delay] .tooltip {
|
|
231
225
|
transition-delay: calc(var(--tooltip-delay, 0) * 1ms);
|
|
232
226
|
}
|
|
233
227
|
|
|
234
|
-
/* Responsive design with modern CSS */
|
|
235
228
|
@media (max-width: 640px) {
|
|
236
229
|
.tooltip {
|
|
237
230
|
--tooltip-max-width: calc(100vw - 2rem);
|
|
@@ -250,19 +243,16 @@
|
|
|
250
243
|
}
|
|
251
244
|
}
|
|
252
245
|
|
|
253
|
-
/* Dark mode support */
|
|
254
246
|
@media (prefers-color-scheme: dark) {
|
|
255
247
|
:root {
|
|
256
248
|
--tooltip-bg: #374151;
|
|
257
249
|
}
|
|
258
250
|
}
|
|
259
251
|
|
|
260
|
-
/* Use CSS color-scheme for better dark mode support */
|
|
261
252
|
.dark :root {
|
|
262
253
|
--tooltip-bg: #374151;
|
|
263
254
|
}
|
|
264
255
|
|
|
265
|
-
/* High contrast mode support */
|
|
266
256
|
@media (prefers-contrast: high) {
|
|
267
257
|
:root {
|
|
268
258
|
--tooltip-bg: #000000;
|
|
@@ -271,7 +261,6 @@
|
|
|
271
261
|
}
|
|
272
262
|
}
|
|
273
263
|
|
|
274
|
-
/* Reduced motion support */
|
|
275
264
|
@media (prefers-reduced-motion: reduce) {
|
|
276
265
|
.tooltip {
|
|
277
266
|
transition: opacity 0.1s ease-in-out, visibility 0.1s ease-in-out;
|
|
@@ -288,7 +277,6 @@
|
|
|
288
277
|
}
|
|
289
278
|
}
|
|
290
279
|
|
|
291
|
-
/* Focus-visible for better keyboard navigation */
|
|
292
280
|
.tooltip-container:focus-visible {
|
|
293
281
|
outline: 2px solid var(--color-focus, #8fa8ff);
|
|
294
282
|
outline-offset: 2px;
|
package/dist/atoms/index.d.ts
CHANGED
|
@@ -2,6 +2,11 @@ export { default as Toggle } from './Toggle.svelte';
|
|
|
2
2
|
export { default as Badge } from './Badge.svelte';
|
|
3
3
|
export { default as Button } from './Button.svelte';
|
|
4
4
|
export { default as Card } from './Card.svelte';
|
|
5
|
+
export { default as CardHeader } from './CardHeader.svelte';
|
|
6
|
+
export { default as CardContent } from './CardContent.svelte';
|
|
7
|
+
export { default as CardFooter } from './CardFooter.svelte';
|
|
8
|
+
export { default as CardTitle } from './CardTitle.svelte';
|
|
9
|
+
export { default as CardDescription } from './CardDescription.svelte';
|
|
5
10
|
export { default as Input } from './Input.svelte';
|
|
6
11
|
export { default as Textarea } from './Textarea.svelte';
|
|
7
12
|
export { default as Select } from './Select.svelte';
|
package/dist/atoms/index.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
// Export atom components - All SSR-safe components
|
|
2
1
|
export { default as Toggle } from './Toggle.svelte';
|
|
3
2
|
export { default as Badge } from './Badge.svelte';
|
|
4
3
|
export { default as Button } from './Button.svelte';
|
|
5
4
|
export { default as Card } from './Card.svelte';
|
|
5
|
+
export { default as CardHeader } from './CardHeader.svelte';
|
|
6
|
+
export { default as CardContent } from './CardContent.svelte';
|
|
7
|
+
export { default as CardFooter } from './CardFooter.svelte';
|
|
8
|
+
export { default as CardTitle } from './CardTitle.svelte';
|
|
9
|
+
export { default as CardDescription } from './CardDescription.svelte';
|
|
6
10
|
export { default as Input } from './Input.svelte';
|
|
7
11
|
export { default as Textarea } from './Textarea.svelte';
|
|
8
12
|
export { default as Select } from './Select.svelte';
|
|
@@ -2,6 +2,11 @@ export { default as Toggle } from './Toggle.svelte';
|
|
|
2
2
|
export { default as Badge } from './Badge.svelte';
|
|
3
3
|
export { default as Button } from './Button.svelte';
|
|
4
4
|
export { default as Card } from './Card.svelte';
|
|
5
|
+
export { default as CardHeader } from './CardHeader.svelte';
|
|
6
|
+
export { default as CardContent } from './CardContent.svelte';
|
|
7
|
+
export { default as CardFooter } from './CardFooter.svelte';
|
|
8
|
+
export { default as CardTitle } from './CardTitle.svelte';
|
|
9
|
+
export { default as CardDescription } from './CardDescription.svelte';
|
|
5
10
|
export { default as Input } from './Input.svelte';
|
|
6
11
|
export { default as Textarea } from './Textarea.svelte';
|
|
7
12
|
export { default as Select } from './Select.svelte';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/atoms/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/atoms/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAE,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACpE,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAEpE,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAE1D,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAEtE,eAAO,MAAM,QAAQ,GAAI,SAAQ,MAAa,KAAG,MAMhD,CAAC;AAEF,eAAO,MAAM,EAAE,GAAI,GAAG,SAAS,CAAC,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,KAAK,CAAC,EAAE,KAAG,MAClC,CAAC;AAEtC,eAAO,MAAM,WAAW,GAAI,MAAM,eAAe,KAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAGrE,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,OAAO,MAAM,KAAG,OAG7C,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,OAAO,GAAG,KAAG,OAE7C,CAAC;AAEF,eAAO,MAAM,SAAS,QAAO,OAE5B,CAAC;AAEF,eAAO,MAAM,UAAU,QAAO,MAAM,GAAG,SAEtC,CAAC;AAEF,eAAO,MAAM,YAAY,QAAO,QAAQ,GAAG,SAE1C,CAAC;AAEF,eAAO,MAAM,gBAAgB,QAAO,OAAO,GAAG,SAE7C,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,SAAQ,MAAa,KAAG,MAKlD,CAAC;AAEF,OAAO,EACH,GAAG,EACH,IAAI,EACJ,OAAO,EACP,IAAI,EACJ,YAAY,EACZ,SAAS,EACT,WAAW,EACX,YAAY,EACZ,GAAG,EACH,SAAS,EACT,SAAS,EACT,QAAQ,EACX,MAAM,gBAAgB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/molecules/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/molecules/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/organisms/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/organisms/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
// Zabi Components - Clean & Simple Svelte Component Library
|
|
2
|
-
// Main library entry point - All components in one place
|
|
3
|
-
// ============================================================================
|
|
4
|
-
// ATOM COMPONENTS - Basic UI building blocks
|
|
5
|
-
// ============================================================================
|
|
6
|
-
// Form Controls - All SSR-safe components
|
|
7
1
|
export { default as Toggle } from './atoms/Toggle.svelte';
|
|
8
2
|
export { default as Badge } from './atoms/Badge.svelte';
|
|
9
3
|
export { default as Button } from './atoms/Button.svelte';
|
|
@@ -14,9 +8,6 @@ export { default as Select } from './atoms/Select.svelte';
|
|
|
14
8
|
export { default as ColorPicker } from './atoms/ColorPicker.svelte';
|
|
15
9
|
export { default as Heading } from './atoms/Heading.svelte';
|
|
16
10
|
export { default as ThemeToggle } from './atoms/ThemeToggle.svelte';
|
|
17
|
-
// ============================================================================
|
|
18
|
-
// MOLECULE COMPONENTS - Composite UI elements
|
|
19
|
-
// ============================================================================
|
|
20
11
|
export { default as Alert } from './molecules/Alert.svelte';
|
|
21
12
|
export { default as ComponentDemo } from './molecules/ComponentDemo.svelte';
|
|
22
13
|
export { default as ContactForm } from './molecules/ContactForm.svelte';
|
|
@@ -26,15 +17,8 @@ export { default as ImageUpload } from './molecules/ImageUpload.svelte';
|
|
|
26
17
|
export { default as Modal } from './molecules/Modal.svelte';
|
|
27
18
|
export { default as SlideUp } from './molecules/SlideUp.svelte';
|
|
28
19
|
export { default as Tabs } from './molecules/Tabs.svelte';
|
|
29
|
-
// ============================================================================
|
|
30
|
-
// ORGANISM COMPONENTS - Complex UI sections
|
|
31
|
-
// ============================================================================
|
|
32
20
|
export { default as Navbar } from './organisms/Navbar.svelte';
|
|
33
21
|
export { default as Navigation } from './organisms/Navigation.svelte';
|
|
34
|
-
// ============================================================================
|
|
35
|
-
// UTILITIES - Helper functions and utilities
|
|
36
|
-
// ============================================================================
|
|
37
|
-
// Simple utility functions
|
|
38
22
|
export const createId = (prefix = 'id') => {
|
|
39
23
|
if (typeof window !== 'undefined') {
|
|
40
24
|
return `${prefix}-${Math.random().toString(36).substr(2, 9)}`;
|
|
@@ -44,12 +28,10 @@ export const createId = (prefix = 'id') => {
|
|
|
44
28
|
}
|
|
45
29
|
};
|
|
46
30
|
export const cn = (...classes) => classes.filter(Boolean).join(' ');
|
|
47
|
-
// Form utilities
|
|
48
31
|
export const getFormData = (form) => {
|
|
49
32
|
const formData = new FormData(form);
|
|
50
33
|
return Object.fromEntries(formData.entries());
|
|
51
34
|
};
|
|
52
|
-
// Validation utilities
|
|
53
35
|
export const validateEmail = (email) => {
|
|
54
36
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
55
37
|
return emailRegex.test(email);
|
|
@@ -57,7 +39,6 @@ export const validateEmail = (email) => {
|
|
|
57
39
|
export const validateRequired = (value) => {
|
|
58
40
|
return value !== null && value !== undefined && value !== '';
|
|
59
41
|
};
|
|
60
|
-
// SSR-safe utilities
|
|
61
42
|
export const isBrowser = () => {
|
|
62
43
|
return typeof window !== 'undefined';
|
|
63
44
|
};
|
|
@@ -70,14 +51,10 @@ export const safeDocument = () => {
|
|
|
70
51
|
export const safeLocalStorage = () => {
|
|
71
52
|
return typeof window !== 'undefined' ? localStorage : undefined;
|
|
72
53
|
};
|
|
73
|
-
// Alias for backward compatibility - separate function to avoid initialization issues
|
|
74
54
|
export const generateId = (prefix = 'id') => {
|
|
75
55
|
if (typeof window !== 'undefined') {
|
|
76
56
|
return `${prefix}-${Math.random().toString(36).substr(2, 9)}`;
|
|
77
57
|
}
|
|
78
58
|
return `${prefix}-ssr-${Date.now()}`;
|
|
79
59
|
};
|
|
80
|
-
// ============================================================================
|
|
81
|
-
// ICONS - Re-export commonly used Lucide icons
|
|
82
|
-
// ============================================================================
|
|
83
60
|
export { Sun, Moon, Monitor, Grip, GripVertical, ChevronUp, ChevronDown, ChevronRight, Zap, Briefcase, Clipboard, Settings } from '@lucide/svelte';
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import type { ExtendedSemanticVariant } from "../../types/variants.js";
|
|
3
|
+
|
|
2
4
|
interface Props {
|
|
3
|
-
variant?:
|
|
4
|
-
| "info"
|
|
5
|
-
| "success"
|
|
6
|
-
| "warning"
|
|
7
|
-
| "error"
|
|
8
|
-
| "neutral"
|
|
9
|
-
| "energetic";
|
|
5
|
+
variant?: ExtendedSemanticVariant;
|
|
10
6
|
title?: string;
|
|
11
7
|
message?: string;
|
|
12
8
|
closable?: boolean;
|
|
@@ -26,7 +22,6 @@
|
|
|
26
22
|
}: Props & { children?: any } = $props();
|
|
27
23
|
|
|
28
24
|
function handleDismiss(event: MouseEvent) {
|
|
29
|
-
// Call the parent's onclick handler if provided
|
|
30
25
|
if (onclick) {
|
|
31
26
|
onclick(event);
|
|
32
27
|
}
|
|
@@ -78,7 +73,6 @@
|
|
|
78
73
|
aria-atomic="true"
|
|
79
74
|
{...restProps}
|
|
80
75
|
>
|
|
81
|
-
<!-- Dismiss button (X in top right) -->
|
|
82
76
|
{#if closable}
|
|
83
77
|
<button
|
|
84
78
|
onclick={handleDismiss}
|
|
@@ -102,14 +96,11 @@
|
|
|
102
96
|
</button>
|
|
103
97
|
{/if}
|
|
104
98
|
|
|
105
|
-
<!-- Content -->
|
|
106
99
|
<div class="flex items-start gap-3 {closable ? 'pr-8' : ''}">
|
|
107
|
-
<!-- Icon -->
|
|
108
100
|
<div class="shrink-0 mt-0.5">
|
|
109
101
|
{@html iconSvg[variant]}
|
|
110
102
|
</div>
|
|
111
103
|
|
|
112
|
-
<!-- Text content -->
|
|
113
104
|
<div class="flex-1 min-w-0">
|
|
114
105
|
{#if title}
|
|
115
106
|
<h4 class="font-semibold text-sm mb-1 text-headline">
|
|
@@ -121,7 +112,6 @@
|
|
|
121
112
|
{message}
|
|
122
113
|
</p>
|
|
123
114
|
{/if}
|
|
124
|
-
<!-- Default slot for custom content -->
|
|
125
115
|
{@render children?.()}
|
|
126
116
|
</div>
|
|
127
117
|
</div>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import type { ExtendedSemanticVariant } from "../../types/variants.js";
|
|
1
2
|
interface Props {
|
|
2
|
-
variant?:
|
|
3
|
+
variant?: ExtendedSemanticVariant;
|
|
3
4
|
title?: string;
|
|
4
5
|
message?: string;
|
|
5
6
|
closable?: boolean;
|
|
@@ -32,7 +32,6 @@
|
|
|
32
32
|
className="overflow-hidden {className}"
|
|
33
33
|
{...restProps}
|
|
34
34
|
>
|
|
35
|
-
<!-- Toggle Button positioned absolutely -->
|
|
36
35
|
<button
|
|
37
36
|
onclick={() => (showCode = !showCode)}
|
|
38
37
|
class="absolute top-8 right-8 flex items-center gap-2 px-3 py-1.5 text-sm text-description hover:text-body hover:bg-base-50 rounded-md transition-colors duration-200 z-10"
|
|
@@ -77,7 +76,6 @@
|
|
|
77
76
|
{/if}
|
|
78
77
|
</button>
|
|
79
78
|
|
|
80
|
-
<!-- Content -->
|
|
81
79
|
{#if showCode}
|
|
82
80
|
<CodeBlock {code} {language} />
|
|
83
81
|
{:else}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import Textarea from "../atoms/Textarea.svelte";
|
|
5
5
|
import Checkbox from "../atoms/Checkbox.svelte";
|
|
6
6
|
import Button from "../atoms/Button.svelte";
|
|
7
|
+
import Card from "../atoms/Card.svelte";
|
|
7
8
|
import type { ContactFormData } from "../../types/page.types";
|
|
8
9
|
|
|
9
10
|
interface Props {
|
|
@@ -33,21 +34,11 @@
|
|
|
33
34
|
subscribe: formData.get("subscribe") === "on" || false,
|
|
34
35
|
};
|
|
35
36
|
|
|
36
|
-
// Form submission is now handled by the parent component
|
|
37
|
-
// through event forwarding. The data is available for processing.
|
|
38
37
|
console.log("Form submitted with data:", data);
|
|
39
38
|
}
|
|
40
39
|
</script>
|
|
41
40
|
|
|
42
|
-
<
|
|
43
|
-
<div class="mb-6">
|
|
44
|
-
<h2 class="text-2xl font-bold text-headline mb-2">Get in Touch</h2>
|
|
45
|
-
<p class="text-description">
|
|
46
|
-
We'd love to hear from you. Send us a message and we'll respond as
|
|
47
|
-
soon as possible.
|
|
48
|
-
</p>
|
|
49
|
-
</div>
|
|
50
|
-
|
|
41
|
+
<Card size="md" fullWidth={true} title="Get in Touch">
|
|
51
42
|
<Form onsubmit={handleFormSubmit} className="space-y-4">
|
|
52
43
|
<div class="space-y-4">
|
|
53
44
|
<Input
|
|
@@ -68,6 +59,24 @@
|
|
|
68
59
|
oninput={(e) =>
|
|
69
60
|
(formData.email = (e.target as HTMLInputElement).value)}
|
|
70
61
|
/>
|
|
62
|
+
<Input
|
|
63
|
+
type="email"
|
|
64
|
+
name="email"
|
|
65
|
+
label="Email"
|
|
66
|
+
placeholder="Enter your email"
|
|
67
|
+
value={formData.email}
|
|
68
|
+
oninput={(e) =>
|
|
69
|
+
(formData.email = (e.target as HTMLInputElement).value)}
|
|
70
|
+
/>
|
|
71
|
+
<Input
|
|
72
|
+
type="email"
|
|
73
|
+
name="email"
|
|
74
|
+
label="Email"
|
|
75
|
+
placeholder="Enter your email"
|
|
76
|
+
value={formData.email}
|
|
77
|
+
oninput={(e) =>
|
|
78
|
+
(formData.email = (e.target as HTMLInputElement).value)}
|
|
79
|
+
/>
|
|
71
80
|
<Textarea
|
|
72
81
|
name="message"
|
|
73
82
|
label="Message"
|
|
@@ -95,4 +104,4 @@
|
|
|
95
104
|
</Button>
|
|
96
105
|
</div>
|
|
97
106
|
</Form>
|
|
98
|
-
</
|
|
107
|
+
</Card>
|
|
@@ -2,17 +2,99 @@
|
|
|
2
2
|
interface Props {
|
|
3
3
|
isOpen?: boolean;
|
|
4
4
|
placement?: "bottom-start" | "bottom-end" | "top-start" | "top-end";
|
|
5
|
+
ariaLabel?: string;
|
|
5
6
|
}
|
|
6
7
|
|
|
7
8
|
let {
|
|
8
9
|
isOpen = false,
|
|
9
10
|
placement = "bottom-start",
|
|
11
|
+
ariaLabel = "Menu",
|
|
10
12
|
children,
|
|
11
13
|
trigger,
|
|
12
14
|
...restProps
|
|
13
15
|
}: Props & { children?: any; trigger?: any } = $props();
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
let dropdownId = `dropdown-${Math.random().toString(36).substr(2, 9)}`;
|
|
18
|
+
let triggerElement: HTMLElement | null = null;
|
|
19
|
+
let menuElement: HTMLElement | null = null;
|
|
20
|
+
|
|
21
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
22
|
+
if (!isOpen) {
|
|
23
|
+
if (event.key === "Enter" || event.key === " " || event.key === "ArrowDown") {
|
|
24
|
+
event.preventDefault();
|
|
25
|
+
isOpen = true;
|
|
26
|
+
}
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
switch (event.key) {
|
|
31
|
+
case "Escape":
|
|
32
|
+
event.preventDefault();
|
|
33
|
+
isOpen = false;
|
|
34
|
+
triggerElement?.focus();
|
|
35
|
+
break;
|
|
36
|
+
case "ArrowDown":
|
|
37
|
+
event.preventDefault();
|
|
38
|
+
focusNextItem();
|
|
39
|
+
break;
|
|
40
|
+
case "ArrowUp":
|
|
41
|
+
event.preventDefault();
|
|
42
|
+
focusPreviousItem();
|
|
43
|
+
break;
|
|
44
|
+
case "Home":
|
|
45
|
+
event.preventDefault();
|
|
46
|
+
focusFirstItem();
|
|
47
|
+
break;
|
|
48
|
+
case "End":
|
|
49
|
+
event.preventDefault();
|
|
50
|
+
focusLastItem();
|
|
51
|
+
break;
|
|
52
|
+
case "Tab":
|
|
53
|
+
isOpen = false;
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function getMenuItems(): HTMLElement[] {
|
|
59
|
+
if (!menuElement) return [];
|
|
60
|
+
return Array.from(
|
|
61
|
+
menuElement.querySelectorAll<HTMLElement>('[role="menuitem"], button, a')
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function focusNextItem() {
|
|
66
|
+
const items = getMenuItems();
|
|
67
|
+
const currentIndex = items.findIndex((item) => item === document.activeElement);
|
|
68
|
+
const nextIndex = currentIndex < items.length - 1 ? currentIndex + 1 : 0;
|
|
69
|
+
items[nextIndex]?.focus();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function focusPreviousItem() {
|
|
73
|
+
const items = getMenuItems();
|
|
74
|
+
const currentIndex = items.findIndex((item) => item === document.activeElement);
|
|
75
|
+
const prevIndex = currentIndex > 0 ? currentIndex - 1 : items.length - 1;
|
|
76
|
+
items[prevIndex]?.focus();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function focusFirstItem() {
|
|
80
|
+
const items = getMenuItems();
|
|
81
|
+
items[0]?.focus();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function focusLastItem() {
|
|
85
|
+
const items = getMenuItems();
|
|
86
|
+
items[items.length - 1]?.focus();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Focus first item when dropdown opens
|
|
90
|
+
$effect(() => {
|
|
91
|
+
if (isOpen && menuElement) {
|
|
92
|
+
setTimeout(() => {
|
|
93
|
+
focusFirstItem();
|
|
94
|
+
}, 0);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
16
98
|
const placementClasses = $derived(() => {
|
|
17
99
|
const base = "absolute z-dropdown min-w-[12rem]";
|
|
18
100
|
const positioning = {
|
|
@@ -24,7 +106,6 @@
|
|
|
24
106
|
return `${base} ${positioning[placement]}`;
|
|
25
107
|
});
|
|
26
108
|
|
|
27
|
-
// Get transform classes based on placement and open state
|
|
28
109
|
const transformClasses = $derived(() => {
|
|
29
110
|
if (!isOpen) {
|
|
30
111
|
const hiddenTransform = {
|
|
@@ -38,7 +119,6 @@
|
|
|
38
119
|
return "opacity-100 visible translate-y-0";
|
|
39
120
|
});
|
|
40
121
|
|
|
41
|
-
// Get dropdown content classes
|
|
42
122
|
const dropdownContentClasses = $derived(() => {
|
|
43
123
|
return `
|
|
44
124
|
${placementClasses()}
|
|
@@ -57,11 +137,25 @@
|
|
|
57
137
|
});
|
|
58
138
|
</script>
|
|
59
139
|
|
|
60
|
-
<div
|
|
61
|
-
|
|
140
|
+
<div
|
|
141
|
+
class="relative inline-block"
|
|
142
|
+
data-placement={placement}
|
|
143
|
+
onkeydown={handleKeydown}
|
|
144
|
+
{...restProps}
|
|
145
|
+
>
|
|
146
|
+
<div bind:this={triggerElement}>
|
|
147
|
+
{@render trigger?.()}
|
|
148
|
+
</div>
|
|
62
149
|
|
|
63
150
|
{#if isOpen}
|
|
64
|
-
<div
|
|
151
|
+
<div
|
|
152
|
+
bind:this={menuElement}
|
|
153
|
+
id={dropdownId}
|
|
154
|
+
class={dropdownContentClasses()}
|
|
155
|
+
role="menu"
|
|
156
|
+
aria-label={ariaLabel}
|
|
157
|
+
tabindex="-1"
|
|
158
|
+
>
|
|
65
159
|
{@render children?.()}
|
|
66
160
|
</div>
|
|
67
161
|
{/if}
|
|
@@ -30,7 +30,6 @@
|
|
|
30
30
|
|
|
31
31
|
const file = input.files[0];
|
|
32
32
|
|
|
33
|
-
// SSR-safe URL creation
|
|
34
33
|
if (typeof URL !== "undefined" && URL.createObjectURL) {
|
|
35
34
|
const url = URL.createObjectURL(file);
|
|
36
35
|
value = url;
|
|
@@ -54,7 +53,6 @@
|
|
|
54
53
|
|
|
55
54
|
<div class="space-y-3">
|
|
56
55
|
{#if value}
|
|
57
|
-
<!-- Image Display -->
|
|
58
56
|
<div class="relative group">
|
|
59
57
|
<img
|
|
60
58
|
src={value}
|
|
@@ -85,7 +83,6 @@
|
|
|
85
83
|
</div>
|
|
86
84
|
</div>
|
|
87
85
|
{:else}
|
|
88
|
-
<!-- Empty State -->
|
|
89
86
|
<div
|
|
90
87
|
class="border-2 border-dashed border-base-200 rounded-lg p-6 text-center hover:border-brand-500 transition-colors {disabled
|
|
91
88
|
? 'cursor-not-allowed opacity-50'
|
|
@@ -113,7 +110,6 @@
|
|
|
113
110
|
</div>
|
|
114
111
|
{/if}
|
|
115
112
|
|
|
116
|
-
<!-- Hidden File Input -->
|
|
117
113
|
<input
|
|
118
114
|
bind:this={fileInput}
|
|
119
115
|
type="file"
|