@lanrenbang/basecoat-ultra-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/dist/components/Accordion.svelte +64 -0
- package/dist/components/Accordion.svelte.d.ts +14 -0
- package/dist/components/Alert.svelte +51 -0
- package/dist/components/Alert.svelte.d.ts +12 -0
- package/dist/components/Avatar.svelte +57 -0
- package/dist/components/Avatar.svelte.d.ts +13 -0
- package/dist/components/Badge.svelte +30 -0
- package/dist/components/Badge.svelte.d.ts +10 -0
- package/dist/components/Breadcrumb.svelte +54 -0
- package/dist/components/Breadcrumb.svelte.d.ts +14 -0
- package/dist/components/Button.svelte +38 -0
- package/dist/components/Button.svelte.d.ts +12 -0
- package/dist/components/ButtonGroup.svelte +26 -0
- package/dist/components/ButtonGroup.svelte.d.ts +10 -0
- package/dist/components/Card.svelte +67 -0
- package/dist/components/Card.svelte.d.ts +13 -0
- package/dist/components/Carousel.svelte +142 -0
- package/dist/components/Carousel.svelte.d.ts +11 -0
- package/dist/components/CatppuccinThemeSwitcher.svelte +132 -0
- package/dist/components/CatppuccinThemeSwitcher.svelte.d.ts +3 -0
- package/dist/components/Checkbox.svelte +20 -0
- package/dist/components/Checkbox.svelte.d.ts +8 -0
- package/dist/components/Collapsible.svelte +39 -0
- package/dist/components/Collapsible.svelte.d.ts +13 -0
- package/dist/components/Command.svelte +78 -0
- package/dist/components/Command.svelte.d.ts +12 -0
- package/dist/components/DatePicker.svelte +172 -0
- package/dist/components/DatePicker.svelte.d.ts +13 -0
- package/dist/components/Dialog.svelte +91 -0
- package/dist/components/Dialog.svelte.d.ts +14 -0
- package/dist/components/Drawer.svelte +127 -0
- package/dist/components/Drawer.svelte.d.ts +12 -0
- package/dist/components/DropdownMenu.svelte +62 -0
- package/dist/components/DropdownMenu.svelte.d.ts +14 -0
- package/dist/components/Empty.svelte +58 -0
- package/dist/components/Empty.svelte.d.ts +12 -0
- package/dist/components/Input.svelte +22 -0
- package/dist/components/Input.svelte.d.ts +9 -0
- package/dist/components/InputOTP.svelte +189 -0
- package/dist/components/InputOTP.svelte.d.ts +12 -0
- package/dist/components/Item.svelte +64 -0
- package/dist/components/Item.svelte.d.ts +14 -0
- package/dist/components/Kbd.svelte +28 -0
- package/dist/components/Kbd.svelte.d.ts +9 -0
- package/dist/components/Label.svelte +30 -0
- package/dist/components/Label.svelte.d.ts +10 -0
- package/dist/components/Pagination.svelte +120 -0
- package/dist/components/Pagination.svelte.d.ts +35 -0
- package/dist/components/Popover.svelte +68 -0
- package/dist/components/Popover.svelte.d.ts +16 -0
- package/dist/components/Progress.svelte +26 -0
- package/dist/components/Progress.svelte.d.ts +9 -0
- package/dist/components/Radio.svelte +22 -0
- package/dist/components/Radio.svelte.d.ts +9 -0
- package/dist/components/Resizable.svelte +66 -0
- package/dist/components/Resizable.svelte.d.ts +13 -0
- package/dist/components/Select.svelte +183 -0
- package/dist/components/Select.svelte.d.ts +16 -0
- package/dist/components/Separator.svelte +19 -0
- package/dist/components/Separator.svelte.d.ts +8 -0
- package/dist/components/Sheet.svelte +182 -0
- package/dist/components/Sheet.svelte.d.ts +13 -0
- package/dist/components/Skeleton.svelte +27 -0
- package/dist/components/Skeleton.svelte.d.ts +8 -0
- package/dist/components/Slider.svelte +38 -0
- package/dist/components/Slider.svelte.d.ts +11 -0
- package/dist/components/Spinner.svelte +28 -0
- package/dist/components/Spinner.svelte.d.ts +8 -0
- package/dist/components/Switch.svelte +20 -0
- package/dist/components/Switch.svelte.d.ts +8 -0
- package/dist/components/Table.svelte +61 -0
- package/dist/components/Table.svelte.d.ts +13 -0
- package/dist/components/Tabs.svelte +97 -0
- package/dist/components/Tabs.svelte.d.ts +15 -0
- package/dist/components/Textarea.svelte +22 -0
- package/dist/components/Textarea.svelte.d.ts +9 -0
- package/dist/components/Toast.svelte +73 -0
- package/dist/components/Toast.svelte.d.ts +3 -0
- package/dist/components/Toggle.svelte +69 -0
- package/dist/components/Toggle.svelte.d.ts +13 -0
- package/dist/components/ToggleGroup.svelte +69 -0
- package/dist/components/ToggleGroup.svelte.d.ts +12 -0
- package/dist/components/Tooltip.svelte +32 -0
- package/dist/components/Tooltip.svelte.d.ts +11 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +47 -0
- package/dist/reference.css +2 -0
- package/package.json +70 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { onMount } from 'svelte';
|
|
4
|
+
import { fade } from 'svelte/transition';
|
|
5
|
+
import { cubicOut, backOut } from 'svelte/easing';
|
|
6
|
+
|
|
7
|
+
let {
|
|
8
|
+
open = $bindable(false),
|
|
9
|
+
side = 'right',
|
|
10
|
+
title,
|
|
11
|
+
description,
|
|
12
|
+
children,
|
|
13
|
+
class: className = '',
|
|
14
|
+
...rest
|
|
15
|
+
}: {
|
|
16
|
+
open?: boolean;
|
|
17
|
+
side?: 'left' | 'right' | 'top' | 'bottom';
|
|
18
|
+
title?: string | Snippet;
|
|
19
|
+
description?: string | Snippet;
|
|
20
|
+
children: Snippet;
|
|
21
|
+
class?: string;
|
|
22
|
+
[key: string]: any;
|
|
23
|
+
} = $props();
|
|
24
|
+
|
|
25
|
+
let container: HTMLDivElement | undefined = $state();
|
|
26
|
+
|
|
27
|
+
// Custom spring-like transition for sheet content
|
|
28
|
+
function springSlide(node: Element, { duration = 400, side: s = 'right' }: { duration?: number; side?: string } = {}) {
|
|
29
|
+
const style = getComputedStyle(node);
|
|
30
|
+
const transform = style.transform === 'none' ? '' : style.transform;
|
|
31
|
+
|
|
32
|
+
let axis: 'x' | 'y';
|
|
33
|
+
let distance: number;
|
|
34
|
+
|
|
35
|
+
switch (s) {
|
|
36
|
+
case 'left':
|
|
37
|
+
axis = 'x';
|
|
38
|
+
distance = -node.clientWidth;
|
|
39
|
+
break;
|
|
40
|
+
case 'right':
|
|
41
|
+
axis = 'x';
|
|
42
|
+
distance = node.clientWidth;
|
|
43
|
+
break;
|
|
44
|
+
case 'top':
|
|
45
|
+
axis = 'y';
|
|
46
|
+
distance = -node.clientHeight;
|
|
47
|
+
break;
|
|
48
|
+
case 'bottom':
|
|
49
|
+
axis = 'y';
|
|
50
|
+
distance = node.clientHeight;
|
|
51
|
+
break;
|
|
52
|
+
default:
|
|
53
|
+
axis = 'x';
|
|
54
|
+
distance = node.clientWidth;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
duration,
|
|
59
|
+
css: (t: number) => {
|
|
60
|
+
// Use backOut easing for spring-like effect on enter
|
|
61
|
+
const eased = backOut(t);
|
|
62
|
+
const offset = (1 - eased) * distance;
|
|
63
|
+
const translateValue = axis === 'x' ? `translateX(${offset}px)` : `translateY(${offset}px)`;
|
|
64
|
+
return `transform: ${transform} ${translateValue}`;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function springSlideOut(node: Element, { duration = 300, side: s = 'right' }: { duration?: number; side?: string } = {}) {
|
|
70
|
+
let axis: 'x' | 'y';
|
|
71
|
+
let distance: number;
|
|
72
|
+
|
|
73
|
+
switch (s) {
|
|
74
|
+
case 'left':
|
|
75
|
+
axis = 'x';
|
|
76
|
+
distance = -node.clientWidth;
|
|
77
|
+
break;
|
|
78
|
+
case 'right':
|
|
79
|
+
axis = 'x';
|
|
80
|
+
distance = node.clientWidth;
|
|
81
|
+
break;
|
|
82
|
+
case 'top':
|
|
83
|
+
axis = 'y';
|
|
84
|
+
distance = -node.clientHeight;
|
|
85
|
+
break;
|
|
86
|
+
case 'bottom':
|
|
87
|
+
axis = 'y';
|
|
88
|
+
distance = node.clientHeight;
|
|
89
|
+
break;
|
|
90
|
+
default:
|
|
91
|
+
axis = 'x';
|
|
92
|
+
distance = node.clientWidth;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
duration,
|
|
97
|
+
css: (t: number) => {
|
|
98
|
+
const eased = cubicOut(t);
|
|
99
|
+
const offset = (1 - eased) * distance;
|
|
100
|
+
const translateValue = axis === 'x' ? `translateX(${offset}px)` : `translateY(${offset}px)`;
|
|
101
|
+
return `transform: ${translateValue}`;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function handleClickOutside(event: MouseEvent) {
|
|
107
|
+
if (open && event.target === container) {
|
|
108
|
+
open = false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function handleKeyDown(event: KeyboardEvent) {
|
|
113
|
+
if (event.key === 'Escape' && open) {
|
|
114
|
+
open = false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
onMount(() => {
|
|
119
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
120
|
+
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Lock body scroll when open
|
|
124
|
+
$effect(() => {
|
|
125
|
+
if (open) {
|
|
126
|
+
document.body.style.overflow = 'hidden';
|
|
127
|
+
} else {
|
|
128
|
+
document.body.style.overflow = '';
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
</script>
|
|
132
|
+
|
|
133
|
+
{#if open}
|
|
134
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
135
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
136
|
+
<div
|
|
137
|
+
bind:this={container}
|
|
138
|
+
class="sheet fixed inset-0 z-50"
|
|
139
|
+
onclick={handleClickOutside}
|
|
140
|
+
>
|
|
141
|
+
<div
|
|
142
|
+
class="absolute inset-0 bg-black/80"
|
|
143
|
+
transition:fade={{ duration: 200 }}
|
|
144
|
+
></div>
|
|
145
|
+
<div
|
|
146
|
+
class={['sheet-content', className].filter(Boolean).join(' ')}
|
|
147
|
+
data-side={side}
|
|
148
|
+
in:springSlide={{ duration: 400, side }}
|
|
149
|
+
out:springSlideOut={{ duration: 300, side }}
|
|
150
|
+
{...rest}
|
|
151
|
+
>
|
|
152
|
+
<button
|
|
153
|
+
class="absolute right-4 top-4 opacity-70 hover:opacity-100 transition-opacity"
|
|
154
|
+
onclick={() => open = false}
|
|
155
|
+
aria-label="Close"
|
|
156
|
+
>
|
|
157
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
|
|
158
|
+
</button>
|
|
159
|
+
|
|
160
|
+
{#if title}
|
|
161
|
+
<h2 class="text-lg font-semibold">
|
|
162
|
+
{#if typeof title === 'string'}{title}{:else}{@render title()}{/if}
|
|
163
|
+
</h2>
|
|
164
|
+
{/if}
|
|
165
|
+
|
|
166
|
+
{#if description}
|
|
167
|
+
<p class="text-sm text-muted-foreground">
|
|
168
|
+
{#if typeof description === 'string'}{description}{:else}{@render description()}{/if}
|
|
169
|
+
</p>
|
|
170
|
+
{/if}
|
|
171
|
+
|
|
172
|
+
<div class="mt-4">
|
|
173
|
+
{@render children()}
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
{/if}
|
|
178
|
+
|
|
179
|
+
<style>
|
|
180
|
+
@import "../../../../ultra/src/css/parts/custom/sheet.css";
|
|
181
|
+
@reference "../reference.css";
|
|
182
|
+
</style>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
open?: boolean;
|
|
4
|
+
side?: 'left' | 'right' | 'top' | 'bottom';
|
|
5
|
+
title?: string | Snippet;
|
|
6
|
+
description?: string | Snippet;
|
|
7
|
+
children: Snippet;
|
|
8
|
+
class?: string;
|
|
9
|
+
[key: string]: any;
|
|
10
|
+
};
|
|
11
|
+
declare const Sheet: import("svelte").Component<$$ComponentProps, {}, "open">;
|
|
12
|
+
type Sheet = ReturnType<typeof Sheet>;
|
|
13
|
+
export default Sheet;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
let {
|
|
3
|
+
class: className = '',
|
|
4
|
+
rounded = 'md',
|
|
5
|
+
...rest
|
|
6
|
+
}: {
|
|
7
|
+
class?: string;
|
|
8
|
+
rounded?: 'full' | 'lg' | 'md' | 'sm' | 'none';
|
|
9
|
+
[key: string]: any;
|
|
10
|
+
} = $props();
|
|
11
|
+
|
|
12
|
+
const roundedMap: Record<string, string> = {
|
|
13
|
+
full: 'rounded-full',
|
|
14
|
+
lg: 'rounded-lg',
|
|
15
|
+
md: 'rounded-md',
|
|
16
|
+
sm: 'rounded-sm',
|
|
17
|
+
none: 'rounded-none'
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const finalClass = $derived([
|
|
21
|
+
'skeleton bg-muted animate-pulse',
|
|
22
|
+
roundedMap[rounded] || rounded,
|
|
23
|
+
className
|
|
24
|
+
].filter(Boolean).join(' '));
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<div class={finalClass} {...rest}></div>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
class?: string;
|
|
3
|
+
rounded?: 'full' | 'lg' | 'md' | 'sm' | 'none';
|
|
4
|
+
[key: string]: any;
|
|
5
|
+
};
|
|
6
|
+
declare const Skeleton: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
7
|
+
type Skeleton = ReturnType<typeof Skeleton>;
|
|
8
|
+
export default Skeleton;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
let {
|
|
3
|
+
value = $bindable(0),
|
|
4
|
+
min = 0,
|
|
5
|
+
max = 100,
|
|
6
|
+
step = 1,
|
|
7
|
+
class: className = '',
|
|
8
|
+
...rest
|
|
9
|
+
}: {
|
|
10
|
+
value?: number;
|
|
11
|
+
min?: number;
|
|
12
|
+
max?: number;
|
|
13
|
+
step?: number;
|
|
14
|
+
class?: string;
|
|
15
|
+
[key: string]: any;
|
|
16
|
+
} = $props();
|
|
17
|
+
|
|
18
|
+
const finalClass = $derived(['slider', className].filter(Boolean).join(' '));
|
|
19
|
+
|
|
20
|
+
// Calculate slider value percentage for CSS variable
|
|
21
|
+
const sliderPercent = $derived(((value - min) / (max - min)) * 100);
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<input
|
|
25
|
+
type="range"
|
|
26
|
+
class={finalClass}
|
|
27
|
+
bind:value
|
|
28
|
+
{min}
|
|
29
|
+
{max}
|
|
30
|
+
{step}
|
|
31
|
+
style="--slider-value: {sliderPercent}%"
|
|
32
|
+
{...rest}
|
|
33
|
+
/>
|
|
34
|
+
|
|
35
|
+
<style>
|
|
36
|
+
@import "../../../../ultra/src/css/parts/components/range.css";
|
|
37
|
+
@reference "../reference.css";
|
|
38
|
+
</style>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
value?: number;
|
|
3
|
+
min?: number;
|
|
4
|
+
max?: number;
|
|
5
|
+
step?: number;
|
|
6
|
+
class?: string;
|
|
7
|
+
[key: string]: any;
|
|
8
|
+
};
|
|
9
|
+
declare const Slider: import("svelte").Component<$$ComponentProps, {}, "value">;
|
|
10
|
+
type Slider = ReturnType<typeof Slider>;
|
|
11
|
+
export default Slider;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
let {
|
|
3
|
+
class: className = '',
|
|
4
|
+
size = 'md',
|
|
5
|
+
...rest
|
|
6
|
+
}: {
|
|
7
|
+
class?: string;
|
|
8
|
+
size?: 'sm' | 'md' | 'lg' | string;
|
|
9
|
+
[key: string]: any;
|
|
10
|
+
} = $props();
|
|
11
|
+
|
|
12
|
+
const sizeMap: Record<string, string> = {
|
|
13
|
+
sm: 'h-4 w-4',
|
|
14
|
+
md: 'h-6 w-6',
|
|
15
|
+
lg: 'h-8 w-8'
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const finalClass = $derived([
|
|
19
|
+
'animate-spin text-primary',
|
|
20
|
+
sizeMap[size] || size,
|
|
21
|
+
className
|
|
22
|
+
].filter(Boolean).join(' '));
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<svg class={finalClass} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" {...rest}>
|
|
26
|
+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
27
|
+
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
28
|
+
</svg>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
let {
|
|
3
|
+
checked = $bindable(false),
|
|
4
|
+
class: className = '',
|
|
5
|
+
...rest
|
|
6
|
+
}: {
|
|
7
|
+
checked?: boolean;
|
|
8
|
+
class?: string;
|
|
9
|
+
[key: string]: any;
|
|
10
|
+
} = $props();
|
|
11
|
+
|
|
12
|
+
const finalClass = $derived(['input', className].filter(Boolean).join(' '));
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<input type="checkbox" role="switch" class={finalClass} bind:checked {...rest} />
|
|
16
|
+
|
|
17
|
+
<style>
|
|
18
|
+
@import "../../../../ultra/src/css/parts/components/switch.css";
|
|
19
|
+
@reference "../reference.css";
|
|
20
|
+
</style>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
let {
|
|
5
|
+
header,
|
|
6
|
+
children,
|
|
7
|
+
footer,
|
|
8
|
+
caption,
|
|
9
|
+
class: className = '',
|
|
10
|
+
containerClass = '',
|
|
11
|
+
...rest
|
|
12
|
+
}: {
|
|
13
|
+
header?: Snippet;
|
|
14
|
+
children?: Snippet;
|
|
15
|
+
footer?: Snippet;
|
|
16
|
+
caption?: Snippet | string;
|
|
17
|
+
class?: string;
|
|
18
|
+
containerClass?: string;
|
|
19
|
+
[key: string]: any;
|
|
20
|
+
} = $props();
|
|
21
|
+
|
|
22
|
+
const finalContainerClass = $derived(['border rounded-lg overflow-hidden bg-card w-full', containerClass].filter(Boolean).join(' '));
|
|
23
|
+
const finalTableClass = $derived(['table w-full', className].filter(Boolean).join(' '));
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<div class={finalContainerClass}>
|
|
27
|
+
<table class={finalTableClass} {...rest}>
|
|
28
|
+
{#if caption}
|
|
29
|
+
<caption>
|
|
30
|
+
{#if typeof caption === 'string'}
|
|
31
|
+
{caption}
|
|
32
|
+
{:else}
|
|
33
|
+
{@render caption()}
|
|
34
|
+
{/if}
|
|
35
|
+
</caption>
|
|
36
|
+
{/if}
|
|
37
|
+
|
|
38
|
+
{#if header}
|
|
39
|
+
<thead>
|
|
40
|
+
{@render header()}
|
|
41
|
+
</thead>
|
|
42
|
+
{/if}
|
|
43
|
+
|
|
44
|
+
{#if children}
|
|
45
|
+
<tbody>
|
|
46
|
+
{@render children()}
|
|
47
|
+
</tbody>
|
|
48
|
+
{/if}
|
|
49
|
+
|
|
50
|
+
{#if footer}
|
|
51
|
+
<tfoot>
|
|
52
|
+
{@render footer()}
|
|
53
|
+
</tfoot>
|
|
54
|
+
{/if}
|
|
55
|
+
</table>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<style>
|
|
59
|
+
@import "../../../../ultra/src/css/parts/components/table.css";
|
|
60
|
+
@reference "../reference.css";
|
|
61
|
+
</style>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
header?: Snippet;
|
|
4
|
+
children?: Snippet;
|
|
5
|
+
footer?: Snippet;
|
|
6
|
+
caption?: Snippet | string;
|
|
7
|
+
class?: string;
|
|
8
|
+
containerClass?: string;
|
|
9
|
+
[key: string]: any;
|
|
10
|
+
};
|
|
11
|
+
declare const Table: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
12
|
+
type Table = ReturnType<typeof Table>;
|
|
13
|
+
export default Table;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
let {
|
|
5
|
+
items = [],
|
|
6
|
+
activeTab = $bindable(''),
|
|
7
|
+
class: className = '',
|
|
8
|
+
contentClass = '',
|
|
9
|
+
...rest
|
|
10
|
+
}: {
|
|
11
|
+
items: Array<{ id: string; label: string | Snippet; content: string | Snippet }>;
|
|
12
|
+
activeTab?: string;
|
|
13
|
+
class?: string;
|
|
14
|
+
contentClass?: string;
|
|
15
|
+
[key: string]: any;
|
|
16
|
+
} = $props();
|
|
17
|
+
|
|
18
|
+
// Set first item as active when items change and no active tab
|
|
19
|
+
$effect(() => {
|
|
20
|
+
if (!activeTab && items.length > 0) {
|
|
21
|
+
activeTab = items[0].id;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
function handleKeyDown(event: KeyboardEvent) {
|
|
26
|
+
const currentIndex = items.findIndex(item => item.id === activeTab);
|
|
27
|
+
if (currentIndex === -1) return;
|
|
28
|
+
|
|
29
|
+
let nextIndex: number;
|
|
30
|
+
|
|
31
|
+
switch (event.key) {
|
|
32
|
+
case 'ArrowRight':
|
|
33
|
+
nextIndex = (currentIndex + 1) % items.length;
|
|
34
|
+
break;
|
|
35
|
+
case 'ArrowLeft':
|
|
36
|
+
nextIndex = (currentIndex - 1 + items.length) % items.length;
|
|
37
|
+
break;
|
|
38
|
+
case 'Home':
|
|
39
|
+
nextIndex = 0;
|
|
40
|
+
break;
|
|
41
|
+
case 'End':
|
|
42
|
+
nextIndex = items.length - 1;
|
|
43
|
+
break;
|
|
44
|
+
default:
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
event.preventDefault();
|
|
49
|
+
activeTab = items[nextIndex].id;
|
|
50
|
+
|
|
51
|
+
const buttons = (event.currentTarget as HTMLElement).querySelectorAll('button');
|
|
52
|
+
(buttons[nextIndex] as HTMLElement).focus();
|
|
53
|
+
}
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
<div class={['tabs', className].filter(Boolean).join(' ')} {...rest}>
|
|
57
|
+
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
58
|
+
<div role="tablist" onkeydown={handleKeyDown} tabindex="0">
|
|
59
|
+
{#each items as item}
|
|
60
|
+
<button
|
|
61
|
+
role="tab"
|
|
62
|
+
aria-controls={item.id}
|
|
63
|
+
aria-selected={activeTab === item.id}
|
|
64
|
+
tabindex={activeTab === item.id ? 0 : -1}
|
|
65
|
+
onclick={() => activeTab = item.id}
|
|
66
|
+
>
|
|
67
|
+
{#if typeof item.label === 'string'}
|
|
68
|
+
{item.label}
|
|
69
|
+
{:else}
|
|
70
|
+
{@render item.label()}
|
|
71
|
+
{/if}
|
|
72
|
+
</button>
|
|
73
|
+
{/each}
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
{#each items as item}
|
|
77
|
+
<section
|
|
78
|
+
id={item.id}
|
|
79
|
+
role="tabpanel"
|
|
80
|
+
hidden={activeTab !== item.id}
|
|
81
|
+
class={['p-4 border rounded-md mt-4', contentClass].filter(Boolean).join(' ')}
|
|
82
|
+
>
|
|
83
|
+
{#if activeTab === item.id}
|
|
84
|
+
{#if typeof item.content === 'string'}
|
|
85
|
+
{item.content}
|
|
86
|
+
{:else}
|
|
87
|
+
{@render item.content()}
|
|
88
|
+
{/if}
|
|
89
|
+
{/if}
|
|
90
|
+
</section>
|
|
91
|
+
{/each}
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<style>
|
|
95
|
+
@import "../../../../ultra/src/css/parts/components/tabs.css";
|
|
96
|
+
@reference "../reference.css";
|
|
97
|
+
</style>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
items: Array<{
|
|
4
|
+
id: string;
|
|
5
|
+
label: string | Snippet;
|
|
6
|
+
content: string | Snippet;
|
|
7
|
+
}>;
|
|
8
|
+
activeTab?: string;
|
|
9
|
+
class?: string;
|
|
10
|
+
contentClass?: string;
|
|
11
|
+
[key: string]: any;
|
|
12
|
+
};
|
|
13
|
+
declare const Tabs: import("svelte").Component<$$ComponentProps, {}, "activeTab">;
|
|
14
|
+
type Tabs = ReturnType<typeof Tabs>;
|
|
15
|
+
export default Tabs;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
let {
|
|
3
|
+
value = $bindable(),
|
|
4
|
+
class: className = '',
|
|
5
|
+
rows = 3,
|
|
6
|
+
...rest
|
|
7
|
+
}: {
|
|
8
|
+
value?: any;
|
|
9
|
+
class?: string;
|
|
10
|
+
rows?: number;
|
|
11
|
+
[key: string]: any;
|
|
12
|
+
} = $props();
|
|
13
|
+
|
|
14
|
+
const finalClass = $derived(['textarea', className].filter(Boolean).join(' '));
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<textarea class={finalClass} bind:value {rows} {...rest}></textarea>
|
|
18
|
+
|
|
19
|
+
<style>
|
|
20
|
+
@import "../../../../ultra/src/css/parts/components/textarea.css";
|
|
21
|
+
@reference "../reference.css";
|
|
22
|
+
</style>
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount } from 'svelte';
|
|
3
|
+
import { fade, slide } from 'svelte/transition';
|
|
4
|
+
|
|
5
|
+
interface ToastConfig {
|
|
6
|
+
id: string;
|
|
7
|
+
title: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
category?: 'success' | 'warning' | 'destructive' | 'info';
|
|
10
|
+
duration?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let toasts = $state<ToastConfig[]>([]);
|
|
14
|
+
|
|
15
|
+
const addToast = (config: Omit<ToastConfig, 'id'>) => {
|
|
16
|
+
const id = Math.random().toString(36).substring(2, 9);
|
|
17
|
+
const newToast = { ...config, id };
|
|
18
|
+
toasts = [...toasts, newToast];
|
|
19
|
+
|
|
20
|
+
if (config.duration !== 0) {
|
|
21
|
+
setTimeout(() => {
|
|
22
|
+
removeToast(id);
|
|
23
|
+
}, config.duration || 3000);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const removeToast = (id: string) => {
|
|
28
|
+
toasts = toasts.filter(t => t.id !== id);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
onMount(() => {
|
|
32
|
+
const handler = (e: CustomEvent) => {
|
|
33
|
+
if (e.detail && e.detail.config) {
|
|
34
|
+
addToast(e.detail.config);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
document.addEventListener('basecoat:toast', handler as any);
|
|
38
|
+
return () => document.removeEventListener('basecoat:toast', handler as any);
|
|
39
|
+
});
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<section id="toaster" class="toaster fixed top-16 right-0 z-[200] flex max-h-screen w-full flex-col p-4 sm:flex-col md:max-w-[420px]">
|
|
43
|
+
{#each toasts as toast (toast.id)}
|
|
44
|
+
<div
|
|
45
|
+
in:slide={{ axis: 'y', duration: 200 }}
|
|
46
|
+
out:slide={{ axis: 'y', duration: 200 }}
|
|
47
|
+
class={['toast group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all',
|
|
48
|
+
toast.category === 'destructive' ? 'destructive border-destructive bg-destructive text-destructive-foreground' : 'bg-card text-card-foreground border-border'
|
|
49
|
+
].join(' ')}
|
|
50
|
+
>
|
|
51
|
+
<div class="grid gap-1">
|
|
52
|
+
{#if toast.title}
|
|
53
|
+
<div class="text-sm font-semibold">{toast.title}</div>
|
|
54
|
+
{/if}
|
|
55
|
+
{#if toast.description}
|
|
56
|
+
<div class="text-sm opacity-90">{toast.description}</div>
|
|
57
|
+
{/if}
|
|
58
|
+
</div>
|
|
59
|
+
<button
|
|
60
|
+
class="absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100"
|
|
61
|
+
onclick={() => removeToast(toast.id)}
|
|
62
|
+
aria-label="Close"
|
|
63
|
+
>
|
|
64
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
|
|
65
|
+
</button>
|
|
66
|
+
</div>
|
|
67
|
+
{/each}
|
|
68
|
+
</section>
|
|
69
|
+
|
|
70
|
+
<style>
|
|
71
|
+
@import "../../../../ultra/src/css/parts/components/toast.css";
|
|
72
|
+
@reference "../reference.css";
|
|
73
|
+
</style>
|