zabi-components 5.0.17 → 5.0.19
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/focus-utils.d.ts +23 -0
- package/dist/routes/lib/focus-utils.ts +112 -0
- 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 +5 -4
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
level?: 1 | 2 | 3 | 4 | 5 | 6;
|
|
6
|
+
className?: string;
|
|
7
|
+
children?: Snippet;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let {
|
|
11
|
+
level = 3,
|
|
12
|
+
className = "",
|
|
13
|
+
children,
|
|
14
|
+
...restProps
|
|
15
|
+
}: Props = $props();
|
|
16
|
+
|
|
17
|
+
const headingTag = $derived(`h${level}`);
|
|
18
|
+
const headingClasses = $derived(() => {
|
|
19
|
+
const baseClasses = "text-2xl font-semibold leading-none tracking-tight text-headline";
|
|
20
|
+
return `${baseClasses} ${className}`.trim();
|
|
21
|
+
});
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<svelte:element
|
|
25
|
+
this={headingTag}
|
|
26
|
+
class={headingClasses()}
|
|
27
|
+
{...restProps}
|
|
28
|
+
>
|
|
29
|
+
{#if children}
|
|
30
|
+
{@render children()}
|
|
31
|
+
{/if}
|
|
32
|
+
</svelte:element>
|
|
33
|
+
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
interface Props {
|
|
3
|
+
level?: 1 | 2 | 3 | 4 | 5 | 6;
|
|
4
|
+
className?: string;
|
|
5
|
+
children?: Snippet;
|
|
6
|
+
}
|
|
7
|
+
declare const CardTitle: import("svelte").Component<Props, {}, "">;
|
|
8
|
+
type CardTitle = ReturnType<typeof CardTitle>;
|
|
9
|
+
export default CardTitle;
|
|
@@ -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)}`;
|
|
@@ -25,10 +24,8 @@
|
|
|
25
24
|
...restProps
|
|
26
25
|
}: Props = $props();
|
|
27
26
|
|
|
28
|
-
// Generate unique ID - SSR safe (call directly, not in $state)
|
|
29
27
|
const checkboxId = generateId("checkbox");
|
|
30
28
|
|
|
31
|
-
// Checkbox container classes matching M3 design
|
|
32
29
|
const checkboxContainerClasses = $derived(() => {
|
|
33
30
|
const baseClasses =
|
|
34
31
|
"relative inline-flex items-center justify-center w-5 h-5 rounded transition-all duration-200";
|
|
@@ -39,7 +36,6 @@
|
|
|
39
36
|
return `${baseClasses} cursor-pointer`;
|
|
40
37
|
});
|
|
41
38
|
|
|
42
|
-
// Checkbox border and background classes matching M3 design
|
|
43
39
|
const checkboxBoxClasses = $derived(() => {
|
|
44
40
|
if (checked) {
|
|
45
41
|
return disabled
|
|
@@ -33,7 +33,6 @@
|
|
|
33
33
|
class="code-block relative bg-base-900 rounded-lg overflow-hidden {className}"
|
|
34
34
|
{...restProps}
|
|
35
35
|
>
|
|
36
|
-
<!-- Header -->
|
|
37
36
|
<div
|
|
38
37
|
class="flex items-center justify-between px-4 py-2 bg-base-800 border-b border-base-700"
|
|
39
38
|
>
|
|
@@ -81,7 +80,6 @@
|
|
|
81
80
|
{/if}
|
|
82
81
|
</div>
|
|
83
82
|
|
|
84
|
-
<!-- Code content -->
|
|
85
83
|
<pre class="p-4 overflow-x-auto text-sm text-base-100 leading-relaxed"><code
|
|
86
84
|
class="language-{language}">{@html code}</code
|
|
87
85
|
></pre>
|
|
@@ -92,7 +90,6 @@
|
|
|
92
90
|
font-family: "Monaco", "Menlo", "Ubuntu Mono", monospace;
|
|
93
91
|
}
|
|
94
92
|
|
|
95
|
-
/* Basic syntax highlighting for common languages */
|
|
96
93
|
:global(.language-svelte .token.tag),
|
|
97
94
|
:global(.language-html .token.tag) {
|
|
98
95
|
color: #f92672;
|
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
|
|
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
|
-
}
|
|
2
|
+
import Input from "./Input.svelte";
|
|
10
3
|
|
|
11
4
|
interface Props {
|
|
12
5
|
value?: string;
|
|
@@ -25,90 +18,65 @@
|
|
|
25
18
|
...restProps
|
|
26
19
|
}: Props = $props();
|
|
27
20
|
|
|
28
|
-
// Generate unique ID - SSR safe
|
|
29
|
-
const inputId = generateId("color-picker");
|
|
30
|
-
|
|
31
|
-
// Hex color validation
|
|
32
21
|
function isValidHex(hex: string): boolean {
|
|
33
22
|
const hexPattern = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
|
|
34
23
|
return hexPattern.test(hex);
|
|
35
24
|
}
|
|
36
25
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
26
|
+
const variant = $derived(() => {
|
|
27
|
+
if (value && !isValidHex(value)) {
|
|
28
|
+
return "error";
|
|
29
|
+
}
|
|
30
|
+
return "default";
|
|
31
|
+
});
|
|
41
32
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
33
|
+
const message = $derived(() => {
|
|
34
|
+
if (value && !isValidHex(value)) {
|
|
35
|
+
return "Please enter a valid hex color (e.g., #ff0000 or #f00)";
|
|
45
36
|
}
|
|
37
|
+
return "";
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
function handleInput(event: Event) {
|
|
41
|
+
if (onchange) onchange(event);
|
|
46
42
|
}
|
|
47
43
|
|
|
48
|
-
|
|
49
|
-
function handleHexBlur(event: Event) {
|
|
44
|
+
function handleBlur(event: Event) {
|
|
50
45
|
const target = event.target as HTMLInputElement;
|
|
51
46
|
let hexValue = target.value.trim();
|
|
52
47
|
|
|
53
|
-
// Add # if missing
|
|
54
48
|
if (hexValue && !hexValue.startsWith("#")) {
|
|
55
49
|
hexValue = "#" + hexValue;
|
|
56
50
|
}
|
|
57
51
|
|
|
58
|
-
// Validate and update
|
|
59
52
|
if (hexValue === "" || isValidHex(hexValue)) {
|
|
60
53
|
value = hexValue;
|
|
61
|
-
|
|
62
|
-
} else {
|
|
63
|
-
// Reset to current value if invalid
|
|
64
|
-
target.value = value;
|
|
54
|
+
if (onchange) onchange(event);
|
|
65
55
|
}
|
|
66
56
|
}
|
|
67
|
-
|
|
68
|
-
// Input classes
|
|
69
|
-
const inputClasses = $derived(() => {
|
|
70
|
-
const baseClasses =
|
|
71
|
-
"w-full px-3 py-2 bg-input hover:bg-input-hover focus:bg-input-focus disabled:bg-input-disabled rounded-lg text-sm text-body placeholder:text-description transition-colors focus:outline-none focus:ring-2 focus:ring-offset-0 disabled:opacity-50 disabled:cursor-not-allowed";
|
|
72
|
-
const stateClasses =
|
|
73
|
-
isValidHex(value) || value === ""
|
|
74
|
-
? "border-base-300 focus:border-brand-500 focus:ring-brand-500"
|
|
75
|
-
: "border-red-300 focus:border-red-500 focus:ring-red-500";
|
|
76
|
-
|
|
77
|
-
return `${baseClasses} ${stateClasses}`.trim();
|
|
78
|
-
});
|
|
79
57
|
</script>
|
|
80
58
|
|
|
81
|
-
<div
|
|
82
|
-
|
|
83
|
-
<
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
{disabled}
|
|
97
|
-
class={inputClasses()}
|
|
98
|
-
aria-label="Hex color input"
|
|
99
|
-
/>
|
|
59
|
+
<div {...restProps}>
|
|
60
|
+
<div class="flex items-start gap-2">
|
|
61
|
+
<div class="flex-1">
|
|
62
|
+
<Input
|
|
63
|
+
bind:value
|
|
64
|
+
{label}
|
|
65
|
+
{placeholder}
|
|
66
|
+
{disabled}
|
|
67
|
+
variant={variant()}
|
|
68
|
+
message={message()}
|
|
69
|
+
oninput={handleInput}
|
|
70
|
+
onblur={handleBlur}
|
|
71
|
+
aria-label="Hex color input"
|
|
72
|
+
/>
|
|
73
|
+
</div>
|
|
100
74
|
{#if value && isValidHex(value)}
|
|
101
75
|
<div
|
|
102
|
-
class="w-8 h-8 rounded border-2 border-base-300 shrink-0"
|
|
76
|
+
class="w-8 h-8 rounded border-2 border-base-300 shrink-0 mt-6"
|
|
103
77
|
style="background-color: {value};"
|
|
104
78
|
aria-label="Color preview"
|
|
105
79
|
></div>
|
|
106
80
|
{/if}
|
|
107
81
|
</div>
|
|
108
|
-
|
|
109
|
-
{#if value && !isValidHex(value)}
|
|
110
|
-
<p class="text-xs text-red-500">
|
|
111
|
-
Please enter a valid hex color (e.g., #ff0000 or #f00)
|
|
112
|
-
</p>
|
|
113
|
-
{/if}
|
|
114
82
|
</div>
|
|
@@ -1,19 +1,10 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onMount } from "svelte";
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
* FeatureCard component - SSR-safe implementation
|
|
6
|
-
* @component
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
4
|
interface Props {
|
|
10
|
-
/** Icon to display */
|
|
11
5
|
icon?: string;
|
|
12
|
-
/** Title of the feature */
|
|
13
6
|
title: string;
|
|
14
|
-
/** Description of the feature */
|
|
15
7
|
description: string;
|
|
16
|
-
/** Additional CSS classes */
|
|
17
8
|
className?: string;
|
|
18
9
|
}
|
|
19
10
|
|
|
@@ -52,7 +43,6 @@
|
|
|
52
43
|
</div>
|
|
53
44
|
</div>
|
|
54
45
|
{:else}
|
|
55
|
-
<!-- SSR fallback -->
|
|
56
46
|
<div
|
|
57
47
|
class="p-6 rounded-lg bg-base-100 border border-base-300 hover:border-base-400 hover:shadow-sm transition-colors duration-200 {className}"
|
|
58
48
|
{...restProps}
|
|
@@ -1,15 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FeatureCard component - SSR-safe implementation
|
|
3
|
-
* @component
|
|
4
|
-
*/
|
|
5
1
|
interface Props {
|
|
6
|
-
/** Icon to display */
|
|
7
2
|
icon?: string;
|
|
8
|
-
/** Title of the feature */
|
|
9
3
|
title: string;
|
|
10
|
-
/** Description of the feature */
|
|
11
4
|
description: string;
|
|
12
|
-
/** Additional CSS classes */
|
|
13
5
|
className?: string;
|
|
14
6
|
}
|
|
15
7
|
declare const FeatureCard: import("svelte").Component<Props, {}, "">;
|
|
@@ -6,11 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
let { level = 1, text }: Props = $props();
|
|
8
8
|
|
|
9
|
-
// Automatic weight and size based on heading level
|
|
10
9
|
const fontWeight = $derived(level <= 2 ? "700" : "500");
|
|
11
10
|
const Tag = $derived(`h${level}`);
|
|
12
11
|
|
|
13
|
-
// Size classes for different heading levels
|
|
14
12
|
const sizeClasses = {
|
|
15
13
|
1: "text-4xl",
|
|
16
14
|
2: "text-3xl",
|
package/dist/atoms/Input.svelte
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { CheckCircle, AlertTriangle, AlertCircle } from "@lucide/svelte";
|
|
3
|
+
import type { SemanticVariant, SizeVariant } from "../../types/variants.js";
|
|
3
4
|
|
|
4
|
-
// SSR-safe ID generation
|
|
5
5
|
function generateId(prefix: string = "id"): string {
|
|
6
6
|
if (typeof window !== "undefined") {
|
|
7
7
|
return `${prefix}-${Math.random().toString(36).substr(2, 9)}`;
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
label?: string;
|
|
18
18
|
placeholder?: string;
|
|
19
19
|
disabled?: boolean;
|
|
20
|
-
size?:
|
|
21
|
-
variant?:
|
|
20
|
+
size?: SizeVariant;
|
|
21
|
+
variant?: SemanticVariant;
|
|
22
22
|
message?: string;
|
|
23
23
|
oninput?: (event: Event) => void;
|
|
24
24
|
}
|
|
@@ -37,10 +37,8 @@
|
|
|
37
37
|
...restProps
|
|
38
38
|
}: Props = $props();
|
|
39
39
|
|
|
40
|
-
// Generate unique ID - SSR safe (call directly, not in $state)
|
|
41
40
|
const inputId = generateId("input");
|
|
42
41
|
|
|
43
|
-
// Size classes matching M3 design specifications
|
|
44
42
|
const sizeClass = $derived(() => {
|
|
45
43
|
if (size === "sm") {
|
|
46
44
|
return {
|
|
@@ -55,7 +53,6 @@
|
|
|
55
53
|
leading: "leading-6",
|
|
56
54
|
};
|
|
57
55
|
} else {
|
|
58
|
-
// default md
|
|
59
56
|
return {
|
|
60
57
|
padding: "px-4 py-2.5",
|
|
61
58
|
text: "text-base",
|
|
@@ -64,7 +61,6 @@
|
|
|
64
61
|
}
|
|
65
62
|
});
|
|
66
63
|
|
|
67
|
-
// Variant classes using semantic colors
|
|
68
64
|
const variantClass = $derived(() => {
|
|
69
65
|
return variant === "success"
|
|
70
66
|
? "border-success focus:border-success focus:ring-success"
|
|
@@ -75,7 +71,6 @@
|
|
|
75
71
|
: "border-0 focus:ring-2 focus:ring-brand-500"; // default - no border
|
|
76
72
|
});
|
|
77
73
|
|
|
78
|
-
// Input classes matching M3 design
|
|
79
74
|
const inputClasses = $derived(() => {
|
|
80
75
|
const sizeStyles = sizeClass();
|
|
81
76
|
const baseClasses =
|
|
@@ -84,12 +79,10 @@
|
|
|
84
79
|
return `${baseClasses} ${sizeStyles.padding} ${sizeStyles.text} ${sizeStyles.leading} ${variantClass()}`.trim();
|
|
85
80
|
});
|
|
86
81
|
|
|
87
|
-
// Label classes using semantic text colors
|
|
88
82
|
const labelClasses = $derived(
|
|
89
83
|
() => "block text-sm font-medium text-label mb-1",
|
|
90
84
|
);
|
|
91
85
|
|
|
92
|
-
// Message classes based on variant
|
|
93
86
|
const messageClasses = $derived(() => {
|
|
94
87
|
if (variant === "error") {
|
|
95
88
|
return "text-error text-sm mt-1 flex items-center gap-1.5";
|
|
@@ -101,7 +94,6 @@
|
|
|
101
94
|
return "text-description text-sm mt-1 flex items-center gap-1.5";
|
|
102
95
|
});
|
|
103
96
|
|
|
104
|
-
// Get icon component based on variant
|
|
105
97
|
const getIcon = $derived(() => {
|
|
106
98
|
if (variant === "error") return AlertCircle;
|
|
107
99
|
if (variant === "success") return CheckCircle;
|
|
@@ -113,7 +105,6 @@
|
|
|
113
105
|
const target = event.target as HTMLInputElement;
|
|
114
106
|
value = target.value;
|
|
115
107
|
|
|
116
|
-
// Call the parent's oninput handler if provided
|
|
117
108
|
if (oninput) {
|
|
118
109
|
oninput(event);
|
|
119
110
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SemanticVariant, SizeVariant } from "../../types/variants.js";
|
|
1
2
|
interface Props {
|
|
2
3
|
value?: string;
|
|
3
4
|
type?: string;
|
|
@@ -5,8 +6,8 @@ interface Props {
|
|
|
5
6
|
label?: string;
|
|
6
7
|
placeholder?: string;
|
|
7
8
|
disabled?: boolean;
|
|
8
|
-
size?:
|
|
9
|
-
variant?:
|
|
9
|
+
size?: SizeVariant;
|
|
10
|
+
variant?: SemanticVariant;
|
|
10
11
|
message?: string;
|
|
11
12
|
oninput?: (event: Event) => void;
|
|
12
13
|
}
|
|
@@ -14,17 +14,14 @@
|
|
|
14
14
|
...restProps
|
|
15
15
|
}: Props = $props();
|
|
16
16
|
|
|
17
|
-
// Generate unique ID - SSR safe
|
|
18
17
|
let progressId = $state(
|
|
19
18
|
typeof window !== "undefined"
|
|
20
19
|
? `progress-${Math.random().toString(36).substr(2, 9)}`
|
|
21
20
|
: `progress-ssr-${Date.now()}`,
|
|
22
21
|
);
|
|
23
22
|
|
|
24
|
-
// Calculate percentage
|
|
25
23
|
let percentage = $derived(Math.min(Math.max((value / max) * 100, 0), 100));
|
|
26
24
|
|
|
27
|
-
// Simple size classes
|
|
28
25
|
let sizeClasses = $derived({
|
|
29
26
|
sm: "h-1",
|
|
30
27
|
md: "h-2",
|
package/dist/atoms/Select.svelte
CHANGED
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
AlertCircle,
|
|
8
8
|
} from "@lucide/svelte";
|
|
9
9
|
|
|
10
|
-
// SSR-safe ID generation
|
|
11
10
|
function generateId(prefix: string = "id"): string {
|
|
12
11
|
if (typeof window !== "undefined") {
|
|
13
12
|
return `${prefix}-${Math.random().toString(36).substr(2, 9)}`;
|
|
@@ -47,7 +46,6 @@
|
|
|
47
46
|
|
|
48
47
|
let isOpen = $state(false);
|
|
49
48
|
|
|
50
|
-
// Size classes matching M3 design specifications
|
|
51
49
|
const sizeClass = $derived(() => {
|
|
52
50
|
if (size === "sm") {
|
|
53
51
|
return {
|
|
@@ -62,7 +60,6 @@
|
|
|
62
60
|
leading: "leading-6",
|
|
63
61
|
};
|
|
64
62
|
} else {
|
|
65
|
-
// default md
|
|
66
63
|
return {
|
|
67
64
|
padding: "px-4 py-2.5",
|
|
68
65
|
text: "text-base",
|
|
@@ -71,7 +68,6 @@
|
|
|
71
68
|
}
|
|
72
69
|
});
|
|
73
70
|
|
|
74
|
-
// Variant classes using semantic colors
|
|
75
71
|
const variantClass = $derived(() => {
|
|
76
72
|
return variant === "success"
|
|
77
73
|
? "border-success focus:border-success focus:ring-success"
|
|
@@ -82,7 +78,6 @@
|
|
|
82
78
|
: "border-0 focus:ring-2 focus:ring-brand-500"; // default - no border
|
|
83
79
|
});
|
|
84
80
|
|
|
85
|
-
// Trigger button classes matching M3 design
|
|
86
81
|
const triggerClasses = $derived(() => {
|
|
87
82
|
const sizeStyles = sizeClass();
|
|
88
83
|
const baseClasses =
|
|
@@ -91,12 +86,10 @@
|
|
|
91
86
|
return `${baseClasses} ${sizeStyles.padding} ${sizeStyles.text} ${sizeStyles.leading} ${variantClass()}`.trim();
|
|
92
87
|
});
|
|
93
88
|
|
|
94
|
-
// Label classes using semantic text colors
|
|
95
89
|
const labelClasses = $derived(
|
|
96
90
|
() => "block text-sm font-medium text-label mb-1",
|
|
97
91
|
);
|
|
98
92
|
|
|
99
|
-
// Message classes based on variant
|
|
100
93
|
const messageClasses = $derived(() => {
|
|
101
94
|
if (variant === "error") {
|
|
102
95
|
return "text-error text-sm mt-1 flex items-center gap-1.5";
|
|
@@ -108,7 +101,6 @@
|
|
|
108
101
|
return "text-description text-sm mt-1 flex items-center gap-1.5";
|
|
109
102
|
});
|
|
110
103
|
|
|
111
|
-
// Get icon component based on variant
|
|
112
104
|
const getIcon = $derived(() => {
|
|
113
105
|
if (variant === "error") return AlertCircle;
|
|
114
106
|
if (variant === "success") return CheckCircle;
|
|
@@ -116,10 +108,8 @@
|
|
|
116
108
|
return null;
|
|
117
109
|
});
|
|
118
110
|
|
|
119
|
-
// Generate unique ID - SSR safe
|
|
120
111
|
const selectId = generateId("select");
|
|
121
112
|
|
|
122
|
-
// Get selected option label
|
|
123
113
|
const selectedLabel = $derived(() => {
|
|
124
114
|
if (isEmpty()) {
|
|
125
115
|
return String(placeholder || "Select an option");
|
|
@@ -130,7 +120,6 @@
|
|
|
130
120
|
: String(placeholder || "Select an option");
|
|
131
121
|
});
|
|
132
122
|
|
|
133
|
-
// Check if value is empty
|
|
134
123
|
const isEmpty = $derived(() => {
|
|
135
124
|
return value === undefined || value === null || value === "";
|
|
136
125
|
});
|
|
@@ -140,7 +129,6 @@
|
|
|
140
129
|
value = optionValue;
|
|
141
130
|
isOpen = false;
|
|
142
131
|
|
|
143
|
-
// Create a synthetic event for onchange
|
|
144
132
|
if (onchange) {
|
|
145
133
|
const syntheticEvent = new Event("change", { bubbles: true });
|
|
146
134
|
Object.defineProperty(syntheticEvent, "target", {
|
|
@@ -157,7 +145,6 @@
|
|
|
157
145
|
isOpen = !isOpen;
|
|
158
146
|
}
|
|
159
147
|
|
|
160
|
-
// Close dropdown when clicking outside
|
|
161
148
|
function handleClickOutside(event: MouseEvent) {
|
|
162
149
|
if (
|
|
163
150
|
isOpen &&
|
|
@@ -167,7 +154,6 @@
|
|
|
167
154
|
}
|
|
168
155
|
}
|
|
169
156
|
|
|
170
|
-
// Handle escape key
|
|
171
157
|
function handleKeydown(event: KeyboardEvent) {
|
|
172
158
|
if (event.key === "Escape" && isOpen) {
|
|
173
159
|
isOpen = false;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { CheckCircle, AlertTriangle, AlertCircle } from "@lucide/svelte";
|
|
3
|
+
import type { SemanticVariant, SizeVariant } from "../../types/variants.js";
|
|
3
4
|
|
|
4
|
-
// SSR-safe ID generation
|
|
5
5
|
function generateId(prefix: string = "id"): string {
|
|
6
6
|
if (typeof window !== "undefined") {
|
|
7
7
|
return `${prefix}-${Math.random().toString(36).substr(2, 9)}`;
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
placeholder?: string;
|
|
18
18
|
disabled?: boolean;
|
|
19
19
|
rows?: number;
|
|
20
|
-
|
|
20
|
+
size?: SizeVariant;
|
|
21
|
+
variant?: SemanticVariant;
|
|
21
22
|
message?: string;
|
|
22
23
|
oninput?: (event: Event) => void;
|
|
23
24
|
}
|
|
@@ -35,10 +36,8 @@
|
|
|
35
36
|
...restProps
|
|
36
37
|
}: Props = $props();
|
|
37
38
|
|
|
38
|
-
// Generate unique ID - SSR safe (call directly, not in $state)
|
|
39
39
|
const textareaId = generateId("textarea");
|
|
40
40
|
|
|
41
|
-
// Variant classes using semantic colors
|
|
42
41
|
const variantClass = $derived(() => {
|
|
43
42
|
return variant === "success"
|
|
44
43
|
? "border-success focus:border-success focus:ring-success"
|
|
@@ -49,7 +48,6 @@
|
|
|
49
48
|
: "border-0 focus:ring-2 focus:ring-brand-500"; // default - no border
|
|
50
49
|
});
|
|
51
50
|
|
|
52
|
-
// Textarea classes matching M3 design
|
|
53
51
|
const textareaClasses = $derived(() => {
|
|
54
52
|
const baseClasses =
|
|
55
53
|
"w-full 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 resize-y px-4 py-2.5 text-base leading-6";
|
|
@@ -57,12 +55,10 @@
|
|
|
57
55
|
return `${baseClasses} ${variantClass()}`.trim();
|
|
58
56
|
});
|
|
59
57
|
|
|
60
|
-
// Label classes using semantic text colors
|
|
61
58
|
const labelClasses = $derived(
|
|
62
59
|
() => "block text-sm font-medium text-label mb-1",
|
|
63
60
|
);
|
|
64
61
|
|
|
65
|
-
// Message classes based on variant
|
|
66
62
|
const messageClasses = $derived(() => {
|
|
67
63
|
if (variant === "error") {
|
|
68
64
|
return "text-error text-sm mt-1 flex items-center gap-1.5";
|
|
@@ -74,7 +70,6 @@
|
|
|
74
70
|
return "text-description text-sm mt-1 flex items-center gap-1.5";
|
|
75
71
|
});
|
|
76
72
|
|
|
77
|
-
// Get icon component based on variant
|
|
78
73
|
const getIcon = $derived(() => {
|
|
79
74
|
if (variant === "error") return AlertCircle;
|
|
80
75
|
if (variant === "success") return CheckCircle;
|
|
@@ -86,7 +81,6 @@
|
|
|
86
81
|
const target = event.target as HTMLTextAreaElement;
|
|
87
82
|
value = target.value;
|
|
88
83
|
|
|
89
|
-
// Call the parent's oninput handler if provided
|
|
90
84
|
if (oninput) {
|
|
91
85
|
oninput(event);
|
|
92
86
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SemanticVariant, SizeVariant } from "../../types/variants.js";
|
|
1
2
|
interface Props {
|
|
2
3
|
value?: string;
|
|
3
4
|
name?: string;
|
|
@@ -5,7 +6,8 @@ interface Props {
|
|
|
5
6
|
placeholder?: string;
|
|
6
7
|
disabled?: boolean;
|
|
7
8
|
rows?: number;
|
|
8
|
-
|
|
9
|
+
size?: SizeVariant;
|
|
10
|
+
variant?: SemanticVariant;
|
|
9
11
|
message?: string;
|
|
10
12
|
oninput?: (event: Event) => void;
|
|
11
13
|
}
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import { onMount } from "svelte";
|
|
3
3
|
import { Sun, Moon } from "@lucide/svelte";
|
|
4
4
|
|
|
5
|
-
// SSR-safe utilities
|
|
6
5
|
function safeLocalStorage(): Storage | undefined {
|
|
7
6
|
return typeof window !== "undefined" ? localStorage : undefined;
|
|
8
7
|
}
|
|
@@ -28,7 +27,6 @@
|
|
|
28
27
|
|
|
29
28
|
onMount(() => {
|
|
30
29
|
mounted = true;
|
|
31
|
-
// Check for saved theme preference or default to light mode
|
|
32
30
|
const storage = safeLocalStorage();
|
|
33
31
|
if (storage) {
|
|
34
32
|
const savedTheme = storage.getItem("theme");
|
|
@@ -71,7 +69,6 @@
|
|
|
71
69
|
}
|
|
72
70
|
}
|
|
73
71
|
|
|
74
|
-
// Size classes matching M3 design
|
|
75
72
|
const sizeClass = $derived(() => {
|
|
76
73
|
if (size === "sm") {
|
|
77
74
|
return {
|
|
@@ -84,7 +81,6 @@
|
|
|
84
81
|
icon: 24
|
|
85
82
|
};
|
|
86
83
|
} else {
|
|
87
|
-
// default md
|
|
88
84
|
return {
|
|
89
85
|
button: "w-10 h-10",
|
|
90
86
|
icon: 20
|
|
@@ -92,14 +88,12 @@
|
|
|
92
88
|
}
|
|
93
89
|
});
|
|
94
90
|
|
|
95
|
-
// Variant classes using semantic colors
|
|
96
91
|
const variantClass = $derived(() => {
|
|
97
92
|
if (variant === "ghost") {
|
|
98
93
|
return "bg-transparent hover:bg-base-100 border-0";
|
|
99
94
|
} else if (variant === "outline") {
|
|
100
95
|
return "bg-base-50 hover:bg-base-100 border border-border";
|
|
101
96
|
} else {
|
|
102
|
-
// default
|
|
103
97
|
return "bg-base-50 hover:bg-base-100 border-0";
|
|
104
98
|
}
|
|
105
99
|
});
|
|
@@ -141,7 +135,6 @@
|
|
|
141
135
|
{/if}
|
|
142
136
|
</button>
|
|
143
137
|
{:else}
|
|
144
|
-
<!-- SSR fallback -->
|
|
145
138
|
<button
|
|
146
139
|
class="w-10 h-10 bg-base-50 rounded-lg flex items-center justify-center text-label cursor-pointer"
|
|
147
140
|
aria-label="Theme toggle"
|