@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,125 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { setContext } from 'svelte';
|
|
4
|
+
import { cn } from '../utils.js';
|
|
5
|
+
import type { Tab } from '../types/components.js';
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
/** Array of tab definitions */
|
|
9
|
+
tabs: Tab[];
|
|
10
|
+
/** Active tab ID (bindable) */
|
|
11
|
+
value?: string;
|
|
12
|
+
/** Unique ID for ARIA relationships */
|
|
13
|
+
id?: string;
|
|
14
|
+
/** Additional classes for container */
|
|
15
|
+
class?: string;
|
|
16
|
+
/** Tab panel content */
|
|
17
|
+
children?: Snippet;
|
|
18
|
+
/** Callback when active tab changes */
|
|
19
|
+
onValueChange?: (value: string) => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let {
|
|
23
|
+
tabs,
|
|
24
|
+
value = $bindable(tabs[0]?.id ?? ''),
|
|
25
|
+
id = `tabs-${Math.random().toString(36).slice(2, 11)}`,
|
|
26
|
+
class: className,
|
|
27
|
+
children,
|
|
28
|
+
onValueChange,
|
|
29
|
+
}: Props = $props();
|
|
30
|
+
|
|
31
|
+
// Create a reactive object for context
|
|
32
|
+
// Initialize with empty values - the effect will set them immediately
|
|
33
|
+
const tabsState = $state({ activeTab: '', tabsId: '' });
|
|
34
|
+
|
|
35
|
+
// Keep context state in sync with props (runs immediately and on changes)
|
|
36
|
+
$effect(() => {
|
|
37
|
+
tabsState.activeTab = value;
|
|
38
|
+
tabsState.tabsId = id;
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Set up context for TabPanel children (done once at component init)
|
|
42
|
+
setContext('tabs', tabsState);
|
|
43
|
+
|
|
44
|
+
function selectTab(tabId: string) {
|
|
45
|
+
if (tabs.find((t) => t.id === tabId && t.disabled)) return;
|
|
46
|
+
value = tabId;
|
|
47
|
+
tabsState.activeTab = tabId;
|
|
48
|
+
onValueChange?.(tabId);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function handleKeyDown(e: KeyboardEvent, currentIndex: number) {
|
|
52
|
+
const enabledTabs = tabs.filter((t) => !t.disabled);
|
|
53
|
+
const currentEnabledIndex = enabledTabs.findIndex((t) => t.id === tabs[currentIndex].id);
|
|
54
|
+
|
|
55
|
+
let newIndex = currentEnabledIndex;
|
|
56
|
+
|
|
57
|
+
switch (e.key) {
|
|
58
|
+
case 'ArrowLeft':
|
|
59
|
+
e.preventDefault();
|
|
60
|
+
newIndex = currentEnabledIndex > 0 ? currentEnabledIndex - 1 : enabledTabs.length - 1;
|
|
61
|
+
break;
|
|
62
|
+
case 'ArrowRight':
|
|
63
|
+
e.preventDefault();
|
|
64
|
+
newIndex = currentEnabledIndex < enabledTabs.length - 1 ? currentEnabledIndex + 1 : 0;
|
|
65
|
+
break;
|
|
66
|
+
case 'Home':
|
|
67
|
+
e.preventDefault();
|
|
68
|
+
newIndex = 0;
|
|
69
|
+
break;
|
|
70
|
+
case 'End':
|
|
71
|
+
e.preventDefault();
|
|
72
|
+
newIndex = enabledTabs.length - 1;
|
|
73
|
+
break;
|
|
74
|
+
default:
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const newTab = enabledTabs[newIndex];
|
|
79
|
+
if (newTab) {
|
|
80
|
+
selectTab(newTab.id);
|
|
81
|
+
// Focus the new tab
|
|
82
|
+
const tabElement = document.getElementById(`${id}-tab-${newTab.id}`);
|
|
83
|
+
tabElement?.focus();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
</script>
|
|
87
|
+
|
|
88
|
+
<div class={cn('w-full', className)}>
|
|
89
|
+
<!-- Tab List -->
|
|
90
|
+
<div
|
|
91
|
+
role="tablist"
|
|
92
|
+
aria-label="Tabs"
|
|
93
|
+
class="inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground"
|
|
94
|
+
>
|
|
95
|
+
{#each tabs as tab, index (tab.id)}
|
|
96
|
+
<button
|
|
97
|
+
id={`${id}-tab-${tab.id}`}
|
|
98
|
+
role="tab"
|
|
99
|
+
type="button"
|
|
100
|
+
aria-selected={value === tab.id}
|
|
101
|
+
aria-controls={`${id}-panel-${tab.id}`}
|
|
102
|
+
tabindex={value === tab.id ? 0 : -1}
|
|
103
|
+
disabled={tab.disabled}
|
|
104
|
+
class={cn(
|
|
105
|
+
'inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
|
106
|
+
value === tab.id
|
|
107
|
+
? 'bg-background text-foreground shadow-sm'
|
|
108
|
+
: 'hover:bg-background/50 hover:text-foreground',
|
|
109
|
+
tab.disabled && 'opacity-50 pointer-events-none cursor-not-allowed'
|
|
110
|
+
)}
|
|
111
|
+
onclick={() => selectTab(tab.id)}
|
|
112
|
+
onkeydown={(e) => handleKeyDown(e, index)}
|
|
113
|
+
>
|
|
114
|
+
{tab.label}
|
|
115
|
+
</button>
|
|
116
|
+
{/each}
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<!-- Tab Panels Container -->
|
|
120
|
+
{#if children}
|
|
121
|
+
<div class="mt-2">
|
|
122
|
+
{@render children()}
|
|
123
|
+
</div>
|
|
124
|
+
{/if}
|
|
125
|
+
</div>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { Tab } from '../types/components.js';
|
|
3
|
+
interface Props {
|
|
4
|
+
/** Array of tab definitions */
|
|
5
|
+
tabs: Tab[];
|
|
6
|
+
/** Active tab ID (bindable) */
|
|
7
|
+
value?: string;
|
|
8
|
+
/** Unique ID for ARIA relationships */
|
|
9
|
+
id?: string;
|
|
10
|
+
/** Additional classes for container */
|
|
11
|
+
class?: string;
|
|
12
|
+
/** Tab panel content */
|
|
13
|
+
children?: Snippet;
|
|
14
|
+
/** Callback when active tab changes */
|
|
15
|
+
onValueChange?: (value: string) => void;
|
|
16
|
+
}
|
|
17
|
+
declare const Tabs: import("svelte").Component<Props, {}, "value">;
|
|
18
|
+
type Tabs = ReturnType<typeof Tabs>;
|
|
19
|
+
export default Tabs;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn } from '../utils.js';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
value?: string;
|
|
6
|
+
placeholder?: string;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
readonly?: boolean;
|
|
9
|
+
required?: boolean;
|
|
10
|
+
name?: string;
|
|
11
|
+
id?: string;
|
|
12
|
+
rows?: number;
|
|
13
|
+
class?: string;
|
|
14
|
+
onValueChange?: (value: string) => void;
|
|
15
|
+
[key: string]: unknown;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let {
|
|
19
|
+
value = $bindable(''),
|
|
20
|
+
placeholder,
|
|
21
|
+
disabled = false,
|
|
22
|
+
readonly = false,
|
|
23
|
+
required = false,
|
|
24
|
+
name,
|
|
25
|
+
id,
|
|
26
|
+
rows = 3,
|
|
27
|
+
class: className,
|
|
28
|
+
onValueChange,
|
|
29
|
+
...restProps
|
|
30
|
+
}: Props = $props();
|
|
31
|
+
|
|
32
|
+
function handleInput(e: Event) {
|
|
33
|
+
const target = e.target as HTMLTextAreaElement;
|
|
34
|
+
value = target.value;
|
|
35
|
+
onValueChange?.(target.value);
|
|
36
|
+
}
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<textarea
|
|
40
|
+
{value}
|
|
41
|
+
{placeholder}
|
|
42
|
+
{disabled}
|
|
43
|
+
{readonly}
|
|
44
|
+
{required}
|
|
45
|
+
{name}
|
|
46
|
+
{id}
|
|
47
|
+
{rows}
|
|
48
|
+
class={cn(
|
|
49
|
+
'flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-gray-500 dark:placeholder:text-gray-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 resize-y',
|
|
50
|
+
className
|
|
51
|
+
)}
|
|
52
|
+
oninput={handleInput}
|
|
53
|
+
{...restProps}
|
|
54
|
+
></textarea>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
value?: string;
|
|
3
|
+
placeholder?: string;
|
|
4
|
+
disabled?: boolean;
|
|
5
|
+
readonly?: boolean;
|
|
6
|
+
required?: boolean;
|
|
7
|
+
name?: string;
|
|
8
|
+
id?: string;
|
|
9
|
+
rows?: number;
|
|
10
|
+
class?: string;
|
|
11
|
+
onValueChange?: (value: string) => void;
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
}
|
|
14
|
+
declare const Textarea: import("svelte").Component<Props, {}, "value">;
|
|
15
|
+
type Textarea = ReturnType<typeof Textarea>;
|
|
16
|
+
export default Textarea;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { fly } from 'svelte/transition';
|
|
4
|
+
import { cn } from '../utils.js';
|
|
5
|
+
import { tv, type VariantProps } from 'tailwind-variants';
|
|
6
|
+
|
|
7
|
+
type ToastType = 'success' | 'error' | 'warning' | 'info';
|
|
8
|
+
|
|
9
|
+
const toastVariants = tv({
|
|
10
|
+
base: 'group pointer-events-auto relative flex w-full items-start gap-3 overflow-hidden rounded-lg border p-4 shadow-lg transition-all',
|
|
11
|
+
variants: {
|
|
12
|
+
type: {
|
|
13
|
+
success:
|
|
14
|
+
'bg-green-50 text-green-800 border-green-200 dark:bg-green-900/20 dark:text-green-400 dark:border-green-800',
|
|
15
|
+
error:
|
|
16
|
+
'bg-red-50 text-red-800 border-red-200 dark:bg-red-900/20 dark:text-red-400 dark:border-red-800',
|
|
17
|
+
warning:
|
|
18
|
+
'bg-yellow-50 text-yellow-800 border-yellow-200 dark:bg-yellow-900/20 dark:text-yellow-400 dark:border-yellow-800',
|
|
19
|
+
info: 'bg-blue-50 text-blue-800 border-blue-200 dark:bg-blue-900/20 dark:text-blue-400 dark:border-blue-800',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
defaultVariants: {
|
|
23
|
+
type: 'info',
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
type ToastVariants = VariantProps<typeof toastVariants>;
|
|
28
|
+
|
|
29
|
+
// Icon paths for each toast type
|
|
30
|
+
const icons: Record<ToastType, string> = {
|
|
31
|
+
success: 'M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z',
|
|
32
|
+
error: 'M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z',
|
|
33
|
+
warning:
|
|
34
|
+
'M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z',
|
|
35
|
+
info: 'M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const iconColors: Record<ToastType, string> = {
|
|
39
|
+
success: 'text-green-500 dark:text-green-400',
|
|
40
|
+
error: 'text-red-500 dark:text-red-400',
|
|
41
|
+
warning: 'text-yellow-500 dark:text-yellow-400',
|
|
42
|
+
info: 'text-blue-500 dark:text-blue-400',
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
interface Props {
|
|
46
|
+
type?: ToastVariants['type'];
|
|
47
|
+
title?: string;
|
|
48
|
+
message: string;
|
|
49
|
+
class?: string;
|
|
50
|
+
onclose?: () => void;
|
|
51
|
+
action?: Snippet;
|
|
52
|
+
[key: string]: unknown;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let {
|
|
56
|
+
type = 'info',
|
|
57
|
+
title,
|
|
58
|
+
message,
|
|
59
|
+
class: className,
|
|
60
|
+
onclose,
|
|
61
|
+
action,
|
|
62
|
+
...restProps
|
|
63
|
+
}: Props = $props();
|
|
64
|
+
|
|
65
|
+
const classes = $derived(cn(toastVariants({ type }), className));
|
|
66
|
+
const toastType = $derived(type as ToastType);
|
|
67
|
+
</script>
|
|
68
|
+
|
|
69
|
+
<div class={classes} role="alert" transition:fly={{ y: 50, duration: 300 }} {...restProps}>
|
|
70
|
+
<svg
|
|
71
|
+
class="h-5 w-5 flex-shrink-0 {iconColors[toastType]}"
|
|
72
|
+
fill="none"
|
|
73
|
+
stroke="currentColor"
|
|
74
|
+
viewBox="0 0 24 24"
|
|
75
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
76
|
+
aria-hidden="true"
|
|
77
|
+
>
|
|
78
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d={icons[toastType]} />
|
|
79
|
+
</svg>
|
|
80
|
+
|
|
81
|
+
<div class="flex-1">
|
|
82
|
+
{#if title}
|
|
83
|
+
<div class="text-sm font-semibold">{title}</div>
|
|
84
|
+
{/if}
|
|
85
|
+
<p class="text-sm font-medium">{message}</p>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
{#if action}
|
|
89
|
+
{@render action()}
|
|
90
|
+
{/if}
|
|
91
|
+
|
|
92
|
+
{#if onclose}
|
|
93
|
+
<button
|
|
94
|
+
type="button"
|
|
95
|
+
class="flex-shrink-0 rounded-md p-1 hover:bg-black/5 dark:hover:bg-white/10 transition-colors"
|
|
96
|
+
onclick={onclose}
|
|
97
|
+
aria-label="Close"
|
|
98
|
+
>
|
|
99
|
+
<svg
|
|
100
|
+
class="h-4 w-4"
|
|
101
|
+
fill="none"
|
|
102
|
+
stroke="currentColor"
|
|
103
|
+
viewBox="0 0 24 24"
|
|
104
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
105
|
+
aria-hidden="true"
|
|
106
|
+
>
|
|
107
|
+
<path
|
|
108
|
+
stroke-linecap="round"
|
|
109
|
+
stroke-linejoin="round"
|
|
110
|
+
stroke-width="2"
|
|
111
|
+
d="M6 18L18 6M6 6l12 12"
|
|
112
|
+
/>
|
|
113
|
+
</svg>
|
|
114
|
+
</button>
|
|
115
|
+
{/if}
|
|
116
|
+
</div>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
declare const Toast: import("svelte").Component<{
|
|
3
|
+
[key: string]: unknown;
|
|
4
|
+
type?: "error" | "success" | "warning" | "info" | undefined;
|
|
5
|
+
title?: string;
|
|
6
|
+
message: string;
|
|
7
|
+
class?: string;
|
|
8
|
+
onclose?: () => void;
|
|
9
|
+
action?: Snippet;
|
|
10
|
+
}, {}, "">;
|
|
11
|
+
type Toast = ReturnType<typeof Toast>;
|
|
12
|
+
export default Toast;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn } from '../utils.js';
|
|
3
|
+
import { toastStore } from '../stores/toast.svelte.js';
|
|
4
|
+
import Toast from './Toast.svelte';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
position?:
|
|
8
|
+
| 'top-left'
|
|
9
|
+
| 'top-right'
|
|
10
|
+
| 'top-center'
|
|
11
|
+
| 'bottom-left'
|
|
12
|
+
| 'bottom-right'
|
|
13
|
+
| 'bottom-center';
|
|
14
|
+
class?: string;
|
|
15
|
+
[key: string]: unknown;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let { position = 'bottom-right', class: className, ...restProps }: Props = $props();
|
|
19
|
+
|
|
20
|
+
const positionClasses = {
|
|
21
|
+
'top-left': 'top-4 left-4',
|
|
22
|
+
'top-right': 'top-4 right-4',
|
|
23
|
+
'top-center': 'top-4 left-1/2 -translate-x-1/2',
|
|
24
|
+
'bottom-left': 'bottom-4 left-4',
|
|
25
|
+
'bottom-right': 'bottom-4 right-4',
|
|
26
|
+
'bottom-center': 'bottom-4 left-1/2 -translate-x-1/2',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Stack order: bottom positions use flex-col-reverse so newest appears at bottom
|
|
30
|
+
const stackOrder = $derived(position.startsWith('bottom') ? 'flex-col' : 'flex-col-reverse');
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
{#if toastStore.toasts.length > 0}
|
|
34
|
+
<div
|
|
35
|
+
class={cn(
|
|
36
|
+
'fixed z-50 flex w-full max-w-sm gap-2 pointer-events-none',
|
|
37
|
+
stackOrder,
|
|
38
|
+
positionClasses[position],
|
|
39
|
+
className
|
|
40
|
+
)}
|
|
41
|
+
aria-live="polite"
|
|
42
|
+
aria-atomic="true"
|
|
43
|
+
{...restProps}
|
|
44
|
+
>
|
|
45
|
+
{#each toastStore.toasts as toast (toast.id)}
|
|
46
|
+
<div class="pointer-events-auto">
|
|
47
|
+
<Toast
|
|
48
|
+
type={toast.type}
|
|
49
|
+
title={toast.title}
|
|
50
|
+
message={toast.message}
|
|
51
|
+
onclose={() => toastStore.remove(toast.id)}
|
|
52
|
+
/>
|
|
53
|
+
</div>
|
|
54
|
+
{/each}
|
|
55
|
+
</div>
|
|
56
|
+
{/if}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
position?: 'top-left' | 'top-right' | 'top-center' | 'bottom-left' | 'bottom-right' | 'bottom-center';
|
|
3
|
+
class?: string;
|
|
4
|
+
[key: string]: unknown;
|
|
5
|
+
}
|
|
6
|
+
declare const ToastContainer: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type ToastContainer = ReturnType<typeof ToastContainer>;
|
|
8
|
+
export default ToastContainer;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Tooltip as TooltipPrimitive } from 'bits-ui';
|
|
3
|
+
import { cn } from '../utils.js';
|
|
4
|
+
import type { Snippet } from 'svelte';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
/** Content to display in the tooltip */
|
|
8
|
+
content: string;
|
|
9
|
+
/** Side of the trigger to show tooltip */
|
|
10
|
+
side?: 'top' | 'right' | 'bottom' | 'left';
|
|
11
|
+
/** Alignment of tooltip relative to trigger */
|
|
12
|
+
align?: 'start' | 'center' | 'end';
|
|
13
|
+
/** Delay in ms before showing tooltip */
|
|
14
|
+
delayDuration?: number;
|
|
15
|
+
/** Additional class for tooltip content */
|
|
16
|
+
class?: string;
|
|
17
|
+
/** Trigger element */
|
|
18
|
+
children: Snippet;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let {
|
|
22
|
+
content,
|
|
23
|
+
side = 'top',
|
|
24
|
+
align = 'center',
|
|
25
|
+
delayDuration = 200,
|
|
26
|
+
class: className,
|
|
27
|
+
children,
|
|
28
|
+
}: Props = $props();
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<TooltipPrimitive.Provider>
|
|
32
|
+
<TooltipPrimitive.Root {delayDuration}>
|
|
33
|
+
<TooltipPrimitive.Trigger asChild>
|
|
34
|
+
{#snippet child({ props })}
|
|
35
|
+
<span {...props} class="inline-flex">
|
|
36
|
+
{@render children()}
|
|
37
|
+
</span>
|
|
38
|
+
{/snippet}
|
|
39
|
+
</TooltipPrimitive.Trigger>
|
|
40
|
+
<TooltipPrimitive.Portal>
|
|
41
|
+
<TooltipPrimitive.Content
|
|
42
|
+
{side}
|
|
43
|
+
{align}
|
|
44
|
+
sideOffset={4}
|
|
45
|
+
class={cn(
|
|
46
|
+
'z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
|
47
|
+
className
|
|
48
|
+
)}
|
|
49
|
+
>
|
|
50
|
+
{content}
|
|
51
|
+
<TooltipPrimitive.Arrow class="fill-primary" />
|
|
52
|
+
</TooltipPrimitive.Content>
|
|
53
|
+
</TooltipPrimitive.Portal>
|
|
54
|
+
</TooltipPrimitive.Root>
|
|
55
|
+
</TooltipPrimitive.Provider>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface Props {
|
|
3
|
+
/** Content to display in the tooltip */
|
|
4
|
+
content: string;
|
|
5
|
+
/** Side of the trigger to show tooltip */
|
|
6
|
+
side?: 'top' | 'right' | 'bottom' | 'left';
|
|
7
|
+
/** Alignment of tooltip relative to trigger */
|
|
8
|
+
align?: 'start' | 'center' | 'end';
|
|
9
|
+
/** Delay in ms before showing tooltip */
|
|
10
|
+
delayDuration?: number;
|
|
11
|
+
/** Additional class for tooltip content */
|
|
12
|
+
class?: string;
|
|
13
|
+
/** Trigger element */
|
|
14
|
+
children: Snippet;
|
|
15
|
+
}
|
|
16
|
+
declare const Tooltip: import("svelte").Component<Props, {}, "">;
|
|
17
|
+
type Tooltip = ReturnType<typeof Tooltip>;
|
|
18
|
+
export default Tooltip;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* AppShell - Base wrapper component for applications
|
|
4
|
+
*
|
|
5
|
+
* Provides:
|
|
6
|
+
* - Skip link for accessibility
|
|
7
|
+
* - Toast container for notifications
|
|
8
|
+
* - Consistent base structure
|
|
9
|
+
*
|
|
10
|
+
* ## Error Handling
|
|
11
|
+
*
|
|
12
|
+
* AppShell does not include built-in error boundaries to give you full control
|
|
13
|
+
* over error handling in your application. We recommend implementing error
|
|
14
|
+
* handling at your application's root level.
|
|
15
|
+
*
|
|
16
|
+
* ### Recommended Pattern
|
|
17
|
+
*
|
|
18
|
+
* Create an ErrorBoundary component that catches errors and wrap AppShell with it.
|
|
19
|
+
* See the package README for a complete ErrorBoundary implementation example.
|
|
20
|
+
*
|
|
21
|
+
* Usage:
|
|
22
|
+
* ErrorBoundary > AppShell > YourContent
|
|
23
|
+
*
|
|
24
|
+
* The ErrorBoundary should:
|
|
25
|
+
* 1. Listen for window 'error' events
|
|
26
|
+
* 2. Store error state using $state
|
|
27
|
+
* 3. Render fallback UI when error is present
|
|
28
|
+
* 4. Provide a way to reset/retry
|
|
29
|
+
*
|
|
30
|
+
* @see README.md for complete ErrorBoundary implementation
|
|
31
|
+
*/
|
|
32
|
+
import type { Snippet } from 'svelte';
|
|
33
|
+
import ToastContainer from '../ToastContainer.svelte';
|
|
34
|
+
|
|
35
|
+
interface Props {
|
|
36
|
+
/** Skip link target ID */
|
|
37
|
+
skipToId?: string;
|
|
38
|
+
/** Skip link text */
|
|
39
|
+
skipToText?: string;
|
|
40
|
+
/** Toast container position */
|
|
41
|
+
toastPosition?:
|
|
42
|
+
| 'top-right'
|
|
43
|
+
| 'top-left'
|
|
44
|
+
| 'bottom-right'
|
|
45
|
+
| 'bottom-left'
|
|
46
|
+
| 'top-center'
|
|
47
|
+
| 'bottom-center';
|
|
48
|
+
/** Main content */
|
|
49
|
+
children: Snippet;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let {
|
|
53
|
+
skipToId = 'main-content',
|
|
54
|
+
skipToText = 'Skip to main content',
|
|
55
|
+
toastPosition = 'top-right',
|
|
56
|
+
children,
|
|
57
|
+
}: Props = $props();
|
|
58
|
+
</script>
|
|
59
|
+
|
|
60
|
+
<div class="app-shell min-h-screen bg-background text-foreground">
|
|
61
|
+
<!-- Skip Link for Accessibility -->
|
|
62
|
+
<a
|
|
63
|
+
href="#{skipToId}"
|
|
64
|
+
class="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-[100] focus:rounded-md focus:bg-primary focus:px-4 focus:py-2 focus:text-primary-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
|
65
|
+
>
|
|
66
|
+
{skipToText}
|
|
67
|
+
</a>
|
|
68
|
+
|
|
69
|
+
<!-- Main Content -->
|
|
70
|
+
{@render children()}
|
|
71
|
+
|
|
72
|
+
<!-- Toast Container -->
|
|
73
|
+
<ToastContainer position={toastPosition} />
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<style>
|
|
77
|
+
.app-shell {
|
|
78
|
+
/* Ensure the app shell fills the viewport */
|
|
79
|
+
display: flex;
|
|
80
|
+
flex-direction: column;
|
|
81
|
+
}
|
|
82
|
+
</style>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AppShell - Base wrapper component for applications
|
|
3
|
+
*
|
|
4
|
+
* Provides:
|
|
5
|
+
* - Skip link for accessibility
|
|
6
|
+
* - Toast container for notifications
|
|
7
|
+
* - Consistent base structure
|
|
8
|
+
*
|
|
9
|
+
* ## Error Handling
|
|
10
|
+
*
|
|
11
|
+
* AppShell does not include built-in error boundaries to give you full control
|
|
12
|
+
* over error handling in your application. We recommend implementing error
|
|
13
|
+
* handling at your application's root level.
|
|
14
|
+
*
|
|
15
|
+
* ### Recommended Pattern
|
|
16
|
+
*
|
|
17
|
+
* Create an ErrorBoundary component that catches errors and wrap AppShell with it.
|
|
18
|
+
* See the package README for a complete ErrorBoundary implementation example.
|
|
19
|
+
*
|
|
20
|
+
* Usage:
|
|
21
|
+
* ErrorBoundary > AppShell > YourContent
|
|
22
|
+
*
|
|
23
|
+
* The ErrorBoundary should:
|
|
24
|
+
* 1. Listen for window 'error' events
|
|
25
|
+
* 2. Store error state using $state
|
|
26
|
+
* 3. Render fallback UI when error is present
|
|
27
|
+
* 4. Provide a way to reset/retry
|
|
28
|
+
*
|
|
29
|
+
* @see README.md for complete ErrorBoundary implementation
|
|
30
|
+
*/
|
|
31
|
+
import type { Snippet } from 'svelte';
|
|
32
|
+
interface Props {
|
|
33
|
+
/** Skip link target ID */
|
|
34
|
+
skipToId?: string;
|
|
35
|
+
/** Skip link text */
|
|
36
|
+
skipToText?: string;
|
|
37
|
+
/** Toast container position */
|
|
38
|
+
toastPosition?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' | 'top-center' | 'bottom-center';
|
|
39
|
+
/** Main content */
|
|
40
|
+
children: Snippet;
|
|
41
|
+
}
|
|
42
|
+
declare const AppShell: import("svelte").Component<Props, {}, "">;
|
|
43
|
+
type AppShell = ReturnType<typeof AppShell>;
|
|
44
|
+
export default AppShell;
|