@classic-homes/theme-svelte 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +305 -0
- package/dist/lib/components/Alert.svelte +51 -0
- package/dist/lib/components/Alert.svelte.d.ts +9 -0
- package/dist/lib/components/AlertDescription.svelte +16 -0
- package/dist/lib/components/AlertDescription.svelte.d.ts +9 -0
- package/dist/lib/components/AlertDialog.svelte +136 -0
- package/dist/lib/components/AlertDialog.svelte.d.ts +79 -0
- package/dist/lib/components/AlertTitle.svelte +16 -0
- package/dist/lib/components/AlertTitle.svelte.d.ts +9 -0
- package/dist/lib/components/Avatar.svelte +56 -0
- package/dist/lib/components/Avatar.svelte.d.ts +26 -0
- package/dist/lib/components/AvatarFallback.svelte +31 -0
- package/dist/lib/components/AvatarFallback.svelte.d.ts +17 -0
- package/dist/lib/components/AvatarImage.svelte +29 -0
- package/dist/lib/components/AvatarImage.svelte.d.ts +12 -0
- package/dist/lib/components/Badge.svelte +73 -0
- package/dist/lib/components/Badge.svelte.d.ts +11 -0
- package/dist/lib/components/Button.svelte +130 -0
- package/dist/lib/components/Button.svelte.d.ts +17 -0
- package/dist/lib/components/Card.svelte +58 -0
- package/dist/lib/components/Card.svelte.d.ts +26 -0
- package/dist/lib/components/CardContent.svelte +16 -0
- package/dist/lib/components/CardContent.svelte.d.ts +9 -0
- package/dist/lib/components/CardDescription.svelte +16 -0
- package/dist/lib/components/CardDescription.svelte.d.ts +9 -0
- package/dist/lib/components/CardFooter.svelte +16 -0
- package/dist/lib/components/CardFooter.svelte.d.ts +9 -0
- package/dist/lib/components/CardHeader.svelte +16 -0
- package/dist/lib/components/CardHeader.svelte.d.ts +9 -0
- package/dist/lib/components/CardTitle.svelte +16 -0
- package/dist/lib/components/CardTitle.svelte.d.ts +9 -0
- package/dist/lib/components/Checkbox.svelte +65 -0
- package/dist/lib/components/Checkbox.svelte.d.ts +14 -0
- package/dist/lib/components/DataTable.svelte +334 -0
- package/dist/lib/components/DataTable.svelte.d.ts +103 -0
- package/dist/lib/components/Dialog.svelte +111 -0
- package/dist/lib/components/Dialog.svelte.d.ts +22 -0
- package/dist/lib/components/DropdownMenu.svelte +135 -0
- package/dist/lib/components/DropdownMenu.svelte.d.ts +33 -0
- package/dist/lib/components/FileUpload.svelte +448 -0
- package/dist/lib/components/FileUpload.svelte.d.ts +42 -0
- package/dist/lib/components/FormField.svelte +134 -0
- package/dist/lib/components/FormField.svelte.d.ts +37 -0
- package/dist/lib/components/Input.svelte +61 -0
- package/dist/lib/components/Input.svelte.d.ts +19 -0
- package/dist/lib/components/Label.svelte +33 -0
- package/dist/lib/components/Label.svelte.d.ts +11 -0
- package/dist/lib/components/LoadingLogo.svelte +124 -0
- package/dist/lib/components/LoadingLogo.svelte.d.ts +16 -0
- package/dist/lib/components/LogoMain.svelte +237 -0
- package/dist/lib/components/LogoMain.svelte.d.ts +20 -0
- package/dist/lib/components/PageHeader.svelte +90 -0
- package/dist/lib/components/PageHeader.svelte.d.ts +28 -0
- package/dist/lib/components/Section.svelte +44 -0
- package/dist/lib/components/Section.svelte.d.ts +28 -0
- package/dist/lib/components/Select.svelte +174 -0
- package/dist/lib/components/Select.svelte.d.ts +32 -0
- package/dist/lib/components/Separator.svelte +29 -0
- package/dist/lib/components/Separator.svelte.d.ts +9 -0
- package/dist/lib/components/Skeleton.svelte +35 -0
- package/dist/lib/components/Skeleton.svelte.d.ts +7 -0
- package/dist/lib/components/Spinner.svelte +50 -0
- package/dist/lib/components/Spinner.svelte.d.ts +8 -0
- package/dist/lib/components/Switch.svelte +56 -0
- package/dist/lib/components/Switch.svelte.d.ts +14 -0
- package/dist/lib/components/TabPanel.svelte +44 -0
- package/dist/lib/components/TabPanel.svelte.d.ts +12 -0
- package/dist/lib/components/Tabs.svelte +125 -0
- package/dist/lib/components/Tabs.svelte.d.ts +19 -0
- package/dist/lib/components/Textarea.svelte +54 -0
- package/dist/lib/components/Textarea.svelte.d.ts +16 -0
- package/dist/lib/components/Toast.svelte +116 -0
- package/dist/lib/components/Toast.svelte.d.ts +12 -0
- package/dist/lib/components/ToastContainer.svelte +56 -0
- package/dist/lib/components/ToastContainer.svelte.d.ts +8 -0
- package/dist/lib/components/Tooltip.svelte +55 -0
- package/dist/lib/components/Tooltip.svelte.d.ts +18 -0
- package/dist/lib/components/layout/AppShell.svelte +82 -0
- package/dist/lib/components/layout/AppShell.svelte.d.ts +44 -0
- package/dist/lib/components/layout/DashboardLayout.svelte +248 -0
- package/dist/lib/components/layout/DashboardLayout.svelte.d.ts +62 -0
- package/dist/lib/components/layout/Footer.svelte +130 -0
- package/dist/lib/components/layout/Footer.svelte.d.ts +32 -0
- package/dist/lib/components/layout/FormPageLayout.svelte +92 -0
- package/dist/lib/components/layout/FormPageLayout.svelte.d.ts +33 -0
- package/dist/lib/components/layout/Header.svelte +94 -0
- package/dist/lib/components/layout/Header.svelte.d.ts +30 -0
- package/dist/lib/components/layout/PublicLayout.svelte +180 -0
- package/dist/lib/components/layout/PublicLayout.svelte.d.ts +39 -0
- package/dist/lib/components/layout/QuickLinks.svelte +112 -0
- package/dist/lib/components/layout/QuickLinks.svelte.d.ts +27 -0
- package/dist/lib/components/layout/Sidebar.svelte +243 -0
- package/dist/lib/components/layout/Sidebar.svelte.d.ts +48 -0
- package/dist/lib/composables/index.d.ts +8 -0
- package/dist/lib/composables/index.js +10 -0
- package/dist/lib/composables/useAsync.svelte.d.ts +102 -0
- package/dist/lib/composables/useAsync.svelte.js +210 -0
- package/dist/lib/composables/useForm.svelte.d.ts +123 -0
- package/dist/lib/composables/useForm.svelte.js +245 -0
- package/dist/lib/index.d.ts +65 -0
- package/dist/lib/index.js +83 -0
- package/dist/lib/performance.d.ts +79 -0
- package/dist/lib/performance.js +170 -0
- package/dist/lib/schemas/auth.d.ts +410 -0
- package/dist/lib/schemas/auth.js +216 -0
- package/dist/lib/schemas/common.d.ts +267 -0
- package/dist/lib/schemas/common.js +268 -0
- package/dist/lib/schemas/index.d.ts +24 -0
- package/dist/lib/schemas/index.js +32 -0
- package/dist/lib/stores/sidebar.svelte.d.ts +25 -0
- package/dist/lib/stores/sidebar.svelte.js +38 -0
- package/dist/lib/stores/theme.svelte.d.ts +72 -0
- package/dist/lib/stores/theme.svelte.js +150 -0
- package/dist/lib/stores/toast.svelte.d.ts +62 -0
- package/dist/lib/stores/toast.svelte.js +93 -0
- package/dist/lib/types/components.d.ts +85 -0
- package/dist/lib/types/components.js +7 -0
- package/dist/lib/types/layout.d.ts +258 -0
- package/dist/lib/types/layout.js +7 -0
- package/dist/lib/utils.d.ts +6 -0
- package/dist/lib/utils.js +9 -0
- package/dist/lib/validation.d.ts +101 -0
- package/dist/lib/validation.js +170 -0
- package/package.json +56 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn } from '../utils.js';
|
|
3
|
+
import Label from './Label.svelte';
|
|
4
|
+
import Input from './Input.svelte';
|
|
5
|
+
import Textarea from './Textarea.svelte';
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
/** Label text */
|
|
9
|
+
label: string;
|
|
10
|
+
/** Input ID for association */
|
|
11
|
+
id: string;
|
|
12
|
+
/** Input type (text, email, password, tel, url, number, textarea) */
|
|
13
|
+
type?: 'text' | 'email' | 'password' | 'tel' | 'url' | 'number' | 'textarea';
|
|
14
|
+
/** Field value (bindable) */
|
|
15
|
+
value?: string;
|
|
16
|
+
/** Error message */
|
|
17
|
+
error?: string;
|
|
18
|
+
/** Help text / hint */
|
|
19
|
+
hint?: string;
|
|
20
|
+
/** Disabled state */
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
/** Required field */
|
|
23
|
+
required?: boolean;
|
|
24
|
+
/** Placeholder text */
|
|
25
|
+
placeholder?: string;
|
|
26
|
+
/** Autocomplete attribute */
|
|
27
|
+
autocomplete?: AutoFill;
|
|
28
|
+
/** Name attribute */
|
|
29
|
+
name?: string;
|
|
30
|
+
/** Number of rows for textarea */
|
|
31
|
+
rows?: number;
|
|
32
|
+
/** Readonly state */
|
|
33
|
+
readonly?: boolean;
|
|
34
|
+
/** Additional class for container */
|
|
35
|
+
class?: string;
|
|
36
|
+
/** Callback when value changes */
|
|
37
|
+
onValueChange?: (value: string) => void;
|
|
38
|
+
/** Additional props */
|
|
39
|
+
[key: string]: unknown;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let {
|
|
43
|
+
label,
|
|
44
|
+
id,
|
|
45
|
+
type = 'text',
|
|
46
|
+
value = $bindable(''),
|
|
47
|
+
error,
|
|
48
|
+
hint,
|
|
49
|
+
disabled = false,
|
|
50
|
+
required = false,
|
|
51
|
+
placeholder,
|
|
52
|
+
autocomplete,
|
|
53
|
+
name,
|
|
54
|
+
rows = 3,
|
|
55
|
+
readonly = false,
|
|
56
|
+
class: className,
|
|
57
|
+
onValueChange,
|
|
58
|
+
...restProps
|
|
59
|
+
}: Props = $props();
|
|
60
|
+
|
|
61
|
+
// Generate IDs for aria associations (derived to react to id changes)
|
|
62
|
+
const errorId = $derived(`${id}-error`);
|
|
63
|
+
const hintId = $derived(`${id}-hint`);
|
|
64
|
+
|
|
65
|
+
// Compute aria-describedby based on error/hint presence
|
|
66
|
+
// Error takes precedence over hint
|
|
67
|
+
const ariaDescribedBy = $derived.by(() => {
|
|
68
|
+
if (error) return errorId;
|
|
69
|
+
if (hint) return hintId;
|
|
70
|
+
return undefined;
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Input styling with error state
|
|
74
|
+
const inputClasses = $derived(error ? 'border-destructive focus-visible:ring-destructive' : '');
|
|
75
|
+
</script>
|
|
76
|
+
|
|
77
|
+
<div class={cn('space-y-2', className)}>
|
|
78
|
+
<!-- Label with required indicator -->
|
|
79
|
+
<Label for={id} {disabled}>
|
|
80
|
+
{label}
|
|
81
|
+
{#if required}
|
|
82
|
+
<span class="text-destructive ml-0.5" aria-hidden="true">*</span>
|
|
83
|
+
<span class="sr-only">(required)</span>
|
|
84
|
+
{/if}
|
|
85
|
+
</Label>
|
|
86
|
+
|
|
87
|
+
<!-- Input or Textarea based on type -->
|
|
88
|
+
{#if type === 'textarea'}
|
|
89
|
+
<Textarea
|
|
90
|
+
{id}
|
|
91
|
+
bind:value
|
|
92
|
+
{placeholder}
|
|
93
|
+
{disabled}
|
|
94
|
+
{readonly}
|
|
95
|
+
{required}
|
|
96
|
+
{name}
|
|
97
|
+
{rows}
|
|
98
|
+
{onValueChange}
|
|
99
|
+
aria-invalid={error ? 'true' : undefined}
|
|
100
|
+
aria-describedby={ariaDescribedBy}
|
|
101
|
+
class={inputClasses}
|
|
102
|
+
{...restProps}
|
|
103
|
+
/>
|
|
104
|
+
{:else}
|
|
105
|
+
<Input
|
|
106
|
+
{id}
|
|
107
|
+
{type}
|
|
108
|
+
bind:value
|
|
109
|
+
{placeholder}
|
|
110
|
+
{disabled}
|
|
111
|
+
{readonly}
|
|
112
|
+
{required}
|
|
113
|
+
{name}
|
|
114
|
+
{autocomplete}
|
|
115
|
+
{onValueChange}
|
|
116
|
+
aria-invalid={error ? 'true' : undefined}
|
|
117
|
+
aria-describedby={ariaDescribedBy}
|
|
118
|
+
class={inputClasses}
|
|
119
|
+
{...restProps}
|
|
120
|
+
/>
|
|
121
|
+
{/if}
|
|
122
|
+
|
|
123
|
+
<!-- Error message (takes precedence over hint) -->
|
|
124
|
+
{#if error}
|
|
125
|
+
<p id={errorId} class="text-sm text-destructive" role="alert" aria-live="polite">
|
|
126
|
+
{error}
|
|
127
|
+
</p>
|
|
128
|
+
{:else if hint}
|
|
129
|
+
<!-- Hint text -->
|
|
130
|
+
<p id={hintId} class="text-sm text-muted-foreground">
|
|
131
|
+
{hint}
|
|
132
|
+
</p>
|
|
133
|
+
{/if}
|
|
134
|
+
</div>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
/** Label text */
|
|
3
|
+
label: string;
|
|
4
|
+
/** Input ID for association */
|
|
5
|
+
id: string;
|
|
6
|
+
/** Input type (text, email, password, tel, url, number, textarea) */
|
|
7
|
+
type?: 'text' | 'email' | 'password' | 'tel' | 'url' | 'number' | 'textarea';
|
|
8
|
+
/** Field value (bindable) */
|
|
9
|
+
value?: string;
|
|
10
|
+
/** Error message */
|
|
11
|
+
error?: string;
|
|
12
|
+
/** Help text / hint */
|
|
13
|
+
hint?: string;
|
|
14
|
+
/** Disabled state */
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
/** Required field */
|
|
17
|
+
required?: boolean;
|
|
18
|
+
/** Placeholder text */
|
|
19
|
+
placeholder?: string;
|
|
20
|
+
/** Autocomplete attribute */
|
|
21
|
+
autocomplete?: AutoFill;
|
|
22
|
+
/** Name attribute */
|
|
23
|
+
name?: string;
|
|
24
|
+
/** Number of rows for textarea */
|
|
25
|
+
rows?: number;
|
|
26
|
+
/** Readonly state */
|
|
27
|
+
readonly?: boolean;
|
|
28
|
+
/** Additional class for container */
|
|
29
|
+
class?: string;
|
|
30
|
+
/** Callback when value changes */
|
|
31
|
+
onValueChange?: (value: string) => void;
|
|
32
|
+
/** Additional props */
|
|
33
|
+
[key: string]: unknown;
|
|
34
|
+
}
|
|
35
|
+
declare const FormField: import("svelte").Component<Props, {}, "value">;
|
|
36
|
+
type FormField = ReturnType<typeof FormField>;
|
|
37
|
+
export default FormField;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn } from '../utils.js';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
type?: string;
|
|
6
|
+
value?: string | number;
|
|
7
|
+
placeholder?: string;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
readonly?: boolean;
|
|
10
|
+
required?: boolean;
|
|
11
|
+
name?: string;
|
|
12
|
+
id?: string;
|
|
13
|
+
autocomplete?: AutoFill;
|
|
14
|
+
/** Input mode hint for mobile keyboards */
|
|
15
|
+
inputmode?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search';
|
|
16
|
+
class?: string;
|
|
17
|
+
onValueChange?: (value: string) => void;
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let {
|
|
22
|
+
type = 'text',
|
|
23
|
+
value = $bindable(''),
|
|
24
|
+
placeholder,
|
|
25
|
+
disabled = false,
|
|
26
|
+
readonly = false,
|
|
27
|
+
required = false,
|
|
28
|
+
name,
|
|
29
|
+
id,
|
|
30
|
+
autocomplete,
|
|
31
|
+
inputmode,
|
|
32
|
+
class: className,
|
|
33
|
+
onValueChange,
|
|
34
|
+
...restProps
|
|
35
|
+
}: Props = $props();
|
|
36
|
+
|
|
37
|
+
function handleInput(e: Event) {
|
|
38
|
+
const target = e.target as HTMLInputElement;
|
|
39
|
+
value = target.value;
|
|
40
|
+
onValueChange?.(target.value);
|
|
41
|
+
}
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<input
|
|
45
|
+
{type}
|
|
46
|
+
{value}
|
|
47
|
+
{placeholder}
|
|
48
|
+
{disabled}
|
|
49
|
+
{readonly}
|
|
50
|
+
{required}
|
|
51
|
+
{name}
|
|
52
|
+
{id}
|
|
53
|
+
{autocomplete}
|
|
54
|
+
{inputmode}
|
|
55
|
+
class={cn(
|
|
56
|
+
'flex h-11 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
|
57
|
+
className
|
|
58
|
+
)}
|
|
59
|
+
oninput={handleInput}
|
|
60
|
+
{...restProps}
|
|
61
|
+
/>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
type?: string;
|
|
3
|
+
value?: string | number;
|
|
4
|
+
placeholder?: string;
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
readonly?: boolean;
|
|
7
|
+
required?: boolean;
|
|
8
|
+
name?: string;
|
|
9
|
+
id?: string;
|
|
10
|
+
autocomplete?: AutoFill;
|
|
11
|
+
/** Input mode hint for mobile keyboards */
|
|
12
|
+
inputmode?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search';
|
|
13
|
+
class?: string;
|
|
14
|
+
onValueChange?: (value: string) => void;
|
|
15
|
+
[key: string]: unknown;
|
|
16
|
+
}
|
|
17
|
+
declare const Input: import("svelte").Component<Props, {}, "value">;
|
|
18
|
+
type Input = ReturnType<typeof Input>;
|
|
19
|
+
export default Input;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { Label as LabelPrimitive } from 'bits-ui';
|
|
4
|
+
import { cn } from '../utils.js';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
for?: string;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
class?: string;
|
|
10
|
+
children: Snippet;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let {
|
|
15
|
+
for: htmlFor,
|
|
16
|
+
disabled = false,
|
|
17
|
+
class: className,
|
|
18
|
+
children,
|
|
19
|
+
...restProps
|
|
20
|
+
}: Props = $props();
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<LabelPrimitive.Root
|
|
24
|
+
for={htmlFor}
|
|
25
|
+
class={cn(
|
|
26
|
+
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-gray-700 dark:text-gray-300',
|
|
27
|
+
disabled && 'opacity-50 cursor-not-allowed',
|
|
28
|
+
className
|
|
29
|
+
)}
|
|
30
|
+
{...restProps}
|
|
31
|
+
>
|
|
32
|
+
{@render children()}
|
|
33
|
+
</LabelPrimitive.Root>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface Props {
|
|
3
|
+
for?: string;
|
|
4
|
+
disabled?: boolean;
|
|
5
|
+
class?: string;
|
|
6
|
+
children: Snippet;
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
}
|
|
9
|
+
declare const Label: import("svelte").Component<Props, {}, "">;
|
|
10
|
+
type Label = ReturnType<typeof Label>;
|
|
11
|
+
export default Label;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn } from '../utils.js';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
/** Width in pixels */
|
|
6
|
+
width?: number;
|
|
7
|
+
/** Height in pixels */
|
|
8
|
+
height?: number;
|
|
9
|
+
/** Enables loading animation */
|
|
10
|
+
loading?: boolean;
|
|
11
|
+
/** Color variant for different backgrounds */
|
|
12
|
+
variant?: 'light' | 'dark';
|
|
13
|
+
/** Additional CSS classes */
|
|
14
|
+
class?: string;
|
|
15
|
+
[key: string]: unknown;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let {
|
|
19
|
+
width = 40,
|
|
20
|
+
height = 40,
|
|
21
|
+
loading = false,
|
|
22
|
+
variant = 'dark',
|
|
23
|
+
class: className,
|
|
24
|
+
...restProps
|
|
25
|
+
}: Props = $props();
|
|
26
|
+
|
|
27
|
+
// Color definitions
|
|
28
|
+
const colors = {
|
|
29
|
+
dark: {
|
|
30
|
+
fill: '#C50F22', // Brand red
|
|
31
|
+
fillLoading: '#ffffff', // White when loading
|
|
32
|
+
stroke: '#C50F22', // Red stroke when loading
|
|
33
|
+
},
|
|
34
|
+
light: {
|
|
35
|
+
fill: '#ffffff', // White
|
|
36
|
+
fillLoading: '#C50F22', // Red when loading
|
|
37
|
+
stroke: '#ffffff', // White stroke when loading
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const currentColors = $derived(colors[variant]);
|
|
42
|
+
const fillColor = $derived(loading ? currentColors.fillLoading : currentColors.fill);
|
|
43
|
+
const strokeColor = $derived(currentColors.stroke);
|
|
44
|
+
|
|
45
|
+
// SVG paths for the Classic Homes floral/rose logo
|
|
46
|
+
const logoPaths = [
|
|
47
|
+
// Left leaf/petal
|
|
48
|
+
'M9.902 16.6c.3.7 1.7.8 1-.6-.6-1.4-2.3-5.1-6.1-6.3-1.7-.5-3.7.4-4.3 1.6-.4.7-1.1 2.9.5 4.3.8.7 1.9 1.2 3.6 1 .4-.1.9-.5.6-.8-.5-.4-1.9-1.7-1.1-2.7.7-.8 1.4-.6 2-.3.4.2 2.1 1 3.7 3.6l.1.2z',
|
|
49
|
+
// Right leaf/petal
|
|
50
|
+
'M14.602 16.6c-.3.7-1.7.8-1-.6.6-1.4 2.3-5.1 6.1-6.3 1.7-.5 3.7.4 4.3 1.6.4.7 1.1 2.9-.5 4.3-.8.7-1.9 1.2-3.6 1-.4-.1-.9-.5-.6-.8.5-.4 1.9-1.7 1.1-2.7-.7-.8-1.4-.6-2-.3-.4.2-2.1 1-3.7 3.6l-.1.2z',
|
|
51
|
+
// Center stem/bud
|
|
52
|
+
'M12.202 0c.7 1.3 2.1 3.2 2.3 5.3.1 1.5-.8 8.3-2.3 11.7-1.5-3.5-2.4-10.3-2.3-11.7.2-2.1 1.6-4 2.3-5.3z',
|
|
53
|
+
// Base bar
|
|
54
|
+
'M9.002 18.1c-.8 0-.7.8-.1.8h6.7c.7 0 .7-.8-.1-.8h-6.5z',
|
|
55
|
+
// Pot/vase center
|
|
56
|
+
'M13.802 24.8c-1.1-1.1-.9-4.6-.9-4.6h-1.3s.2 3.6-.9 4.6h3.1z',
|
|
57
|
+
// Pot/vase right detail
|
|
58
|
+
'M14.202 22.5c-.1-.3-.5-1.4-.6-2.3 0-.2.6-.5.7.1.1.6.4 1.6.6 1.9.3.5-.4.9-.7.3z',
|
|
59
|
+
// Pot/vase left detail
|
|
60
|
+
'M10.302 22.5c.1-.3.5-1.4.6-2.2 0-.3-.5-.6-.7 0-.1.6-.4 1.6-.6 1.9-.3.5.4.9.7.3z',
|
|
61
|
+
];
|
|
62
|
+
</script>
|
|
63
|
+
|
|
64
|
+
<div
|
|
65
|
+
class={cn('inline-flex items-center justify-center', className)}
|
|
66
|
+
style="width: {width}px; height: {height}px;"
|
|
67
|
+
role={loading ? 'status' : 'img'}
|
|
68
|
+
aria-label={loading ? 'Loading' : 'Classic Homes'}
|
|
69
|
+
{...restProps}
|
|
70
|
+
>
|
|
71
|
+
<svg
|
|
72
|
+
width="100%"
|
|
73
|
+
height="100%"
|
|
74
|
+
viewBox="-1 0 26 25"
|
|
75
|
+
fill="none"
|
|
76
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
77
|
+
aria-hidden="true"
|
|
78
|
+
class={cn(loading && 'loading-logo-animated')}
|
|
79
|
+
>
|
|
80
|
+
<!-- Main filled paths -->
|
|
81
|
+
<g fill={fillColor} class="transition-all duration-300">
|
|
82
|
+
{#each logoPaths as path}
|
|
83
|
+
<path d={path} />
|
|
84
|
+
{/each}
|
|
85
|
+
</g>
|
|
86
|
+
|
|
87
|
+
<!-- Tracing stroke overlay (only visible when loading) -->
|
|
88
|
+
{#if loading}
|
|
89
|
+
<g
|
|
90
|
+
fill="none"
|
|
91
|
+
stroke={strokeColor}
|
|
92
|
+
stroke-width="0.5"
|
|
93
|
+
stroke-dasharray="10 4"
|
|
94
|
+
class="loading-logo-trace"
|
|
95
|
+
>
|
|
96
|
+
{#each logoPaths as path}
|
|
97
|
+
<path d={path} />
|
|
98
|
+
{/each}
|
|
99
|
+
</g>
|
|
100
|
+
{/if}
|
|
101
|
+
</svg>
|
|
102
|
+
|
|
103
|
+
{#if loading}
|
|
104
|
+
<span class="sr-only">Loading</span>
|
|
105
|
+
{/if}
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
<style>
|
|
109
|
+
@keyframes trace {
|
|
110
|
+
to {
|
|
111
|
+
stroke-dashoffset: -14;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.loading-logo-trace {
|
|
116
|
+
animation: trace 2s linear infinite;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
@media (prefers-reduced-motion: reduce) {
|
|
120
|
+
.loading-logo-trace {
|
|
121
|
+
animation: none;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
</style>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
/** Width in pixels */
|
|
3
|
+
width?: number;
|
|
4
|
+
/** Height in pixels */
|
|
5
|
+
height?: number;
|
|
6
|
+
/** Enables loading animation */
|
|
7
|
+
loading?: boolean;
|
|
8
|
+
/** Color variant for different backgrounds */
|
|
9
|
+
variant?: 'light' | 'dark';
|
|
10
|
+
/** Additional CSS classes */
|
|
11
|
+
class?: string;
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
}
|
|
14
|
+
declare const LoadingLogo: import("svelte").Component<Props, {}, "">;
|
|
15
|
+
type LoadingLogo = ReturnType<typeof LoadingLogo>;
|
|
16
|
+
export default LoadingLogo;
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn } from '../utils.js';
|
|
3
|
+
import Badge from './Badge.svelte';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
/** Layout variant */
|
|
7
|
+
variant?: 'horizontal' | 'stacked' | 'icon';
|
|
8
|
+
/** Color mode for different backgrounds */
|
|
9
|
+
color?: 'dark' | 'light' | 'mono-dark' | 'mono-light';
|
|
10
|
+
/** Optional subtitle (e.g., "CHAPI", "MY HOME") */
|
|
11
|
+
subtitle?: string;
|
|
12
|
+
/** Environment indicator (LOCAL, DEV, DEMO) */
|
|
13
|
+
environment?: 'local' | 'dev' | 'demo';
|
|
14
|
+
/** Size preset */
|
|
15
|
+
size?: 'sm' | 'md' | 'lg';
|
|
16
|
+
/** Additional CSS classes */
|
|
17
|
+
class?: string;
|
|
18
|
+
/** Accessible label - set to empty string if wrapped in a link with its own label */
|
|
19
|
+
ariaLabel?: string;
|
|
20
|
+
[key: string]: unknown;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let {
|
|
24
|
+
variant = 'horizontal',
|
|
25
|
+
color = 'dark',
|
|
26
|
+
subtitle,
|
|
27
|
+
environment,
|
|
28
|
+
size = 'md',
|
|
29
|
+
class: className,
|
|
30
|
+
ariaLabel = 'Classic Homes',
|
|
31
|
+
...restProps
|
|
32
|
+
}: Props = $props();
|
|
33
|
+
|
|
34
|
+
// Size configurations
|
|
35
|
+
const sizes = {
|
|
36
|
+
sm: { icon: 24, title: '1rem', subtitle: '0.7rem', badge: '0.5rem', gap: '0.375rem' },
|
|
37
|
+
md: { icon: 32, title: '1.2rem', subtitle: '0.85rem', badge: '0.6rem', gap: '0.5rem' },
|
|
38
|
+
lg: { icon: 40, title: '1.4rem', subtitle: '1rem', badge: '0.7rem', gap: '0.5rem' },
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Environment badge configuration
|
|
42
|
+
const environmentConfig = {
|
|
43
|
+
local: { label: 'LOCAL', variant: 'info' as const },
|
|
44
|
+
dev: { label: 'DEV', variant: 'warning' as const },
|
|
45
|
+
demo: { label: 'DEMO', variant: 'secondary' as const },
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const envConfig = $derived(environment ? environmentConfig[environment] : null);
|
|
49
|
+
|
|
50
|
+
const currentSize = $derived(sizes[size]);
|
|
51
|
+
|
|
52
|
+
// Color definitions based on brand guidelines
|
|
53
|
+
const colors = {
|
|
54
|
+
dark: {
|
|
55
|
+
logo: '#C50F22', // Brand red
|
|
56
|
+
title: '#000000', // Black for light backgrounds
|
|
57
|
+
subtitle: '#7a7a7a', // Gray
|
|
58
|
+
},
|
|
59
|
+
light: {
|
|
60
|
+
logo: '#ffffff',
|
|
61
|
+
title: '#ffffff',
|
|
62
|
+
subtitle: 'rgba(255, 255, 255, 0.8)',
|
|
63
|
+
},
|
|
64
|
+
'mono-dark': {
|
|
65
|
+
logo: '#002e3f', // Navy
|
|
66
|
+
title: '#002e3f',
|
|
67
|
+
subtitle: '#7a7a7a',
|
|
68
|
+
},
|
|
69
|
+
'mono-light': {
|
|
70
|
+
logo: '#ffffff',
|
|
71
|
+
title: '#ffffff',
|
|
72
|
+
subtitle: 'rgba(255, 255, 255, 0.8)',
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const currentColors = $derived(colors[color]);
|
|
77
|
+
|
|
78
|
+
// SVG paths for the Classic Homes floral/rose logo
|
|
79
|
+
const logoPaths = [
|
|
80
|
+
// Left leaf/petal
|
|
81
|
+
'M9.902 16.6c.3.7 1.7.8 1-.6-.6-1.4-2.3-5.1-6.1-6.3-1.7-.5-3.7.4-4.3 1.6-.4.7-1.1 2.9.5 4.3.8.7 1.9 1.2 3.6 1 .4-.1.9-.5.6-.8-.5-.4-1.9-1.7-1.1-2.7.7-.8 1.4-.6 2-.3.4.2 2.1 1 3.7 3.6l.1.2z',
|
|
82
|
+
// Right leaf/petal
|
|
83
|
+
'M14.602 16.6c-.3.7-1.7.8-1-.6.6-1.4 2.3-5.1 6.1-6.3 1.7-.5 3.7.4 4.3 1.6.4.7 1.1 2.9-.5 4.3-.8.7-1.9 1.2-3.6 1-.4-.1-.9-.5-.6-.8.5-.4 1.9-1.7 1.1-2.7-.7-.8-1.4-.6-2-.3-.4.2-2.1 1-3.7 3.6l-.1.2z',
|
|
84
|
+
// Center stem/bud
|
|
85
|
+
'M12.202 0c.7 1.3 2.1 3.2 2.3 5.3.1 1.5-.8 8.3-2.3 11.7-1.5-3.5-2.4-10.3-2.3-11.7.2-2.1 1.6-4 2.3-5.3z',
|
|
86
|
+
// Base bar
|
|
87
|
+
'M9.002 18.1c-.8 0-.7.8-.1.8h6.7c.7 0 .7-.8-.1-.8h-6.5z',
|
|
88
|
+
// Pot/vase center
|
|
89
|
+
'M13.802 24.8c-1.1-1.1-.9-4.6-.9-4.6h-1.3s.2 3.6-.9 4.6h3.1z',
|
|
90
|
+
// Pot/vase right detail
|
|
91
|
+
'M14.202 22.5c-.1-.3-.5-1.4-.6-2.3 0-.2.6-.5.7.1.1.6.4 1.6.6 1.9.3.5-.4.9-.7.3z',
|
|
92
|
+
// Pot/vase left detail
|
|
93
|
+
'M10.302 22.5c.1-.3.5-1.4.6-2.2 0-.3-.5-.6-.7 0-.1.6-.4 1.6-.6 1.9-.3.5.4.9.7.3z',
|
|
94
|
+
];
|
|
95
|
+
</script>
|
|
96
|
+
|
|
97
|
+
{#if variant === 'icon'}
|
|
98
|
+
<!-- Icon only -->
|
|
99
|
+
<div
|
|
100
|
+
class={cn('shrink-0', className)}
|
|
101
|
+
style="width: {currentSize.icon}px; height: {currentSize.icon}px;"
|
|
102
|
+
role={ariaLabel ? 'img' : undefined}
|
|
103
|
+
aria-label={ariaLabel || undefined}
|
|
104
|
+
{...restProps}
|
|
105
|
+
>
|
|
106
|
+
<svg
|
|
107
|
+
width="100%"
|
|
108
|
+
height="100%"
|
|
109
|
+
viewBox="0 0 25 25"
|
|
110
|
+
fill="none"
|
|
111
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
112
|
+
aria-hidden="true"
|
|
113
|
+
>
|
|
114
|
+
<g fill={currentColors.logo}>
|
|
115
|
+
{#each logoPaths as path}
|
|
116
|
+
<path d={path} />
|
|
117
|
+
{/each}
|
|
118
|
+
</g>
|
|
119
|
+
</svg>
|
|
120
|
+
</div>
|
|
121
|
+
{:else if variant === 'stacked'}
|
|
122
|
+
<!-- Stacked layout (icon above text) -->
|
|
123
|
+
<div
|
|
124
|
+
class={cn('flex flex-col items-center justify-start shrink-0', className)}
|
|
125
|
+
style="gap: {currentSize.gap};"
|
|
126
|
+
role={ariaLabel ? 'img' : undefined}
|
|
127
|
+
aria-label={ariaLabel || undefined}
|
|
128
|
+
{...restProps}
|
|
129
|
+
>
|
|
130
|
+
<div style="width: {currentSize.icon}px; height: {currentSize.icon}px;">
|
|
131
|
+
<svg
|
|
132
|
+
width="100%"
|
|
133
|
+
height="100%"
|
|
134
|
+
viewBox="0 0 25 25"
|
|
135
|
+
fill="none"
|
|
136
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
137
|
+
aria-hidden="true"
|
|
138
|
+
>
|
|
139
|
+
<g fill={currentColors.logo}>
|
|
140
|
+
{#each logoPaths as path}
|
|
141
|
+
<path d={path} />
|
|
142
|
+
{/each}
|
|
143
|
+
</g>
|
|
144
|
+
</svg>
|
|
145
|
+
</div>
|
|
146
|
+
<div
|
|
147
|
+
class="flex flex-col items-center justify-start"
|
|
148
|
+
aria-hidden={ariaLabel ? 'true' : undefined}
|
|
149
|
+
>
|
|
150
|
+
<span
|
|
151
|
+
class="leading-tight font-medium m-0 p-0"
|
|
152
|
+
style="font-family: 'Figtree', system-ui, sans-serif; font-size: {currentSize.title}; color: {currentColors.title};"
|
|
153
|
+
>
|
|
154
|
+
Classic Homes
|
|
155
|
+
</span>
|
|
156
|
+
{#if subtitle || envConfig}
|
|
157
|
+
<div class="flex flex-row items-center justify-center gap-1.5">
|
|
158
|
+
{#if envConfig}
|
|
159
|
+
<Badge
|
|
160
|
+
variant={envConfig.variant}
|
|
161
|
+
class="font-bold px-1.5 py-0"
|
|
162
|
+
style="font-size: {currentSize.badge}; height: auto; line-height: 1.4;"
|
|
163
|
+
>
|
|
164
|
+
{envConfig.label}
|
|
165
|
+
</Badge>
|
|
166
|
+
{/if}
|
|
167
|
+
{#if subtitle}
|
|
168
|
+
<span
|
|
169
|
+
class="uppercase font-black leading-none m-0 p-0"
|
|
170
|
+
style="font-family: 'Figtree', system-ui, sans-serif; font-size: {currentSize.subtitle}; color: {currentColors.subtitle};"
|
|
171
|
+
>
|
|
172
|
+
{subtitle}
|
|
173
|
+
</span>
|
|
174
|
+
{/if}
|
|
175
|
+
</div>
|
|
176
|
+
{/if}
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
{:else}
|
|
180
|
+
<!-- Horizontal layout (default) -->
|
|
181
|
+
<div
|
|
182
|
+
class={cn('flex flex-row items-center justify-start shrink-0', className)}
|
|
183
|
+
style="gap: {currentSize.gap};"
|
|
184
|
+
role={ariaLabel ? 'img' : undefined}
|
|
185
|
+
aria-label={ariaLabel || undefined}
|
|
186
|
+
{...restProps}
|
|
187
|
+
>
|
|
188
|
+
<div style="width: {currentSize.icon}px; height: {currentSize.icon}px;">
|
|
189
|
+
<svg
|
|
190
|
+
width="100%"
|
|
191
|
+
height="100%"
|
|
192
|
+
viewBox="0 0 25 25"
|
|
193
|
+
fill="none"
|
|
194
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
195
|
+
aria-hidden="true"
|
|
196
|
+
>
|
|
197
|
+
<g fill={currentColors.logo}>
|
|
198
|
+
{#each logoPaths as path}
|
|
199
|
+
<path d={path} />
|
|
200
|
+
{/each}
|
|
201
|
+
</g>
|
|
202
|
+
</svg>
|
|
203
|
+
</div>
|
|
204
|
+
<div
|
|
205
|
+
class="flex flex-col items-start justify-start"
|
|
206
|
+
aria-hidden={ariaLabel ? 'true' : undefined}
|
|
207
|
+
>
|
|
208
|
+
<span
|
|
209
|
+
class="leading-tight font-medium m-0 p-0 whitespace-nowrap"
|
|
210
|
+
style="font-family: 'Figtree', system-ui, sans-serif; font-size: {currentSize.title}; color: {currentColors.title};"
|
|
211
|
+
>
|
|
212
|
+
Classic Homes
|
|
213
|
+
</span>
|
|
214
|
+
{#if subtitle || envConfig}
|
|
215
|
+
<div class="flex flex-row items-center justify-end gap-1.5 w-full">
|
|
216
|
+
{#if envConfig}
|
|
217
|
+
<Badge
|
|
218
|
+
variant={envConfig.variant}
|
|
219
|
+
class="font-bold px-1.5 py-0"
|
|
220
|
+
style="font-size: {currentSize.badge}; height: auto; line-height: 1.4;"
|
|
221
|
+
>
|
|
222
|
+
{envConfig.label}
|
|
223
|
+
</Badge>
|
|
224
|
+
{/if}
|
|
225
|
+
{#if subtitle}
|
|
226
|
+
<span
|
|
227
|
+
class="uppercase font-black leading-none m-0 p-0"
|
|
228
|
+
style="font-family: 'Figtree', system-ui, sans-serif; font-size: {currentSize.subtitle}; color: {currentColors.subtitle};"
|
|
229
|
+
>
|
|
230
|
+
{subtitle}
|
|
231
|
+
</span>
|
|
232
|
+
{/if}
|
|
233
|
+
</div>
|
|
234
|
+
{/if}
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
{/if}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
/** Layout variant */
|
|
3
|
+
variant?: 'horizontal' | 'stacked' | 'icon';
|
|
4
|
+
/** Color mode for different backgrounds */
|
|
5
|
+
color?: 'dark' | 'light' | 'mono-dark' | 'mono-light';
|
|
6
|
+
/** Optional subtitle (e.g., "CHAPI", "MY HOME") */
|
|
7
|
+
subtitle?: string;
|
|
8
|
+
/** Environment indicator (LOCAL, DEV, DEMO) */
|
|
9
|
+
environment?: 'local' | 'dev' | 'demo';
|
|
10
|
+
/** Size preset */
|
|
11
|
+
size?: 'sm' | 'md' | 'lg';
|
|
12
|
+
/** Additional CSS classes */
|
|
13
|
+
class?: string;
|
|
14
|
+
/** Accessible label - set to empty string if wrapped in a link with its own label */
|
|
15
|
+
ariaLabel?: string;
|
|
16
|
+
[key: string]: unknown;
|
|
17
|
+
}
|
|
18
|
+
declare const LogoMain: import("svelte").Component<Props, {}, "">;
|
|
19
|
+
type LogoMain = ReturnType<typeof LogoMain>;
|
|
20
|
+
export default LogoMain;
|