@lyeve-labs/ui-kit 0.8.2
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/LICENSE +21 -0
- package/README.md +199 -0
- package/dist/components/Accordion.svelte +48 -0
- package/dist/components/Accordion.svelte.d.ts +10 -0
- package/dist/components/AccordionItem.svelte +55 -0
- package/dist/components/AccordionItem.svelte.d.ts +10 -0
- package/dist/components/Alert.svelte +60 -0
- package/dist/components/Alert.svelte.d.ts +13 -0
- package/dist/components/Autocomplete.svelte +198 -0
- package/dist/components/Autocomplete.svelte.d.ts +23 -0
- package/dist/components/Avatar.svelte +65 -0
- package/dist/components/Avatar.svelte.d.ts +9 -0
- package/dist/components/AvatarGroup.svelte +45 -0
- package/dist/components/AvatarGroup.svelte.d.ts +14 -0
- package/dist/components/Badge.svelte +48 -0
- package/dist/components/Badge.svelte.d.ts +13 -0
- package/dist/components/Banner.svelte +73 -0
- package/dist/components/Banner.svelte.d.ts +13 -0
- package/dist/components/Breadcrumb.svelte +47 -0
- package/dist/components/Breadcrumb.svelte.d.ts +10 -0
- package/dist/components/Button.svelte +71 -0
- package/dist/components/Button.svelte.d.ts +18 -0
- package/dist/components/ButtonGroup.svelte +33 -0
- package/dist/components/ButtonGroup.svelte.d.ts +9 -0
- package/dist/components/Card.svelte +72 -0
- package/dist/components/Card.svelte.d.ts +17 -0
- package/dist/components/Checkbox.svelte +80 -0
- package/dist/components/Checkbox.svelte.d.ts +14 -0
- package/dist/components/DatePicker.svelte +274 -0
- package/dist/components/DatePicker.svelte.d.ts +17 -0
- package/dist/components/Divider.svelte +23 -0
- package/dist/components/Divider.svelte.d.ts +8 -0
- package/dist/components/Drawer.svelte +120 -0
- package/dist/components/Drawer.svelte.d.ts +16 -0
- package/dist/components/Dropdown.svelte +88 -0
- package/dist/components/Dropdown.svelte.d.ts +23 -0
- package/dist/components/EmptyState.svelte +51 -0
- package/dist/components/EmptyState.svelte.d.ts +13 -0
- package/dist/components/FileInput.svelte +102 -0
- package/dist/components/FileInput.svelte.d.ts +15 -0
- package/dist/components/Indicator.svelte +38 -0
- package/dist/components/Indicator.svelte.d.ts +11 -0
- package/dist/components/Input.svelte +60 -0
- package/dist/components/Input.svelte.d.ts +17 -0
- package/dist/components/Kbd.svelte +18 -0
- package/dist/components/Kbd.svelte.d.ts +8 -0
- package/dist/components/Label.svelte +30 -0
- package/dist/components/Label.svelte.d.ts +11 -0
- package/dist/components/Modal.svelte +105 -0
- package/dist/components/Modal.svelte.d.ts +14 -0
- package/dist/components/MultiSelect.svelte +212 -0
- package/dist/components/MultiSelect.svelte.d.ts +22 -0
- package/dist/components/NumberInput.svelte +98 -0
- package/dist/components/NumberInput.svelte.d.ts +15 -0
- package/dist/components/PageHeader.svelte +22 -0
- package/dist/components/PageHeader.svelte.d.ts +10 -0
- package/dist/components/Pagination.svelte +112 -0
- package/dist/components/Pagination.svelte.d.ts +10 -0
- package/dist/components/Progress.svelte +70 -0
- package/dist/components/Progress.svelte.d.ts +15 -0
- package/dist/components/Radio.svelte +65 -0
- package/dist/components/Radio.svelte.d.ts +14 -0
- package/dist/components/RadioGroup.svelte +96 -0
- package/dist/components/RadioGroup.svelte.d.ts +22 -0
- package/dist/components/SearchInput.svelte +81 -0
- package/dist/components/SearchInput.svelte.d.ts +10 -0
- package/dist/components/Select.svelte +69 -0
- package/dist/components/Select.svelte.d.ts +18 -0
- package/dist/components/Skeleton.svelte +20 -0
- package/dist/components/Skeleton.svelte.d.ts +9 -0
- package/dist/components/Spinner.svelte +19 -0
- package/dist/components/Spinner.svelte.d.ts +7 -0
- package/dist/components/Stat.svelte +45 -0
- package/dist/components/Stat.svelte.d.ts +14 -0
- package/dist/components/StepIndicator.svelte +142 -0
- package/dist/components/StepIndicator.svelte.d.ts +13 -0
- package/dist/components/Table.svelte +36 -0
- package/dist/components/Table.svelte.d.ts +11 -0
- package/dist/components/Tabs.svelte +39 -0
- package/dist/components/Tabs.svelte.d.ts +13 -0
- package/dist/components/Tag.svelte +56 -0
- package/dist/components/Tag.svelte.d.ts +11 -0
- package/dist/components/Textarea.svelte +75 -0
- package/dist/components/Textarea.svelte.d.ts +26 -0
- package/dist/components/ThemeToggle.svelte +33 -0
- package/dist/components/ThemeToggle.svelte.d.ts +6 -0
- package/dist/components/Toaster.svelte +54 -0
- package/dist/components/Toaster.svelte.d.ts +18 -0
- package/dist/components/Toggle.svelte +74 -0
- package/dist/components/Toggle.svelte.d.ts +11 -0
- package/dist/components/Tooltip.svelte +44 -0
- package/dist/components/Tooltip.svelte.d.ts +11 -0
- package/dist/components/dialog/ConfirmDialog.svelte +95 -0
- package/dist/components/dialog/ConfirmDialog.svelte.d.ts +8 -0
- package/dist/components/dialog/Dialog.svelte +225 -0
- package/dist/components/dialog/Dialog.svelte.d.ts +9 -0
- package/dist/components/dialog/DialogContainer.svelte +17 -0
- package/dist/components/dialog/DialogContainer.svelte.d.ts +18 -0
- package/dist/components/dialog/dialog-manager.svelte.d.ts +54 -0
- package/dist/components/dialog/dialog-manager.svelte.js +164 -0
- package/dist/components/dialog/types.d.ts +33 -0
- package/dist/components/dialog/types.js +10 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.js +72 -0
- package/dist/stores/toast.svelte.d.ts +18 -0
- package/dist/stores/toast.svelte.js +28 -0
- package/dist/styles/theme.css +148 -0
- package/dist/utils/cn.d.ts +13 -0
- package/dist/utils/cn.js +24 -0
- package/dist/utils/theme.d.ts +26 -0
- package/dist/utils/theme.js +55 -0
- package/package.json +81 -0
- package/src/lib/styles/theme.css +148 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
let {
|
|
3
|
+
name,
|
|
4
|
+
src,
|
|
5
|
+
size = 'md',
|
|
6
|
+
class: cls = '',
|
|
7
|
+
}: {
|
|
8
|
+
name: string;
|
|
9
|
+
src?: string;
|
|
10
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
11
|
+
class?: string;
|
|
12
|
+
} = $props();
|
|
13
|
+
|
|
14
|
+
const sizes: Record<'xs' | 'sm' | 'md' | 'lg' | 'xl', { wrap: string; text: string }> = {
|
|
15
|
+
xs: { wrap: 'w-6 h-6', text: 'text-[9px]' },
|
|
16
|
+
sm: { wrap: 'w-7 h-7', text: 'text-[10px]' },
|
|
17
|
+
md: { wrap: 'w-8 h-8', text: 'text-xs' },
|
|
18
|
+
lg: { wrap: 'w-10 h-10', text: 'text-sm' },
|
|
19
|
+
xl: { wrap: 'w-14 h-14', text: 'text-base' },
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const hues = [
|
|
23
|
+
'bg-violet/80',
|
|
24
|
+
'bg-brand/60',
|
|
25
|
+
'bg-success/70',
|
|
26
|
+
'bg-warn/60',
|
|
27
|
+
'bg-danger/60',
|
|
28
|
+
'bg-violet/50',
|
|
29
|
+
'bg-brand/40',
|
|
30
|
+
'bg-success/50',
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
let imgError = $state(false);
|
|
34
|
+
|
|
35
|
+
let initials = $derived(
|
|
36
|
+
(name ?? '')
|
|
37
|
+
.trim()
|
|
38
|
+
.split(/\s+/)
|
|
39
|
+
.slice(0, 2)
|
|
40
|
+
.map((w) => w[0]?.toUpperCase() ?? '')
|
|
41
|
+
.join(''),
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
let hue = $derived(hues[(name.charCodeAt(0) || 0) % hues.length]);
|
|
45
|
+
let s = $derived(sizes[size]);
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<span
|
|
49
|
+
class="inline-flex items-center justify-center rounded-full font-semibold shrink-0
|
|
50
|
+
{s.wrap} {hue} text-ink {cls}"
|
|
51
|
+
title={name}
|
|
52
|
+
>
|
|
53
|
+
{#if src && !imgError}
|
|
54
|
+
<img
|
|
55
|
+
{src}
|
|
56
|
+
alt={name}
|
|
57
|
+
class="w-full h-full rounded-full object-cover"
|
|
58
|
+
onerror={() => {
|
|
59
|
+
imgError = true;
|
|
60
|
+
}}
|
|
61
|
+
/>
|
|
62
|
+
{:else}
|
|
63
|
+
<span class={s.text}>{initials}</span>
|
|
64
|
+
{/if}
|
|
65
|
+
</span>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
name: string;
|
|
3
|
+
src?: string;
|
|
4
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
5
|
+
class?: string;
|
|
6
|
+
};
|
|
7
|
+
declare const Avatar: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
8
|
+
type Avatar = ReturnType<typeof Avatar>;
|
|
9
|
+
export default Avatar;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Avatar from './Avatar.svelte';
|
|
3
|
+
|
|
4
|
+
type AvatarSize = 'xs' | 'sm' | 'md' | 'lg';
|
|
5
|
+
|
|
6
|
+
interface User {
|
|
7
|
+
name: string;
|
|
8
|
+
src?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
users: User[];
|
|
13
|
+
max?: number;
|
|
14
|
+
size?: AvatarSize;
|
|
15
|
+
class?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let { users, max = 4, size = 'sm', class: cls = '' }: Props = $props();
|
|
19
|
+
|
|
20
|
+
let visible = $derived(users.slice(0, max));
|
|
21
|
+
let overflow = $derived(users.length - max);
|
|
22
|
+
|
|
23
|
+
const overflowSizes: Record<AvatarSize, string> = {
|
|
24
|
+
xs: 'w-6 h-6 text-[9px]',
|
|
25
|
+
sm: 'w-7 h-7 text-[10px]',
|
|
26
|
+
md: 'w-8 h-8 text-xs',
|
|
27
|
+
lg: 'w-10 h-10 text-sm',
|
|
28
|
+
};
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<div class="flex items-center {cls}">
|
|
32
|
+
{#each visible as user}
|
|
33
|
+
<span class="-ml-1.5 inline-block rounded-full ring-2 ring-ink first:ml-0">
|
|
34
|
+
<Avatar name={user.name} src={user.src} {size} />
|
|
35
|
+
</span>
|
|
36
|
+
{/each}
|
|
37
|
+
|
|
38
|
+
{#if overflow > 0}
|
|
39
|
+
<span
|
|
40
|
+
class="-ml-1.5 inline-flex shrink-0 items-center justify-center rounded-full
|
|
41
|
+
bg-surface-2 font-semibold text-faint ring-2 ring-ink
|
|
42
|
+
{overflowSizes[size]}">+{overflow}</span
|
|
43
|
+
>
|
|
44
|
+
{/if}
|
|
45
|
+
</div>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
type AvatarSize = 'xs' | 'sm' | 'md' | 'lg';
|
|
2
|
+
interface User {
|
|
3
|
+
name: string;
|
|
4
|
+
src?: string;
|
|
5
|
+
}
|
|
6
|
+
interface Props {
|
|
7
|
+
users: User[];
|
|
8
|
+
max?: number;
|
|
9
|
+
size?: AvatarSize;
|
|
10
|
+
class?: string;
|
|
11
|
+
}
|
|
12
|
+
declare const AvatarGroup: import("svelte").Component<Props, {}, "">;
|
|
13
|
+
type AvatarGroup = ReturnType<typeof AvatarGroup>;
|
|
14
|
+
export default AvatarGroup;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
type Tone = 'neutral' | 'brand' | 'violet' | 'success' | 'warn' | 'danger';
|
|
5
|
+
type Size = 'sm' | 'md';
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
tone?: Tone;
|
|
9
|
+
size?: Size;
|
|
10
|
+
dot?: boolean;
|
|
11
|
+
class?: string;
|
|
12
|
+
children: Snippet;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let { tone = 'neutral', size = 'sm', dot = false, class: klass = '', children }: Props = $props();
|
|
16
|
+
|
|
17
|
+
const tones: Record<Tone, string> = {
|
|
18
|
+
neutral: 'bg-surface-2 text-muted border-line',
|
|
19
|
+
brand: 'bg-brand/10 text-brand border-brand/20',
|
|
20
|
+
violet: 'bg-violet/10 text-violet border-violet/20',
|
|
21
|
+
success: 'bg-success/10 text-success border-success/20',
|
|
22
|
+
warn: 'bg-warn/10 text-warn border-warn/20',
|
|
23
|
+
danger: 'bg-danger/10 text-danger border-danger/20',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const dotColor: Record<Tone, string> = {
|
|
27
|
+
neutral: 'bg-muted',
|
|
28
|
+
brand: 'bg-brand',
|
|
29
|
+
violet: 'bg-violet',
|
|
30
|
+
success: 'bg-success',
|
|
31
|
+
warn: 'bg-warn',
|
|
32
|
+
danger: 'bg-danger',
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const sizes: Record<Size, string> = {
|
|
36
|
+
sm: 'text-xs px-2 py-0.5 gap-1.5',
|
|
37
|
+
md: 'text-sm px-2.5 py-1 gap-1.5',
|
|
38
|
+
};
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<span
|
|
42
|
+
class="inline-flex items-center font-medium rounded-full border {tones[tone]} {sizes[
|
|
43
|
+
size
|
|
44
|
+
]} {klass}"
|
|
45
|
+
>
|
|
46
|
+
{#if dot}<span class="w-1.5 h-1.5 rounded-full {dotColor[tone]}"></span>{/if}
|
|
47
|
+
{@render children()}
|
|
48
|
+
</span>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type Tone = 'neutral' | 'brand' | 'violet' | 'success' | 'warn' | 'danger';
|
|
3
|
+
type Size = 'sm' | 'md';
|
|
4
|
+
interface Props {
|
|
5
|
+
tone?: Tone;
|
|
6
|
+
size?: Size;
|
|
7
|
+
dot?: boolean;
|
|
8
|
+
class?: string;
|
|
9
|
+
children: Snippet;
|
|
10
|
+
}
|
|
11
|
+
declare const Badge: import("svelte").Component<Props, {}, "">;
|
|
12
|
+
type Badge = ReturnType<typeof Badge>;
|
|
13
|
+
export default Badge;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
type Tone = 'info' | 'success' | 'warn' | 'danger' | 'neutral';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
tone?: Tone;
|
|
8
|
+
dismissible?: boolean;
|
|
9
|
+
ondismiss?: () => void;
|
|
10
|
+
class?: string;
|
|
11
|
+
children: Snippet;
|
|
12
|
+
action?: Snippet;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let {
|
|
16
|
+
tone = 'info',
|
|
17
|
+
dismissible = false,
|
|
18
|
+
ondismiss = undefined,
|
|
19
|
+
class: cls = '',
|
|
20
|
+
children,
|
|
21
|
+
action,
|
|
22
|
+
}: Props = $props();
|
|
23
|
+
|
|
24
|
+
let visible = $state(true);
|
|
25
|
+
|
|
26
|
+
function dismiss() {
|
|
27
|
+
visible = false;
|
|
28
|
+
ondismiss?.();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const tones: Record<Tone, string> = {
|
|
32
|
+
info: 'bg-brand/10 border-brand/20',
|
|
33
|
+
success: 'bg-success/10 border-success/20',
|
|
34
|
+
warn: 'bg-warn/10 border-warn/20',
|
|
35
|
+
danger: 'bg-danger/10 border-danger/20',
|
|
36
|
+
neutral: 'bg-surface-2 border-line',
|
|
37
|
+
};
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
{#if visible}
|
|
41
|
+
<div
|
|
42
|
+
role="status"
|
|
43
|
+
class="relative flex items-center gap-3 border-b px-4 py-3 text-sm {tones[tone]} {cls}"
|
|
44
|
+
>
|
|
45
|
+
<div class="flex flex-1 items-center justify-center gap-3 text-fg">
|
|
46
|
+
{@render children()}
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
{#if action}
|
|
50
|
+
<div class="shrink-0">{@render action()}</div>
|
|
51
|
+
{/if}
|
|
52
|
+
|
|
53
|
+
{#if dismissible}
|
|
54
|
+
<button
|
|
55
|
+
type="button"
|
|
56
|
+
onclick={dismiss}
|
|
57
|
+
aria-label="Dismiss"
|
|
58
|
+
class="shrink-0 text-faint transition-colors hover:text-fg"
|
|
59
|
+
>
|
|
60
|
+
<svg
|
|
61
|
+
width="16"
|
|
62
|
+
height="16"
|
|
63
|
+
viewBox="0 0 24 24"
|
|
64
|
+
fill="none"
|
|
65
|
+
stroke="currentColor"
|
|
66
|
+
stroke-width="2"
|
|
67
|
+
stroke-linecap="round"
|
|
68
|
+
aria-hidden="true"><path d="M18 6L6 18M6 6l12 12" /></svg
|
|
69
|
+
>
|
|
70
|
+
</button>
|
|
71
|
+
{/if}
|
|
72
|
+
</div>
|
|
73
|
+
{/if}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type Tone = 'info' | 'success' | 'warn' | 'danger' | 'neutral';
|
|
3
|
+
interface Props {
|
|
4
|
+
tone?: Tone;
|
|
5
|
+
dismissible?: boolean;
|
|
6
|
+
ondismiss?: () => void;
|
|
7
|
+
class?: string;
|
|
8
|
+
children: Snippet;
|
|
9
|
+
action?: Snippet;
|
|
10
|
+
}
|
|
11
|
+
declare const Banner: import("svelte").Component<Props, {}, "">;
|
|
12
|
+
type Banner = ReturnType<typeof Banner>;
|
|
13
|
+
export default Banner;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
let {
|
|
3
|
+
items,
|
|
4
|
+
class: cls = '',
|
|
5
|
+
}: {
|
|
6
|
+
items: { label: string; href?: string }[];
|
|
7
|
+
class?: string;
|
|
8
|
+
} = $props();
|
|
9
|
+
|
|
10
|
+
function safeUrl(url: string | undefined): string | undefined {
|
|
11
|
+
return url && !/^(javascript|data):/i.test(url) ? url : undefined;
|
|
12
|
+
}
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<nav aria-label="Breadcrumb" class="flex items-center gap-1 text-xs {cls}">
|
|
16
|
+
{#each items as item, i}
|
|
17
|
+
{#if i > 0}
|
|
18
|
+
<svg
|
|
19
|
+
width="12"
|
|
20
|
+
height="12"
|
|
21
|
+
viewBox="0 0 24 24"
|
|
22
|
+
fill="none"
|
|
23
|
+
stroke="currentColor"
|
|
24
|
+
stroke-width="2"
|
|
25
|
+
stroke-linecap="round"
|
|
26
|
+
stroke-linejoin="round"
|
|
27
|
+
class="text-faint shrink-0"
|
|
28
|
+
aria-hidden="true"
|
|
29
|
+
>
|
|
30
|
+
<path d="M9 18l6-6-6-6" />
|
|
31
|
+
</svg>
|
|
32
|
+
{/if}
|
|
33
|
+
|
|
34
|
+
{#if item.href && i < items.length - 1}
|
|
35
|
+
<a href={safeUrl(item.href)} class="text-muted hover:text-fg transition-colors truncate">
|
|
36
|
+
{item.label}
|
|
37
|
+
</a>
|
|
38
|
+
{:else}
|
|
39
|
+
<span
|
|
40
|
+
class="text-fg font-medium truncate"
|
|
41
|
+
aria-current={i === items.length - 1 ? 'page' : undefined}
|
|
42
|
+
>
|
|
43
|
+
{item.label}
|
|
44
|
+
</span>
|
|
45
|
+
{/if}
|
|
46
|
+
{/each}
|
|
47
|
+
</nav>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
items: {
|
|
3
|
+
label: string;
|
|
4
|
+
href?: string;
|
|
5
|
+
}[];
|
|
6
|
+
class?: string;
|
|
7
|
+
};
|
|
8
|
+
declare const Breadcrumb: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
9
|
+
type Breadcrumb = ReturnType<typeof Breadcrumb>;
|
|
10
|
+
export default Breadcrumb;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import Spinner from './Spinner.svelte';
|
|
4
|
+
|
|
5
|
+
type Variant = 'primary' | 'secondary' | 'danger' | 'ghost' | 'outline' | 'violet';
|
|
6
|
+
type Size = 'sm' | 'md' | 'lg';
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
variant?: Variant;
|
|
10
|
+
size?: Size;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
loading?: boolean;
|
|
13
|
+
type?: 'button' | 'submit' | 'reset';
|
|
14
|
+
href?: string;
|
|
15
|
+
full?: boolean;
|
|
16
|
+
onclick?: (e: MouseEvent) => void;
|
|
17
|
+
children: Snippet;
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let {
|
|
22
|
+
variant = 'primary',
|
|
23
|
+
size = 'md',
|
|
24
|
+
disabled = false,
|
|
25
|
+
loading = false,
|
|
26
|
+
type = 'button',
|
|
27
|
+
href = undefined,
|
|
28
|
+
full = false,
|
|
29
|
+
onclick,
|
|
30
|
+
children,
|
|
31
|
+
...rest
|
|
32
|
+
}: Props = $props();
|
|
33
|
+
|
|
34
|
+
const variants: Record<Variant, string> = {
|
|
35
|
+
primary: 'bg-brand text-ink hover:bg-brand-light active:bg-brand shadow-sm shadow-brand/20',
|
|
36
|
+
violet:
|
|
37
|
+
'bg-violet text-ink hover:brightness-110 active:brightness-100 shadow-sm shadow-violet/20',
|
|
38
|
+
secondary: 'bg-surface-2 text-fg border border-line hover:bg-line',
|
|
39
|
+
danger: 'bg-danger text-ink hover:brightness-110 active:brightness-100',
|
|
40
|
+
ghost: 'text-muted hover:bg-surface-2 hover:text-fg',
|
|
41
|
+
outline: 'border border-line text-fg hover:border-brand hover:text-brand',
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const sizes: Record<Size, string> = {
|
|
45
|
+
sm: 'px-3 py-1.5 text-sm gap-1.5 rounded-md',
|
|
46
|
+
md: 'px-4 py-2 text-sm gap-2 rounded-lg',
|
|
47
|
+
lg: 'px-5 py-2.5 text-base gap-2 rounded-lg',
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const spinnerSize = $derived(size === 'lg' ? 18 : 15);
|
|
51
|
+
const cls = $derived(
|
|
52
|
+
`inline-flex items-center justify-center font-medium transition-all duration-150 ` +
|
|
53
|
+
`disabled:opacity-50 disabled:cursor-not-allowed select-none ` +
|
|
54
|
+
`${full ? 'w-full' : ''} ${variants[variant]} ${sizes[size]}`,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// Reject javascript: and data: URIs - only allow standard schemes and relative URLs.
|
|
58
|
+
let safeHref = $derived(href && !/^(javascript|data):/i.test(href) ? href : undefined);
|
|
59
|
+
</script>
|
|
60
|
+
|
|
61
|
+
{#if href}
|
|
62
|
+
<a href={safeHref} class={cls} {...rest}>
|
|
63
|
+
{#if loading}<Spinner size={spinnerSize} />{/if}
|
|
64
|
+
{@render children()}
|
|
65
|
+
</a>
|
|
66
|
+
{:else}
|
|
67
|
+
<button {type} disabled={disabled || loading} {onclick} class={cls} {...rest}>
|
|
68
|
+
{#if loading}<Spinner size={spinnerSize} />{/if}
|
|
69
|
+
{@render children()}
|
|
70
|
+
</button>
|
|
71
|
+
{/if}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type Variant = 'primary' | 'secondary' | 'danger' | 'ghost' | 'outline' | 'violet';
|
|
3
|
+
type Size = 'sm' | 'md' | 'lg';
|
|
4
|
+
interface Props {
|
|
5
|
+
variant?: Variant;
|
|
6
|
+
size?: Size;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
loading?: boolean;
|
|
9
|
+
type?: 'button' | 'submit' | 'reset';
|
|
10
|
+
href?: string;
|
|
11
|
+
full?: boolean;
|
|
12
|
+
onclick?: (e: MouseEvent) => void;
|
|
13
|
+
children: Snippet;
|
|
14
|
+
[key: string]: unknown;
|
|
15
|
+
}
|
|
16
|
+
declare const Button: import("svelte").Component<Props, {}, "">;
|
|
17
|
+
type Button = ReturnType<typeof Button>;
|
|
18
|
+
export default Button;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
orientation?: 'horizontal' | 'vertical';
|
|
6
|
+
class?: string;
|
|
7
|
+
children: Snippet;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let { orientation = 'horizontal', class: cls = '', children }: Props = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
{#if orientation === 'horizontal'}
|
|
14
|
+
<div
|
|
15
|
+
class="inline-flex
|
|
16
|
+
[&>*:not(:first-child)]:rounded-l-none
|
|
17
|
+
[&>*:not(:last-child)]:rounded-r-none
|
|
18
|
+
[&>*:not(:first-child)]:-ml-px
|
|
19
|
+
{cls}"
|
|
20
|
+
>
|
|
21
|
+
{@render children()}
|
|
22
|
+
</div>
|
|
23
|
+
{:else}
|
|
24
|
+
<div
|
|
25
|
+
class="inline-flex flex-col
|
|
26
|
+
[&>*:not(:first-child)]:rounded-t-none
|
|
27
|
+
[&>*:not(:last-child)]:rounded-b-none
|
|
28
|
+
[&>*:not(:first-child)]:-mt-px
|
|
29
|
+
{cls}"
|
|
30
|
+
>
|
|
31
|
+
{@render children()}
|
|
32
|
+
</div>
|
|
33
|
+
{/if}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface Props {
|
|
3
|
+
orientation?: 'horizontal' | 'vertical';
|
|
4
|
+
class?: string;
|
|
5
|
+
children: Snippet;
|
|
6
|
+
}
|
|
7
|
+
declare const ButtonGroup: import("svelte").Component<Props, {}, "">;
|
|
8
|
+
type ButtonGroup = ReturnType<typeof ButtonGroup>;
|
|
9
|
+
export default ButtonGroup;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
type Pad = 'none' | 'sm' | 'md' | 'lg';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
title?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
pad?: Pad;
|
|
10
|
+
hover?: boolean;
|
|
11
|
+
class?: string;
|
|
12
|
+
header?: Snippet;
|
|
13
|
+
footer?: Snippet;
|
|
14
|
+
onclick?: (e: MouseEvent) => void;
|
|
15
|
+
onkeydown?: (e: KeyboardEvent) => void;
|
|
16
|
+
children: Snippet;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let {
|
|
20
|
+
title = undefined,
|
|
21
|
+
description = undefined,
|
|
22
|
+
pad = 'md',
|
|
23
|
+
hover = false,
|
|
24
|
+
class: klass = '',
|
|
25
|
+
header,
|
|
26
|
+
footer,
|
|
27
|
+
onclick,
|
|
28
|
+
onkeydown,
|
|
29
|
+
children,
|
|
30
|
+
}: Props = $props();
|
|
31
|
+
|
|
32
|
+
const pads: Record<Pad, string> = {
|
|
33
|
+
none: '',
|
|
34
|
+
sm: 'p-4',
|
|
35
|
+
md: 'p-5',
|
|
36
|
+
lg: 'p-6',
|
|
37
|
+
};
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<!-- tabindex is only set alongside role="button" (when onclick is provided),
|
|
41
|
+
so the element is interactive; the compiler can't narrow the dynamic role. -->
|
|
42
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
43
|
+
<div
|
|
44
|
+
class="bg-surface border border-line rounded-xl overflow-hidden transition-colors {hover
|
|
45
|
+
? 'hover:border-line/0 hover:ring-1 hover:ring-brand/30'
|
|
46
|
+
: ''} {onclick ? 'cursor-pointer' : ''} {klass}"
|
|
47
|
+
role={onclick ? 'button' : undefined}
|
|
48
|
+
tabindex={onclick ? 0 : undefined}
|
|
49
|
+
{onclick}
|
|
50
|
+
{onkeydown}
|
|
51
|
+
>
|
|
52
|
+
{#if header || title}
|
|
53
|
+
<div class="px-5 py-4 border-b border-line">
|
|
54
|
+
{#if header}
|
|
55
|
+
{@render header()}
|
|
56
|
+
{:else}
|
|
57
|
+
<h3 class="font-semibold text-fg">{title}</h3>
|
|
58
|
+
{#if description}<p class="text-sm text-muted mt-0.5">{description}</p>{/if}
|
|
59
|
+
{/if}
|
|
60
|
+
</div>
|
|
61
|
+
{/if}
|
|
62
|
+
|
|
63
|
+
<div class={pads[pad]}>
|
|
64
|
+
{@render children()}
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
{#if footer}
|
|
68
|
+
<div class="px-5 py-3 border-t border-line bg-surface-2/40">
|
|
69
|
+
{@render footer()}
|
|
70
|
+
</div>
|
|
71
|
+
{/if}
|
|
72
|
+
</div>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type Pad = 'none' | 'sm' | 'md' | 'lg';
|
|
3
|
+
interface Props {
|
|
4
|
+
title?: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
pad?: Pad;
|
|
7
|
+
hover?: boolean;
|
|
8
|
+
class?: string;
|
|
9
|
+
header?: Snippet;
|
|
10
|
+
footer?: Snippet;
|
|
11
|
+
onclick?: (e: MouseEvent) => void;
|
|
12
|
+
onkeydown?: (e: KeyboardEvent) => void;
|
|
13
|
+
children: Snippet;
|
|
14
|
+
}
|
|
15
|
+
declare const Card: import("svelte").Component<Props, {}, "">;
|
|
16
|
+
type Card = ReturnType<typeof Card>;
|
|
17
|
+
export default Card;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
let {
|
|
3
|
+
checked = $bindable(false),
|
|
4
|
+
label,
|
|
5
|
+
hint,
|
|
6
|
+
required = false,
|
|
7
|
+
disabled = false,
|
|
8
|
+
id,
|
|
9
|
+
name,
|
|
10
|
+
value,
|
|
11
|
+
onchange,
|
|
12
|
+
}: {
|
|
13
|
+
checked?: boolean;
|
|
14
|
+
label?: string;
|
|
15
|
+
hint?: string;
|
|
16
|
+
required?: boolean;
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
id?: string;
|
|
19
|
+
name?: string;
|
|
20
|
+
value?: string;
|
|
21
|
+
onchange?: (checked: boolean) => void;
|
|
22
|
+
} = $props();
|
|
23
|
+
|
|
24
|
+
function handleChange(e: Event & { currentTarget: HTMLInputElement }) {
|
|
25
|
+
checked = e.currentTarget.checked;
|
|
26
|
+
onchange?.(checked);
|
|
27
|
+
}
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<label
|
|
31
|
+
class="flex items-start gap-2.5 cursor-pointer select-none {disabled
|
|
32
|
+
? 'opacity-50 cursor-not-allowed'
|
|
33
|
+
: ''}"
|
|
34
|
+
>
|
|
35
|
+
<span class="relative flex items-center justify-center mt-0.5 shrink-0">
|
|
36
|
+
<input
|
|
37
|
+
type="checkbox"
|
|
38
|
+
{id}
|
|
39
|
+
{name}
|
|
40
|
+
{value}
|
|
41
|
+
{required}
|
|
42
|
+
{disabled}
|
|
43
|
+
{checked}
|
|
44
|
+
onchange={handleChange}
|
|
45
|
+
class="peer sr-only"
|
|
46
|
+
/>
|
|
47
|
+
<span
|
|
48
|
+
class="w-4 h-4 rounded border transition-colors flex items-center justify-center
|
|
49
|
+
{checked
|
|
50
|
+
? 'bg-brand border-brand'
|
|
51
|
+
: 'bg-surface-2 border-line peer-focus-visible:border-brand'}"
|
|
52
|
+
>
|
|
53
|
+
{#if checked}
|
|
54
|
+
<svg width="10" height="8" viewBox="0 0 10 8" fill="none" aria-hidden="true">
|
|
55
|
+
<path
|
|
56
|
+
d="M1 4l3 3 5-6"
|
|
57
|
+
stroke="currentColor"
|
|
58
|
+
stroke-width="1.5"
|
|
59
|
+
stroke-linecap="round"
|
|
60
|
+
stroke-linejoin="round"
|
|
61
|
+
class="text-ink"
|
|
62
|
+
/>
|
|
63
|
+
</svg>
|
|
64
|
+
{/if}
|
|
65
|
+
</span>
|
|
66
|
+
</span>
|
|
67
|
+
{#if label || hint}
|
|
68
|
+
<span class="flex flex-col gap-0.5">
|
|
69
|
+
{#if label}
|
|
70
|
+
<span class="text-sm text-fg">
|
|
71
|
+
{label}
|
|
72
|
+
{#if required}<span class="text-danger ml-0.5" aria-label="required">*</span>{/if}
|
|
73
|
+
</span>
|
|
74
|
+
{/if}
|
|
75
|
+
{#if hint}
|
|
76
|
+
<span class="text-xs text-faint">{hint}</span>
|
|
77
|
+
{/if}
|
|
78
|
+
</span>
|
|
79
|
+
{/if}
|
|
80
|
+
</label>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
checked?: boolean;
|
|
3
|
+
label?: string;
|
|
4
|
+
hint?: string;
|
|
5
|
+
required?: boolean;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
id?: string;
|
|
8
|
+
name?: string;
|
|
9
|
+
value?: string;
|
|
10
|
+
onchange?: (checked: boolean) => void;
|
|
11
|
+
};
|
|
12
|
+
declare const Checkbox: import("svelte").Component<$$ComponentProps, {}, "checked">;
|
|
13
|
+
type Checkbox = ReturnType<typeof Checkbox>;
|
|
14
|
+
export default Checkbox;
|