@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,127 @@
|
|
|
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
|
+
title,
|
|
10
|
+
description,
|
|
11
|
+
children,
|
|
12
|
+
class: className = '',
|
|
13
|
+
...rest
|
|
14
|
+
}: {
|
|
15
|
+
open?: boolean;
|
|
16
|
+
title?: string | Snippet;
|
|
17
|
+
description?: string | Snippet;
|
|
18
|
+
children: Snippet;
|
|
19
|
+
class?: string;
|
|
20
|
+
[key: string]: any;
|
|
21
|
+
} = $props();
|
|
22
|
+
|
|
23
|
+
let container: HTMLDivElement | undefined = $state();
|
|
24
|
+
let isAnimating = $state(false);
|
|
25
|
+
|
|
26
|
+
// Custom spring-like transition for drawer content (slides up from bottom)
|
|
27
|
+
function springSlideUp(node: Element, { duration = 400 }: { duration?: number } = {}) {
|
|
28
|
+
return {
|
|
29
|
+
duration,
|
|
30
|
+
css: (t: number) => {
|
|
31
|
+
const eased = backOut(t);
|
|
32
|
+
const offset = (1 - eased) * 100;
|
|
33
|
+
return `transform: translateY(${offset}%)`;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function slideDown(node: Element, { duration = 300 }: { duration?: number } = {}) {
|
|
39
|
+
return {
|
|
40
|
+
duration,
|
|
41
|
+
css: (t: number) => {
|
|
42
|
+
const eased = cubicOut(t);
|
|
43
|
+
const offset = (1 - eased) * 100;
|
|
44
|
+
return `transform: translateY(${offset}%)`;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function handleClickOutside(event: MouseEvent) {
|
|
50
|
+
if (open && !isAnimating && event.target === container) {
|
|
51
|
+
open = false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function handleKeyDown(event: KeyboardEvent) {
|
|
56
|
+
if (event.key === 'Escape' && open && !isAnimating) {
|
|
57
|
+
open = false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
onMount(() => {
|
|
62
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
63
|
+
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Lock body scroll when open
|
|
67
|
+
$effect(() => {
|
|
68
|
+
if (open) {
|
|
69
|
+
document.body.style.overflow = 'hidden';
|
|
70
|
+
} else {
|
|
71
|
+
document.body.style.overflow = '';
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
</script>
|
|
75
|
+
|
|
76
|
+
{#if open}
|
|
77
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
78
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
79
|
+
<div
|
|
80
|
+
bind:this={container}
|
|
81
|
+
class="drawer fixed inset-0 z-50 flex flex-col items-end sm:items-center justify-end"
|
|
82
|
+
onclick={handleClickOutside}
|
|
83
|
+
>
|
|
84
|
+
<div
|
|
85
|
+
class="absolute inset-0 bg-black/80"
|
|
86
|
+
transition:fade={{ duration: 200 }}
|
|
87
|
+
></div>
|
|
88
|
+
<div
|
|
89
|
+
class={['drawer-content', className].filter(Boolean).join(' ')}
|
|
90
|
+
in:springSlideUp={{ duration: 400 }}
|
|
91
|
+
out:slideDown={{ duration: 300 }}
|
|
92
|
+
onintrostart={() => isAnimating = true}
|
|
93
|
+
onintroend={() => isAnimating = false}
|
|
94
|
+
onoutrostart={() => isAnimating = true}
|
|
95
|
+
onoutroend={() => isAnimating = false}
|
|
96
|
+
{...rest}
|
|
97
|
+
>
|
|
98
|
+
<!-- Drag handle -->
|
|
99
|
+
<div class="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted cursor-grab active:cursor-grabbing"></div>
|
|
100
|
+
|
|
101
|
+
<div class="p-4 text-center sm:text-left">
|
|
102
|
+
{#if title}
|
|
103
|
+
<h2 class="text-lg font-semibold leading-none tracking-tight">
|
|
104
|
+
{#if typeof title === 'string'}{title}{:else}{@render title()}{/if}
|
|
105
|
+
</h2>
|
|
106
|
+
{/if}
|
|
107
|
+
|
|
108
|
+
{#if description}
|
|
109
|
+
<p class="text-sm text-muted-foreground mt-1.5">
|
|
110
|
+
{#if typeof description === 'string'}{description}{:else}{@render description()}{/if}
|
|
111
|
+
</p>
|
|
112
|
+
{/if}
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
{@render children()}
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
{/if}
|
|
119
|
+
|
|
120
|
+
<style>
|
|
121
|
+
@import "../../../../ultra/src/css/parts/custom/sheet.css";
|
|
122
|
+
@reference "../reference.css";
|
|
123
|
+
|
|
124
|
+
.drawer-content {
|
|
125
|
+
@apply fixed z-50 flex flex-col bg-background border border-b-0 rounded-t-[10px] bottom-0 inset-x-0 h-auto max-h-[96vh];
|
|
126
|
+
}
|
|
127
|
+
</style>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
open?: boolean;
|
|
4
|
+
title?: string | Snippet;
|
|
5
|
+
description?: string | Snippet;
|
|
6
|
+
children: Snippet;
|
|
7
|
+
class?: string;
|
|
8
|
+
[key: string]: any;
|
|
9
|
+
};
|
|
10
|
+
declare const Drawer: import("svelte").Component<$$ComponentProps, {}, "open">;
|
|
11
|
+
type Drawer = ReturnType<typeof Drawer>;
|
|
12
|
+
export default Drawer;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { onMount } from 'svelte';
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
open = $bindable(false),
|
|
7
|
+
trigger,
|
|
8
|
+
children,
|
|
9
|
+
align = 'start',
|
|
10
|
+
class: className = '',
|
|
11
|
+
...rest
|
|
12
|
+
}: {
|
|
13
|
+
open?: boolean;
|
|
14
|
+
trigger: Snippet<{ open: boolean }>;
|
|
15
|
+
children: Snippet;
|
|
16
|
+
align?: 'start' | 'end';
|
|
17
|
+
class?: string;
|
|
18
|
+
[key: string]: any;
|
|
19
|
+
} = $props();
|
|
20
|
+
|
|
21
|
+
let container: HTMLDivElement;
|
|
22
|
+
|
|
23
|
+
function handleClickOutside(event: MouseEvent) {
|
|
24
|
+
if (open && container && !container.contains(event.target as Node)) {
|
|
25
|
+
open = false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
onMount(() => {
|
|
30
|
+
document.addEventListener('click', handleClickOutside);
|
|
31
|
+
return () => document.removeEventListener('click', handleClickOutside);
|
|
32
|
+
});
|
|
33
|
+
const finalClass = $derived(['dropdown-menu relative inline-block', className].filter(Boolean).join(' '));
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<div bind:this={container} class={['dropdown-menu relative inline-block', className].filter(Boolean).join(' ')} {...rest}>
|
|
37
|
+
<div
|
|
38
|
+
role="button"
|
|
39
|
+
tabindex="0"
|
|
40
|
+
aria-expanded={open}
|
|
41
|
+
onclick={(e) => { e.stopPropagation(); open = !open; }}
|
|
42
|
+
onkeydown={(e) => e.key === 'Enter' && (open = !open)}
|
|
43
|
+
>
|
|
44
|
+
{@render trigger({ open })}
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
{#if open}
|
|
48
|
+
<div
|
|
49
|
+
role="menu"
|
|
50
|
+
data-popover
|
|
51
|
+
data-align={align}
|
|
52
|
+
class="absolute z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 mt-1"
|
|
53
|
+
>
|
|
54
|
+
{@render children()}
|
|
55
|
+
</div>
|
|
56
|
+
{/if}
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<style>
|
|
60
|
+
@import "../../../../ultra/src/css/parts/components/dropdown-menu.css";
|
|
61
|
+
@reference "../reference.css";
|
|
62
|
+
</style>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
open?: boolean;
|
|
4
|
+
trigger: Snippet<{
|
|
5
|
+
open: boolean;
|
|
6
|
+
}>;
|
|
7
|
+
children: Snippet;
|
|
8
|
+
align?: 'start' | 'end';
|
|
9
|
+
class?: string;
|
|
10
|
+
[key: string]: any;
|
|
11
|
+
};
|
|
12
|
+
declare const DropdownMenu: import("svelte").Component<$$ComponentProps, {}, "open">;
|
|
13
|
+
type DropdownMenu = ReturnType<typeof DropdownMenu>;
|
|
14
|
+
export default DropdownMenu;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
let {
|
|
5
|
+
children,
|
|
6
|
+
icon,
|
|
7
|
+
title,
|
|
8
|
+
description,
|
|
9
|
+
class: className = '',
|
|
10
|
+
...rest
|
|
11
|
+
}: {
|
|
12
|
+
children?: Snippet;
|
|
13
|
+
icon?: Snippet;
|
|
14
|
+
title?: Snippet | string;
|
|
15
|
+
description?: Snippet | string;
|
|
16
|
+
class?: string;
|
|
17
|
+
[key: string]: any;
|
|
18
|
+
} = $props();
|
|
19
|
+
|
|
20
|
+
const finalClass = $derived([
|
|
21
|
+
'p-12 border border-dashed rounded-lg bg-card text-center flex flex-col items-center justify-center',
|
|
22
|
+
className
|
|
23
|
+
].filter(Boolean).join(' '));
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<div class={finalClass} {...rest}>
|
|
27
|
+
{#if icon}
|
|
28
|
+
<div class="h-10 w-10 rounded-full bg-muted flex items-center justify-center mb-3 text-muted-foreground">
|
|
29
|
+
{@render icon()}
|
|
30
|
+
</div>
|
|
31
|
+
{/if}
|
|
32
|
+
|
|
33
|
+
{#if title}
|
|
34
|
+
<h4 class="font-medium">
|
|
35
|
+
{#if typeof title === 'string'}
|
|
36
|
+
{title}
|
|
37
|
+
{:else}
|
|
38
|
+
{@render title()}
|
|
39
|
+
{/if}
|
|
40
|
+
</h4>
|
|
41
|
+
{/if}
|
|
42
|
+
|
|
43
|
+
{#if description}
|
|
44
|
+
<p class="text-sm text-muted-foreground">
|
|
45
|
+
{#if typeof description === 'string'}
|
|
46
|
+
{description}
|
|
47
|
+
{:else}
|
|
48
|
+
{@render description()}
|
|
49
|
+
{/if}
|
|
50
|
+
</p>
|
|
51
|
+
{/if}
|
|
52
|
+
|
|
53
|
+
{#if children}
|
|
54
|
+
<div class="mt-4">
|
|
55
|
+
{@render children()}
|
|
56
|
+
</div>
|
|
57
|
+
{/if}
|
|
58
|
+
</div>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
children?: Snippet;
|
|
4
|
+
icon?: Snippet;
|
|
5
|
+
title?: Snippet | string;
|
|
6
|
+
description?: Snippet | string;
|
|
7
|
+
class?: string;
|
|
8
|
+
[key: string]: any;
|
|
9
|
+
};
|
|
10
|
+
declare const Empty: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
11
|
+
type Empty = ReturnType<typeof Empty>;
|
|
12
|
+
export default Empty;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
let {
|
|
3
|
+
value = $bindable(),
|
|
4
|
+
type = 'text',
|
|
5
|
+
class: className = '',
|
|
6
|
+
...rest
|
|
7
|
+
}: {
|
|
8
|
+
value?: any;
|
|
9
|
+
type?: string;
|
|
10
|
+
class?: string;
|
|
11
|
+
[key: string]: any;
|
|
12
|
+
} = $props();
|
|
13
|
+
|
|
14
|
+
const finalClass = $derived(['input', className].filter(Boolean).join(' '));
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<input {type} class={finalClass} bind:value {...rest} />
|
|
18
|
+
|
|
19
|
+
<style>
|
|
20
|
+
@import "../../../../ultra/src/css/parts/components/input.css";
|
|
21
|
+
@reference "../reference.css";
|
|
22
|
+
</style>
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
let {
|
|
5
|
+
length = 6,
|
|
6
|
+
value = $bindable(''),
|
|
7
|
+
separator,
|
|
8
|
+
separatorIndices = [],
|
|
9
|
+
class: className = '',
|
|
10
|
+
...rest
|
|
11
|
+
}: {
|
|
12
|
+
length?: number;
|
|
13
|
+
value?: string;
|
|
14
|
+
separator?: Snippet;
|
|
15
|
+
separatorIndices?: number[];
|
|
16
|
+
class?: string;
|
|
17
|
+
[key: string]: any;
|
|
18
|
+
} = $props();
|
|
19
|
+
|
|
20
|
+
// Use a reactive object to store input refs to avoid binding_property_non_reactive warning
|
|
21
|
+
let inputRefs = $state<Record<number, HTMLInputElement>>({});
|
|
22
|
+
let values = $state<string[]>([]);
|
|
23
|
+
|
|
24
|
+
// Helper to get input by index
|
|
25
|
+
function getInput(index: number): HTMLInputElement | undefined {
|
|
26
|
+
return inputRefs[index];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Initialize values array when length changes
|
|
30
|
+
$effect(() => {
|
|
31
|
+
if (values.length !== length) {
|
|
32
|
+
values = Array(length).fill('');
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Sync external value to internal values
|
|
37
|
+
$effect(() => {
|
|
38
|
+
if (value) {
|
|
39
|
+
const chars = value.split('');
|
|
40
|
+
values = Array(length).fill('').map((_, i) => chars[i] || '');
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
function updateValue() {
|
|
45
|
+
value = values.join('');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function handleFocus(e: FocusEvent) {
|
|
49
|
+
// Select content on focus to allow easy overwrite
|
|
50
|
+
const input = e.target as HTMLInputElement;
|
|
51
|
+
input.select();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function handleInput(e: Event, index: number) {
|
|
55
|
+
const input = e.target as HTMLInputElement;
|
|
56
|
+
const val = input.value;
|
|
57
|
+
|
|
58
|
+
// Handle clearing (empty value)
|
|
59
|
+
if (val === '') {
|
|
60
|
+
values[index] = '';
|
|
61
|
+
updateValue();
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// If multiple chars (e.g. fast typing), take the last one
|
|
66
|
+
if (val.length > 1) {
|
|
67
|
+
values[index] = val.slice(-1);
|
|
68
|
+
input.value = values[index];
|
|
69
|
+
} else {
|
|
70
|
+
values[index] = val;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
updateValue();
|
|
74
|
+
|
|
75
|
+
// Move to next if we have a value and not at the end
|
|
76
|
+
if (values[index] && index < length - 1) {
|
|
77
|
+
getInput(index + 1)?.focus();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function handleKeyDown(e: KeyboardEvent, index: number) {
|
|
82
|
+
const input = e.target as HTMLInputElement;
|
|
83
|
+
|
|
84
|
+
if (e.key === 'Backspace') {
|
|
85
|
+
e.preventDefault();
|
|
86
|
+
|
|
87
|
+
if (values[index] !== '') {
|
|
88
|
+
values[index] = '';
|
|
89
|
+
input.value = '';
|
|
90
|
+
updateValue();
|
|
91
|
+
} else if (index > 0) {
|
|
92
|
+
getInput(index - 1)?.focus();
|
|
93
|
+
}
|
|
94
|
+
} else if (e.key === 'ArrowLeft' && index > 0) {
|
|
95
|
+
e.preventDefault();
|
|
96
|
+
getInput(index - 1)?.focus();
|
|
97
|
+
} else if (e.key === 'ArrowRight' && index < length - 1) {
|
|
98
|
+
e.preventDefault();
|
|
99
|
+
getInput(index + 1)?.focus();
|
|
100
|
+
} else if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
|
101
|
+
// If cursor is not selecting text and we have value, clear it first to ensure overwrite
|
|
102
|
+
if (values[index] && input.selectionStart === input.selectionEnd) {
|
|
103
|
+
values[index] = '';
|
|
104
|
+
input.value = '';
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function handlePaste(e: ClipboardEvent, startIndex: number) {
|
|
110
|
+
e.preventDefault();
|
|
111
|
+
const pasteData = e.clipboardData?.getData('text') || '';
|
|
112
|
+
const chars = pasteData.split('');
|
|
113
|
+
|
|
114
|
+
let currentIndex = startIndex;
|
|
115
|
+
chars.forEach((char) => {
|
|
116
|
+
if (currentIndex < length) {
|
|
117
|
+
values[currentIndex] = char;
|
|
118
|
+
const input = getInput(currentIndex);
|
|
119
|
+
if (input) {
|
|
120
|
+
input.value = char;
|
|
121
|
+
}
|
|
122
|
+
currentIndex++;
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
updateValue();
|
|
127
|
+
|
|
128
|
+
// Focus the input after the last pasted character
|
|
129
|
+
if (currentIndex < length) {
|
|
130
|
+
getInput(currentIndex)?.focus();
|
|
131
|
+
} else {
|
|
132
|
+
getInput(length - 1)?.focus();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function getGroups(): number[][] {
|
|
137
|
+
const groups: number[][] = [];
|
|
138
|
+
let currentGroup: number[] = [];
|
|
139
|
+
|
|
140
|
+
for (let i = 0; i < length; i++) {
|
|
141
|
+
if (i > 0 && separatorIndices.includes(i)) {
|
|
142
|
+
groups.push(currentGroup);
|
|
143
|
+
currentGroup = [];
|
|
144
|
+
}
|
|
145
|
+
currentGroup.push(i);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (currentGroup.length > 0) {
|
|
149
|
+
groups.push(currentGroup);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return groups;
|
|
153
|
+
}
|
|
154
|
+
</script>
|
|
155
|
+
|
|
156
|
+
<div
|
|
157
|
+
class={['input-otp flex items-center gap-2', className].filter(Boolean).join(' ')}
|
|
158
|
+
{...rest}
|
|
159
|
+
>
|
|
160
|
+
{#each getGroups() as group, groupIndex}
|
|
161
|
+
{#if groupIndex > 0}
|
|
162
|
+
<div class="input-otp-separator text-muted-foreground">
|
|
163
|
+
{#if separator}{@render separator()}{/if}
|
|
164
|
+
</div>
|
|
165
|
+
{/if}
|
|
166
|
+
<div class="input-otp-group flex items-center">
|
|
167
|
+
{#each group as inputIndex}
|
|
168
|
+
<input
|
|
169
|
+
bind:this={inputRefs[inputIndex]}
|
|
170
|
+
type="text"
|
|
171
|
+
inputmode="text"
|
|
172
|
+
autocomplete="one-time-code"
|
|
173
|
+
class="input w-10 h-10 text-center p-0"
|
|
174
|
+
maxlength="1"
|
|
175
|
+
value={values[inputIndex]}
|
|
176
|
+
onfocus={handleFocus}
|
|
177
|
+
oninput={(e) => handleInput(e, inputIndex)}
|
|
178
|
+
onkeydown={(e) => handleKeyDown(e, inputIndex)}
|
|
179
|
+
onpaste={(e) => handlePaste(e, inputIndex)}
|
|
180
|
+
/>
|
|
181
|
+
{/each}
|
|
182
|
+
</div>
|
|
183
|
+
{/each}
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
<style>
|
|
187
|
+
@import "../../../../ultra/src/css/parts/custom/input-otp.css";
|
|
188
|
+
@reference "../reference.css";
|
|
189
|
+
</style>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
length?: number;
|
|
4
|
+
value?: string;
|
|
5
|
+
separator?: Snippet;
|
|
6
|
+
separatorIndices?: number[];
|
|
7
|
+
class?: string;
|
|
8
|
+
[key: string]: any;
|
|
9
|
+
};
|
|
10
|
+
declare const InputOTP: import("svelte").Component<$$ComponentProps, {}, "value">;
|
|
11
|
+
type InputOTP = ReturnType<typeof InputOTP>;
|
|
12
|
+
export default InputOTP;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
let {
|
|
5
|
+
children,
|
|
6
|
+
icon,
|
|
7
|
+
title,
|
|
8
|
+
description,
|
|
9
|
+
actions,
|
|
10
|
+
class: className = '',
|
|
11
|
+
containerClass = '',
|
|
12
|
+
...rest
|
|
13
|
+
}: {
|
|
14
|
+
children?: Snippet;
|
|
15
|
+
icon?: Snippet;
|
|
16
|
+
title?: Snippet | string;
|
|
17
|
+
description?: Snippet | string;
|
|
18
|
+
actions?: Snippet;
|
|
19
|
+
class?: string;
|
|
20
|
+
containerClass?: string;
|
|
21
|
+
[key: string]: any;
|
|
22
|
+
} = $props();
|
|
23
|
+
|
|
24
|
+
const finalClass = $derived([
|
|
25
|
+
'flex items-center gap-3 p-3 hover:bg-muted cursor-pointer transition-colors border-b last:border-0',
|
|
26
|
+
className
|
|
27
|
+
].filter(Boolean).join(' '));
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<div class={finalClass} {...rest}>
|
|
31
|
+
{#if icon}
|
|
32
|
+
<div class="h-8 w-8 rounded-full bg-accent flex items-center justify-center shrink-0">
|
|
33
|
+
{@render icon()}
|
|
34
|
+
</div>
|
|
35
|
+
{/if}
|
|
36
|
+
|
|
37
|
+
<div class="flex-1 min-w-0">
|
|
38
|
+
{#if title}
|
|
39
|
+
<div class="text-sm font-medium truncate">
|
|
40
|
+
{#if typeof title === 'string'}
|
|
41
|
+
{title}
|
|
42
|
+
{:else}
|
|
43
|
+
{@render title()}
|
|
44
|
+
{/if}
|
|
45
|
+
</div>
|
|
46
|
+
{/if}
|
|
47
|
+
{#if description}
|
|
48
|
+
<div class="text-xs text-muted-foreground truncate">
|
|
49
|
+
{#if typeof description === 'string'}
|
|
50
|
+
{description}
|
|
51
|
+
{:else}
|
|
52
|
+
{@render description()}
|
|
53
|
+
{/if}
|
|
54
|
+
</div>
|
|
55
|
+
{/if}
|
|
56
|
+
{@render children?.()}
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
{#if actions}
|
|
60
|
+
<div class="flex items-center gap-2 shrink-0">
|
|
61
|
+
{@render actions()}
|
|
62
|
+
</div>
|
|
63
|
+
{/if}
|
|
64
|
+
</div>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
children?: Snippet;
|
|
4
|
+
icon?: Snippet;
|
|
5
|
+
title?: Snippet | string;
|
|
6
|
+
description?: Snippet | string;
|
|
7
|
+
actions?: Snippet;
|
|
8
|
+
class?: string;
|
|
9
|
+
containerClass?: string;
|
|
10
|
+
[key: string]: any;
|
|
11
|
+
};
|
|
12
|
+
declare const Item: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
13
|
+
type Item = ReturnType<typeof Item>;
|
|
14
|
+
export default Item;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
let {
|
|
5
|
+
children,
|
|
6
|
+
class: className = '',
|
|
7
|
+
...rest
|
|
8
|
+
}: {
|
|
9
|
+
children?: Snippet | string;
|
|
10
|
+
class?: string;
|
|
11
|
+
[key: string]: any;
|
|
12
|
+
} = $props();
|
|
13
|
+
|
|
14
|
+
const finalClass = $derived(['kbd', className].filter(Boolean).join(' '));
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<kbd class={finalClass} {...rest}>
|
|
18
|
+
{#if typeof children === 'string'}
|
|
19
|
+
{children}
|
|
20
|
+
{:else if children}
|
|
21
|
+
{@render children()}
|
|
22
|
+
{/if}
|
|
23
|
+
</kbd>
|
|
24
|
+
|
|
25
|
+
<style>
|
|
26
|
+
@import "../../../../ultra/src/css/parts/components/kbd.css";
|
|
27
|
+
@reference "../reference.css";
|
|
28
|
+
</style>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
children?: Snippet | string;
|
|
4
|
+
class?: string;
|
|
5
|
+
[key: string]: any;
|
|
6
|
+
};
|
|
7
|
+
declare const Kbd: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
8
|
+
type Kbd = ReturnType<typeof Kbd>;
|
|
9
|
+
export default Kbd;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
let {
|
|
5
|
+
children,
|
|
6
|
+
for: forId,
|
|
7
|
+
class: className = '',
|
|
8
|
+
...rest
|
|
9
|
+
}: {
|
|
10
|
+
children?: Snippet | string;
|
|
11
|
+
for?: string;
|
|
12
|
+
class?: string;
|
|
13
|
+
[key: string]: any;
|
|
14
|
+
} = $props();
|
|
15
|
+
|
|
16
|
+
const finalClass = $derived(['label', className].filter(Boolean).join(' '));
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<label class={finalClass} for={forId} {...rest}>
|
|
20
|
+
{#if typeof children === 'string'}
|
|
21
|
+
{children}
|
|
22
|
+
{:else if children}
|
|
23
|
+
{@render children()}
|
|
24
|
+
{/if}
|
|
25
|
+
</label>
|
|
26
|
+
|
|
27
|
+
<style>
|
|
28
|
+
@import "../../../../ultra/src/css/parts/components/label.css";
|
|
29
|
+
@reference "../reference.css";
|
|
30
|
+
</style>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
children?: Snippet | string;
|
|
4
|
+
for?: string;
|
|
5
|
+
class?: string;
|
|
6
|
+
[key: string]: any;
|
|
7
|
+
};
|
|
8
|
+
declare const Label: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
9
|
+
type Label = ReturnType<typeof Label>;
|
|
10
|
+
export default Label;
|