@orochibraru/ui 1.0.0 → 1.0.1
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 +84 -3
- package/dist/components/blocks/accent-toggle.svelte +7 -3
- package/dist/components/blocks/accent-toggle.svelte.d.ts +1 -1
- package/dist/components/blocks/bottom-nav.svelte +1 -1
- package/dist/components/blocks/command-palette.svelte +268 -0
- package/dist/components/blocks/command-palette.svelte.d.ts +37 -0
- package/dist/components/blocks/error-page.svelte +23 -7
- package/dist/components/blocks/error-page.svelte.d.ts +4 -0
- package/dist/components/blocks/fab.svelte +1 -1
- package/dist/components/blocks/install-prompt.svelte +14 -8
- package/dist/components/blocks/install-prompt.svelte.d.ts +2 -1
- package/dist/components/blocks/music-player/labelled-slider.svelte +3 -12
- package/dist/components/blocks/music-player/labelled-slider.svelte.d.ts +1 -2
- package/dist/components/blocks/music-player.svelte +13 -12
- package/dist/components/blocks/otp-field.svelte +77 -33
- package/dist/components/blocks/otp-field.svelte.d.ts +7 -0
- package/dist/components/blocks/password-input.svelte +56 -16
- package/dist/components/blocks/password-input.svelte.d.ts +3 -0
- package/dist/components/blocks/stepper/stepper-phone-header.svelte +93 -0
- package/dist/components/blocks/stepper/stepper-phone-header.svelte.d.ts +13 -0
- package/dist/components/blocks/stepper.svelte +175 -0
- package/dist/components/blocks/stepper.svelte.d.ts +30 -0
- package/dist/components/blocks/time-range-picker.svelte +229 -0
- package/dist/components/blocks/time-range-picker.svelte.d.ts +33 -0
- package/dist/components/blocks/time-series-chart/data-table.svelte +43 -0
- package/dist/components/blocks/time-series-chart/data-table.svelte.d.ts +15 -0
- package/dist/components/blocks/time-series-chart/legend.svelte +23 -0
- package/dist/components/blocks/time-series-chart/legend.svelte.d.ts +10 -0
- package/dist/components/blocks/time-series-chart/marks.svelte +118 -0
- package/dist/components/blocks/time-series-chart/marks.svelte.d.ts +29 -0
- package/dist/components/blocks/time-series-chart/messages.d.ts +13 -0
- package/dist/components/blocks/time-series-chart/messages.js +12 -0
- package/dist/components/blocks/time-series-chart/readout.svelte +44 -0
- package/dist/components/blocks/time-series-chart/readout.svelte.d.ts +15 -0
- package/dist/components/blocks/time-series-chart/scale.d.ts +24 -0
- package/dist/components/blocks/time-series-chart/scale.js +136 -0
- package/dist/components/blocks/time-series-chart/time.d.ts +7 -0
- package/dist/components/blocks/time-series-chart/time.js +96 -0
- package/dist/components/blocks/time-series-chart.svelte +492 -0
- package/dist/components/blocks/time-series-chart.svelte.d.ts +18 -0
- package/dist/components/blocks/top-loading-bar.svelte +1 -1
- package/dist/components/blocks/video-player/center-controls.svelte +7 -7
- package/dist/components/blocks/video-player/control-bar.svelte +41 -35
- package/dist/components/blocks/video-player/control-button.svelte +1 -1
- package/dist/components/blocks/video-player/option-menu.svelte +1 -1
- package/dist/components/blocks/video-player/scrub-bar.svelte +39 -12
- package/dist/components/blocks/video-player/scrub-bar.svelte.d.ts +2 -0
- package/dist/components/blocks/video-player.svelte +2 -2
- package/dist/components/kit/accordion.svelte +70 -0
- package/dist/components/kit/accordion.svelte.d.ts +37 -0
- package/dist/components/kit/avatar-group.svelte +84 -0
- package/dist/components/kit/avatar-group.svelte.d.ts +14 -0
- package/dist/components/kit/bar-list.svelte +104 -0
- package/dist/components/kit/bar-list.svelte.d.ts +22 -0
- package/dist/components/kit/breadcrumbs.svelte +102 -0
- package/dist/components/kit/breadcrumbs.svelte.d.ts +14 -0
- package/dist/components/kit/checkbox-list.svelte +98 -0
- package/dist/components/kit/checkbox-list.svelte.d.ts +15 -0
- package/dist/components/kit/checkbox.svelte +84 -0
- package/dist/components/kit/checkbox.svelte.d.ts +8 -0
- package/dist/components/kit/confirm-dialog.svelte +127 -0
- package/dist/components/kit/confirm-dialog.svelte.d.ts +19 -0
- package/dist/components/kit/data-table.svelte +280 -0
- package/dist/components/kit/data-table.svelte.d.ts +46 -0
- package/dist/components/kit/date-field.svelte +210 -0
- package/dist/components/kit/date-field.svelte.d.ts +14 -0
- package/dist/components/kit/field-frame.svelte +71 -0
- package/dist/components/kit/field-frame.svelte.d.ts +26 -0
- package/dist/components/kit/fieldset-frame.svelte +69 -0
- package/dist/components/kit/fieldset-frame.svelte.d.ts +14 -0
- package/dist/components/kit/file-dropzone/files.d.ts +14 -0
- package/dist/components/kit/file-dropzone/files.js +69 -0
- package/dist/components/kit/file-dropzone.svelte +253 -0
- package/dist/components/kit/file-dropzone.svelte.d.ts +25 -0
- package/dist/components/kit/form.svelte +335 -0
- package/dist/components/kit/form.svelte.d.ts +61 -0
- package/dist/components/kit/input.svelte +73 -0
- package/dist/components/kit/input.svelte.d.ts +9 -0
- package/dist/components/kit/menu.svelte +163 -0
- package/dist/components/kit/menu.svelte.d.ts +28 -0
- package/dist/components/kit/pagination.svelte +134 -0
- package/dist/components/kit/pagination.svelte.d.ts +17 -0
- package/dist/components/kit/radio-list.svelte +131 -0
- package/dist/components/kit/radio-list.svelte.d.ts +15 -0
- package/dist/components/kit/segmented-control/segment.svelte +47 -0
- package/dist/components/kit/segmented-control/segment.svelte.d.ts +11 -0
- package/dist/components/kit/segmented-control.svelte +70 -0
- package/dist/components/kit/segmented-control.svelte.d.ts +16 -0
- package/dist/components/kit/select.svelte +244 -0
- package/dist/components/kit/select.svelte.d.ts +26 -0
- package/dist/components/kit/status-badge.svelte +54 -0
- package/dist/components/kit/status-badge.svelte.d.ts +31 -0
- package/dist/components/kit/switch.svelte +84 -0
- package/dist/components/kit/switch.svelte.d.ts +8 -0
- package/dist/components/kit/tabs.svelte +86 -0
- package/dist/components/kit/tabs.svelte.d.ts +20 -0
- package/dist/components/kit/textarea.svelte +92 -0
- package/dist/components/kit/textarea.svelte.d.ts +11 -0
- package/dist/components/ui/drawer/drawer-content.svelte +1 -1
- package/dist/components/ui/input-group/input-group-input.svelte.d.ts +1 -1
- package/dist/components/ui/sidebar/sidebar-input.svelte.d.ts +1 -1
- package/dist/components/ui/sonner/sonner.svelte +2 -4
- package/dist/mcp-content.json +1 -1
- package/dist/mcp.js +11 -11
- package/dist/server/hooks.d.ts +9 -0
- package/dist/server/hooks.js +41 -0
- package/dist/server/logger.d.ts +39 -0
- package/dist/server/logger.js +144 -0
- package/dist/server/telemetry.d.ts +23 -0
- package/dist/server/telemetry.js +82 -0
- package/dist/theme.css +18 -6
- package/package.json +40 -1
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLInputAttributes } from "svelte/elements";
|
|
3
|
+
|
|
4
|
+
import PasswordInput from "../blocks/password-input.svelte";
|
|
5
|
+
import { Input } from "../ui/input/index.js";
|
|
6
|
+
|
|
7
|
+
import FieldFrame, { errorList, type FieldProps } from "./field-frame.svelte";
|
|
8
|
+
|
|
9
|
+
type Props = FieldProps &
|
|
10
|
+
Omit<HTMLInputAttributes, "value" | "files" | "class" | "id" | "name"> & {
|
|
11
|
+
value?: string | number;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
let {
|
|
15
|
+
value = $bindable(""),
|
|
16
|
+
name,
|
|
17
|
+
label,
|
|
18
|
+
description,
|
|
19
|
+
error,
|
|
20
|
+
required = false,
|
|
21
|
+
disabled = false,
|
|
22
|
+
id,
|
|
23
|
+
class: className,
|
|
24
|
+
type = "text",
|
|
25
|
+
...rest
|
|
26
|
+
}: Props = $props();
|
|
27
|
+
|
|
28
|
+
const generatedId = $props.id();
|
|
29
|
+
const inputId = $derived(id ?? generatedId);
|
|
30
|
+
const invalid = $derived(errorList(error).length > 0);
|
|
31
|
+
const describedBy = $derived(
|
|
32
|
+
invalid
|
|
33
|
+
? `${inputId}-error`
|
|
34
|
+
: description
|
|
35
|
+
? `${inputId}-description`
|
|
36
|
+
: undefined,
|
|
37
|
+
);
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<FieldFrame
|
|
41
|
+
id={inputId}
|
|
42
|
+
{label}
|
|
43
|
+
{description}
|
|
44
|
+
{error}
|
|
45
|
+
{required}
|
|
46
|
+
class={className}
|
|
47
|
+
>
|
|
48
|
+
{#if type === "password"}
|
|
49
|
+
<PasswordInput
|
|
50
|
+
{...rest}
|
|
51
|
+
id={inputId}
|
|
52
|
+
{name}
|
|
53
|
+
{required}
|
|
54
|
+
{disabled}
|
|
55
|
+
aria-invalid={invalid || undefined}
|
|
56
|
+
aria-describedby={describedBy}
|
|
57
|
+
bind:value={() => String(value ?? ""), (next) => (value = next)}
|
|
58
|
+
/>
|
|
59
|
+
{:else}
|
|
60
|
+
<Input
|
|
61
|
+
{...rest}
|
|
62
|
+
id={inputId}
|
|
63
|
+
{name}
|
|
64
|
+
type={type ?? "text"}
|
|
65
|
+
{required}
|
|
66
|
+
{disabled}
|
|
67
|
+
aria-invalid={invalid || undefined}
|
|
68
|
+
aria-describedby={describedBy}
|
|
69
|
+
bind:value
|
|
70
|
+
class="h-11"
|
|
71
|
+
/>
|
|
72
|
+
{/if}
|
|
73
|
+
</FieldFrame>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { HTMLInputAttributes } from "svelte/elements";
|
|
2
|
+
import { Input } from "../ui/input/index.js";
|
|
3
|
+
import { type FieldProps } from "./field-frame.svelte";
|
|
4
|
+
type Props = FieldProps & Omit<HTMLInputAttributes, "value" | "files" | "class" | "id" | "name"> & {
|
|
5
|
+
value?: string | number;
|
|
6
|
+
};
|
|
7
|
+
declare const Input: import("svelte").Component<Props, {}, "value">;
|
|
8
|
+
type Input = ReturnType<typeof Input>;
|
|
9
|
+
export default Input;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { Component, Snippet } from "svelte";
|
|
3
|
+
|
|
4
|
+
export interface MenuItem {
|
|
5
|
+
label: string;
|
|
6
|
+
icon?: Component<{ class?: string }>;
|
|
7
|
+
href?: string;
|
|
8
|
+
onSelect?: () => void;
|
|
9
|
+
shortcut?: string;
|
|
10
|
+
destructive?: boolean;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type MenuEntry = MenuItem | "separator";
|
|
15
|
+
|
|
16
|
+
export type MenuTrigger = Snippet<[{ props: Record<string, unknown> }]>;
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<script lang="ts">
|
|
20
|
+
import EllipsisIcon from "@lucide/svelte/icons/ellipsis";
|
|
21
|
+
import { MediaQuery } from "svelte/reactivity";
|
|
22
|
+
|
|
23
|
+
import IconButton from "../blocks/icon-button.svelte";
|
|
24
|
+
import { Button } from "../ui/button/index.js";
|
|
25
|
+
import * as Drawer from "../ui/drawer/index.js";
|
|
26
|
+
import * as DropdownMenu from "../ui/dropdown-menu/index.js";
|
|
27
|
+
import { Separator } from "../ui/separator/index.js";
|
|
28
|
+
import { cn } from "../../utils.js";
|
|
29
|
+
|
|
30
|
+
const defaultMessages = {
|
|
31
|
+
label: "More actions",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
interface Props {
|
|
35
|
+
items: MenuEntry[];
|
|
36
|
+
trigger?: MenuTrigger;
|
|
37
|
+
label?: string;
|
|
38
|
+
title?: string;
|
|
39
|
+
align?: "start" | "center" | "end";
|
|
40
|
+
messages?: Partial<typeof defaultMessages>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const {
|
|
44
|
+
items,
|
|
45
|
+
trigger,
|
|
46
|
+
label,
|
|
47
|
+
title,
|
|
48
|
+
align = "end",
|
|
49
|
+
messages = {},
|
|
50
|
+
}: Props = $props();
|
|
51
|
+
|
|
52
|
+
const desktop = new MediaQuery("(min-width: 768px)");
|
|
53
|
+
const text = $derived({ ...defaultMessages, ...messages });
|
|
54
|
+
const accessibleName = $derived(label ?? text.label);
|
|
55
|
+
|
|
56
|
+
let open = $state(false);
|
|
57
|
+
|
|
58
|
+
function select(item: MenuItem) {
|
|
59
|
+
open = false;
|
|
60
|
+
item.onSelect?.();
|
|
61
|
+
}
|
|
62
|
+
</script>
|
|
63
|
+
|
|
64
|
+
{#snippet defaultTrigger(props: Record<string, unknown>)}
|
|
65
|
+
<IconButton
|
|
66
|
+
{...props}
|
|
67
|
+
label={accessibleName}
|
|
68
|
+
icon={EllipsisIcon}
|
|
69
|
+
class="text-muted-foreground"
|
|
70
|
+
/>
|
|
71
|
+
{/snippet}
|
|
72
|
+
|
|
73
|
+
{#snippet itemContent(item: MenuItem)}
|
|
74
|
+
{#if item.icon}
|
|
75
|
+
<item.icon class={desktop.current ? "size-4" : "size-5"} />
|
|
76
|
+
{/if}
|
|
77
|
+
<span class="truncate">{item.label}</span>
|
|
78
|
+
{/snippet}
|
|
79
|
+
|
|
80
|
+
{#if desktop.current}
|
|
81
|
+
<DropdownMenu.Root>
|
|
82
|
+
<DropdownMenu.Trigger>
|
|
83
|
+
{#snippet child({ props })}
|
|
84
|
+
{#if trigger}
|
|
85
|
+
{@render trigger({ props })}
|
|
86
|
+
{:else}
|
|
87
|
+
{@render defaultTrigger(props)}
|
|
88
|
+
{/if}
|
|
89
|
+
{/snippet}
|
|
90
|
+
</DropdownMenu.Trigger>
|
|
91
|
+
<DropdownMenu.Content {align} class="min-w-48">
|
|
92
|
+
{#each items as item, index (index)}
|
|
93
|
+
{#if item === "separator"}
|
|
94
|
+
<DropdownMenu.Separator />
|
|
95
|
+
{:else if item.href && !item.disabled}
|
|
96
|
+
<DropdownMenu.Item
|
|
97
|
+
variant={item.destructive ? "destructive" : "default"}
|
|
98
|
+
onSelect={() => select(item)}
|
|
99
|
+
>
|
|
100
|
+
{#snippet child({ props })}
|
|
101
|
+
<a href={item.href} {...props}>
|
|
102
|
+
{@render itemContent(item)}
|
|
103
|
+
{#if item.shortcut}
|
|
104
|
+
<DropdownMenu.Shortcut>{item.shortcut}</DropdownMenu.Shortcut>
|
|
105
|
+
{/if}
|
|
106
|
+
</a>
|
|
107
|
+
{/snippet}
|
|
108
|
+
</DropdownMenu.Item>
|
|
109
|
+
{:else}
|
|
110
|
+
<DropdownMenu.Item
|
|
111
|
+
variant={item.destructive ? "destructive" : "default"}
|
|
112
|
+
disabled={item.disabled}
|
|
113
|
+
onSelect={() => select(item)}
|
|
114
|
+
>
|
|
115
|
+
{@render itemContent(item)}
|
|
116
|
+
{#if item.shortcut}
|
|
117
|
+
<DropdownMenu.Shortcut>{item.shortcut}</DropdownMenu.Shortcut>
|
|
118
|
+
{/if}
|
|
119
|
+
</DropdownMenu.Item>
|
|
120
|
+
{/if}
|
|
121
|
+
{/each}
|
|
122
|
+
</DropdownMenu.Content>
|
|
123
|
+
</DropdownMenu.Root>
|
|
124
|
+
{:else}
|
|
125
|
+
{@const props = {
|
|
126
|
+
type: "button",
|
|
127
|
+
"aria-haspopup": "dialog",
|
|
128
|
+
"aria-expanded": open,
|
|
129
|
+
onclick: () => (open = true),
|
|
130
|
+
}}
|
|
131
|
+
{#if trigger}
|
|
132
|
+
{@render trigger({ props })}
|
|
133
|
+
{:else}
|
|
134
|
+
{@render defaultTrigger(props)}
|
|
135
|
+
{/if}
|
|
136
|
+
<Drawer.Root bind:open>
|
|
137
|
+
<Drawer.Content>
|
|
138
|
+
<Drawer.Header class={cn("px-5 text-left", !title && "sr-only")}>
|
|
139
|
+
<Drawer.Title class="truncate">{title ?? accessibleName}</Drawer.Title>
|
|
140
|
+
</Drawer.Header>
|
|
141
|
+
<div class="flex flex-col gap-1 px-3 pb-4">
|
|
142
|
+
{#each items as item, index (index)}
|
|
143
|
+
{#if item === "separator"}
|
|
144
|
+
<Separator class="my-1" />
|
|
145
|
+
{:else}
|
|
146
|
+
<Button
|
|
147
|
+
variant="ghost"
|
|
148
|
+
href={item.href}
|
|
149
|
+
disabled={item.disabled}
|
|
150
|
+
class={cn(
|
|
151
|
+
"h-12 justify-start gap-3 rounded-xl px-3 text-base",
|
|
152
|
+
item.destructive && "text-destructive hover:text-destructive",
|
|
153
|
+
)}
|
|
154
|
+
onclick={() => select(item)}
|
|
155
|
+
>
|
|
156
|
+
{@render itemContent(item)}
|
|
157
|
+
</Button>
|
|
158
|
+
{/if}
|
|
159
|
+
{/each}
|
|
160
|
+
</div>
|
|
161
|
+
</Drawer.Content>
|
|
162
|
+
</Drawer.Root>
|
|
163
|
+
{/if}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Component, Snippet } from "svelte";
|
|
2
|
+
export interface MenuItem {
|
|
3
|
+
label: string;
|
|
4
|
+
icon?: Component<{
|
|
5
|
+
class?: string;
|
|
6
|
+
}>;
|
|
7
|
+
href?: string;
|
|
8
|
+
onSelect?: () => void;
|
|
9
|
+
shortcut?: string;
|
|
10
|
+
destructive?: boolean;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export type MenuEntry = MenuItem | "separator";
|
|
14
|
+
export type MenuTrigger = Snippet<[{
|
|
15
|
+
props: Record<string, unknown>;
|
|
16
|
+
}]>;
|
|
17
|
+
declare const Menu: Component<{
|
|
18
|
+
items: MenuEntry[];
|
|
19
|
+
trigger?: MenuTrigger;
|
|
20
|
+
label?: string;
|
|
21
|
+
title?: string;
|
|
22
|
+
align?: "start" | "center" | "end";
|
|
23
|
+
messages?: Partial<{
|
|
24
|
+
label: string;
|
|
25
|
+
}>;
|
|
26
|
+
}, {}, "">;
|
|
27
|
+
type Menu = ReturnType<typeof Menu>;
|
|
28
|
+
export default Menu;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import ChevronLeftIcon from "@lucide/svelte/icons/chevron-left";
|
|
3
|
+
import ChevronRightIcon from "@lucide/svelte/icons/chevron-right";
|
|
4
|
+
import EllipsisIcon from "@lucide/svelte/icons/ellipsis";
|
|
5
|
+
import { MediaQuery } from "svelte/reactivity";
|
|
6
|
+
|
|
7
|
+
import { Button } from "../ui/button/index.js";
|
|
8
|
+
import { cn } from "../../utils.js";
|
|
9
|
+
|
|
10
|
+
const defaultMessages = {
|
|
11
|
+
label: "Pagination",
|
|
12
|
+
previous: "Previous",
|
|
13
|
+
next: "Next",
|
|
14
|
+
pageOf: (page: number, pageCount: number) => `Page ${page} of ${pageCount}`,
|
|
15
|
+
range: (first: number, last: number, total: number) =>
|
|
16
|
+
`${first}–${last} of ${total}`,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
interface Props {
|
|
20
|
+
page?: number;
|
|
21
|
+
total: number;
|
|
22
|
+
perPage?: number;
|
|
23
|
+
onchange?: (page: number) => void;
|
|
24
|
+
href?: (page: number) => string;
|
|
25
|
+
class?: string;
|
|
26
|
+
messages?: Partial<typeof defaultMessages>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let {
|
|
30
|
+
page = $bindable(1),
|
|
31
|
+
total,
|
|
32
|
+
perPage = 20,
|
|
33
|
+
onchange,
|
|
34
|
+
href,
|
|
35
|
+
class: className,
|
|
36
|
+
messages = {},
|
|
37
|
+
}: Props = $props();
|
|
38
|
+
|
|
39
|
+
const desktop = new MediaQuery("(min-width: 768px)");
|
|
40
|
+
const text = $derived({ ...defaultMessages, ...messages });
|
|
41
|
+
|
|
42
|
+
const pageCount = $derived(Math.max(1, Math.ceil(total / perPage)));
|
|
43
|
+
const first = $derived(total === 0 ? 0 : (page - 1) * perPage + 1);
|
|
44
|
+
const last = $derived(Math.min(page * perPage, total));
|
|
45
|
+
|
|
46
|
+
const pages = $derived.by<(number | "gap")[]>(() => {
|
|
47
|
+
if (pageCount <= 7) {
|
|
48
|
+
return Array.from({ length: pageCount }, (_, index) => index + 1);
|
|
49
|
+
}
|
|
50
|
+
if (page <= 4) {
|
|
51
|
+
return [1, 2, 3, 4, 5, "gap", pageCount];
|
|
52
|
+
}
|
|
53
|
+
if (page >= pageCount - 3) {
|
|
54
|
+
return [
|
|
55
|
+
1,
|
|
56
|
+
"gap",
|
|
57
|
+
...Array.from({ length: 5 }, (_, index) => pageCount - 4 + index),
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
return [1, "gap", page - 1, page, page + 1, "gap", pageCount];
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
function go(next: number) {
|
|
64
|
+
if (next < 1 || next > pageCount || next === page) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
page = next;
|
|
68
|
+
onchange?.(next);
|
|
69
|
+
}
|
|
70
|
+
</script>
|
|
71
|
+
|
|
72
|
+
{#if total > perPage}
|
|
73
|
+
<nav
|
|
74
|
+
aria-label={text.label}
|
|
75
|
+
class={cn("flex w-full items-center justify-between gap-3", className)}
|
|
76
|
+
>
|
|
77
|
+
{#if desktop.current}
|
|
78
|
+
<p class="text-muted-foreground text-sm tabular-nums">
|
|
79
|
+
{text.range(first, last, total)}
|
|
80
|
+
</p>
|
|
81
|
+
{/if}
|
|
82
|
+
<div
|
|
83
|
+
class="flex w-full items-center justify-between gap-1 md:w-auto md:justify-end"
|
|
84
|
+
>
|
|
85
|
+
<Button
|
|
86
|
+
variant="outline"
|
|
87
|
+
class="h-11 rounded-xl md:h-9"
|
|
88
|
+
href={href?.(page - 1)}
|
|
89
|
+
disabled={page <= 1}
|
|
90
|
+
onclick={() => go(page - 1)}
|
|
91
|
+
>
|
|
92
|
+
<ChevronLeftIcon />
|
|
93
|
+
{text.previous}
|
|
94
|
+
</Button>
|
|
95
|
+
{#if desktop.current}
|
|
96
|
+
{#each pages as entry, index (index)}
|
|
97
|
+
{#if entry === "gap"}
|
|
98
|
+
<span
|
|
99
|
+
aria-hidden="true"
|
|
100
|
+
class="text-muted-foreground flex size-9 items-center justify-center"
|
|
101
|
+
>
|
|
102
|
+
<EllipsisIcon class="size-4" />
|
|
103
|
+
</span>
|
|
104
|
+
{:else}
|
|
105
|
+
<Button
|
|
106
|
+
variant={entry === page ? "outline" : "ghost"}
|
|
107
|
+
size="icon"
|
|
108
|
+
class="tabular-nums"
|
|
109
|
+
href={href?.(entry)}
|
|
110
|
+
aria-current={entry === page ? "page" : undefined}
|
|
111
|
+
onclick={() => go(entry)}
|
|
112
|
+
>
|
|
113
|
+
{entry}
|
|
114
|
+
</Button>
|
|
115
|
+
{/if}
|
|
116
|
+
{/each}
|
|
117
|
+
{:else}
|
|
118
|
+
<span class="text-muted-foreground text-sm tabular-nums">
|
|
119
|
+
{text.pageOf(page, pageCount)}
|
|
120
|
+
</span>
|
|
121
|
+
{/if}
|
|
122
|
+
<Button
|
|
123
|
+
variant="outline"
|
|
124
|
+
class="h-11 rounded-xl md:h-9"
|
|
125
|
+
href={href?.(page + 1)}
|
|
126
|
+
disabled={page >= pageCount}
|
|
127
|
+
onclick={() => go(page + 1)}
|
|
128
|
+
>
|
|
129
|
+
{text.next}
|
|
130
|
+
<ChevronRightIcon />
|
|
131
|
+
</Button>
|
|
132
|
+
</div>
|
|
133
|
+
</nav>
|
|
134
|
+
{/if}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
declare const Pagination: import("svelte").Component<{
|
|
2
|
+
page?: number;
|
|
3
|
+
total: number;
|
|
4
|
+
perPage?: number;
|
|
5
|
+
onchange?: (page: number) => void;
|
|
6
|
+
href?: (page: number) => string;
|
|
7
|
+
class?: string;
|
|
8
|
+
messages?: Partial<{
|
|
9
|
+
label: string;
|
|
10
|
+
previous: string;
|
|
11
|
+
next: string;
|
|
12
|
+
pageOf: (page: number, pageCount: number) => string;
|
|
13
|
+
range: (first: number, last: number, total: number) => string;
|
|
14
|
+
}>;
|
|
15
|
+
}, {}, "page">;
|
|
16
|
+
type Pagination = ReturnType<typeof Pagination>;
|
|
17
|
+
export default Pagination;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import CheckIcon from "@lucide/svelte/icons/check";
|
|
3
|
+
import { RadioGroup as RadioGroupPrimitive } from "bits-ui";
|
|
4
|
+
|
|
5
|
+
import * as RadioGroup from "../ui/radio-group/index.js";
|
|
6
|
+
import { cn } from "../../utils.js";
|
|
7
|
+
|
|
8
|
+
import { errorList, type FieldProps } from "./field-frame.svelte";
|
|
9
|
+
import FieldsetFrame from "./fieldset-frame.svelte";
|
|
10
|
+
|
|
11
|
+
interface Option {
|
|
12
|
+
value: string;
|
|
13
|
+
label: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type Props = FieldProps & {
|
|
19
|
+
options: readonly Option[];
|
|
20
|
+
value?: string;
|
|
21
|
+
variant?: "list" | "cards";
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
let {
|
|
25
|
+
options,
|
|
26
|
+
value = $bindable(""),
|
|
27
|
+
variant = "list",
|
|
28
|
+
name,
|
|
29
|
+
label,
|
|
30
|
+
description,
|
|
31
|
+
error,
|
|
32
|
+
required = false,
|
|
33
|
+
disabled = false,
|
|
34
|
+
id,
|
|
35
|
+
class: className,
|
|
36
|
+
}: Props = $props();
|
|
37
|
+
|
|
38
|
+
const generatedId = $props.id();
|
|
39
|
+
const listId = $derived(id ?? generatedId);
|
|
40
|
+
const invalid = $derived(errorList(error).length > 0);
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
<FieldsetFrame
|
|
44
|
+
id={listId}
|
|
45
|
+
{label}
|
|
46
|
+
{description}
|
|
47
|
+
{error}
|
|
48
|
+
{required}
|
|
49
|
+
{disabled}
|
|
50
|
+
class={className}
|
|
51
|
+
>
|
|
52
|
+
<RadioGroup.Root
|
|
53
|
+
{name}
|
|
54
|
+
{required}
|
|
55
|
+
{disabled}
|
|
56
|
+
bind:value
|
|
57
|
+
aria-labelledby={label ? `${listId}-label` : undefined}
|
|
58
|
+
aria-invalid={invalid || undefined}
|
|
59
|
+
class={variant === "cards"
|
|
60
|
+
? "gap-2 md:grid-cols-2 xl:grid-cols-3"
|
|
61
|
+
: "flex flex-col gap-0"}
|
|
62
|
+
>
|
|
63
|
+
{#each options as option, index (option.value)}
|
|
64
|
+
{@const optionId = `${listId}-${index}`}
|
|
65
|
+
{@const describedBy = option.description
|
|
66
|
+
? `${optionId}-description`
|
|
67
|
+
: undefined}
|
|
68
|
+
{#if variant === "cards"}
|
|
69
|
+
<RadioGroupPrimitive.Item
|
|
70
|
+
id={optionId}
|
|
71
|
+
value={option.value}
|
|
72
|
+
disabled={option.disabled}
|
|
73
|
+
aria-labelledby="{optionId}-label"
|
|
74
|
+
aria-describedby={describedBy}
|
|
75
|
+
class={cn(
|
|
76
|
+
"border-border bg-card data-checked:border-primary data-checked:ring-primary/30 focus-visible:ring-ring/50 flex min-h-14 w-full cursor-pointer items-center gap-3 rounded-2xl border p-3 text-left transition-all outline-none data-checked:ring-2 focus-visible:ring-3 disabled:cursor-not-allowed disabled:opacity-50",
|
|
77
|
+
invalid && "border-destructive",
|
|
78
|
+
)}
|
|
79
|
+
>
|
|
80
|
+
{#snippet children({ checked })}
|
|
81
|
+
<span class="min-w-0 flex-1">
|
|
82
|
+
<span id="{optionId}-label" class="block text-sm font-medium"
|
|
83
|
+
>{option.label}</span
|
|
84
|
+
>
|
|
85
|
+
{#if option.description}
|
|
86
|
+
<span
|
|
87
|
+
id="{optionId}-description"
|
|
88
|
+
class="text-muted-foreground block text-sm"
|
|
89
|
+
>{option.description}</span
|
|
90
|
+
>
|
|
91
|
+
{/if}
|
|
92
|
+
</span>
|
|
93
|
+
<CheckIcon
|
|
94
|
+
class={cn(
|
|
95
|
+
"text-primary size-5 shrink-0",
|
|
96
|
+
!checked && "invisible",
|
|
97
|
+
)}
|
|
98
|
+
aria-hidden="true"
|
|
99
|
+
/>
|
|
100
|
+
{/snippet}
|
|
101
|
+
</RadioGroupPrimitive.Item>
|
|
102
|
+
{:else}
|
|
103
|
+
<div
|
|
104
|
+
class="group/row relative flex min-h-11 items-start gap-3 py-2.5 has-disabled:opacity-50"
|
|
105
|
+
>
|
|
106
|
+
<RadioGroup.Item
|
|
107
|
+
id={optionId}
|
|
108
|
+
value={option.value}
|
|
109
|
+
disabled={option.disabled}
|
|
110
|
+
aria-describedby={describedBy}
|
|
111
|
+
class={cn("z-10 size-5", invalid && "border-destructive")}
|
|
112
|
+
/>
|
|
113
|
+
<span class="min-w-0 flex-1">
|
|
114
|
+
<label
|
|
115
|
+
for={optionId}
|
|
116
|
+
class="block cursor-pointer text-sm leading-snug font-medium after:absolute after:inset-0 group-has-disabled/row:cursor-not-allowed"
|
|
117
|
+
>{option.label}</label
|
|
118
|
+
>
|
|
119
|
+
{#if option.description}
|
|
120
|
+
<span
|
|
121
|
+
id="{optionId}-description"
|
|
122
|
+
class="text-muted-foreground mt-0.5 block text-sm"
|
|
123
|
+
>{option.description}</span
|
|
124
|
+
>
|
|
125
|
+
{/if}
|
|
126
|
+
</span>
|
|
127
|
+
</div>
|
|
128
|
+
{/if}
|
|
129
|
+
{/each}
|
|
130
|
+
</RadioGroup.Root>
|
|
131
|
+
</FieldsetFrame>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type FieldProps } from "./field-frame.svelte";
|
|
2
|
+
interface Option {
|
|
3
|
+
value: string;
|
|
4
|
+
label: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
}
|
|
8
|
+
type Props = FieldProps & {
|
|
9
|
+
options: readonly Option[];
|
|
10
|
+
value?: string;
|
|
11
|
+
variant?: "list" | "cards";
|
|
12
|
+
};
|
|
13
|
+
declare const RadioList: import("svelte").Component<Props, {}, "value">;
|
|
14
|
+
type RadioList = ReturnType<typeof RadioList>;
|
|
15
|
+
export default RadioList;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Component } from "svelte";
|
|
3
|
+
|
|
4
|
+
import * as ToggleGroup from "../../ui/toggle-group/index.js";
|
|
5
|
+
import * as Tooltip from "../../ui/tooltip/index.js";
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
value: string;
|
|
9
|
+
label: string;
|
|
10
|
+
icon?: Component<{ class?: string }>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const { value, label, icon: Icon }: Props = $props();
|
|
14
|
+
|
|
15
|
+
let available = $state(0);
|
|
16
|
+
let needed = $state(0);
|
|
17
|
+
const fits = $derived(!Icon || needed <= available);
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<Tooltip.Root disabled={fits}>
|
|
21
|
+
<Tooltip.Trigger>
|
|
22
|
+
{#snippet child({ props })}
|
|
23
|
+
<ToggleGroup.Item
|
|
24
|
+
{...props}
|
|
25
|
+
{value}
|
|
26
|
+
aria-label={label}
|
|
27
|
+
class="bg-secondary data-[state=on]:bg-primary data-[state=on]:text-primary-foreground relative h-11 min-w-0 flex-1 overflow-hidden rounded-xl px-3"
|
|
28
|
+
>
|
|
29
|
+
<span
|
|
30
|
+
class="flex min-w-0 flex-1 items-center justify-center gap-2"
|
|
31
|
+
bind:clientWidth={available}
|
|
32
|
+
>
|
|
33
|
+
{#if Icon}<Icon class="shrink-0" />{/if}
|
|
34
|
+
{#if fits}<span class="truncate">{label}</span>{/if}
|
|
35
|
+
</span>
|
|
36
|
+
<span
|
|
37
|
+
class="invisible absolute flex items-center gap-2 whitespace-nowrap"
|
|
38
|
+
aria-hidden="true"
|
|
39
|
+
bind:clientWidth={needed}
|
|
40
|
+
>
|
|
41
|
+
{#if Icon}<Icon class="shrink-0" />{/if}{label}
|
|
42
|
+
</span>
|
|
43
|
+
</ToggleGroup.Item>
|
|
44
|
+
{/snippet}
|
|
45
|
+
</Tooltip.Trigger>
|
|
46
|
+
<Tooltip.Content>{label}</Tooltip.Content>
|
|
47
|
+
</Tooltip.Root>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Component } from "svelte";
|
|
2
|
+
interface Props {
|
|
3
|
+
value: string;
|
|
4
|
+
label: string;
|
|
5
|
+
icon?: Component<{
|
|
6
|
+
class?: string;
|
|
7
|
+
}>;
|
|
8
|
+
}
|
|
9
|
+
declare const Segment: Component<Props, {}, "">;
|
|
10
|
+
type Segment = ReturnType<typeof Segment>;
|
|
11
|
+
export default Segment;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Component } from "svelte";
|
|
3
|
+
|
|
4
|
+
import * as ToggleGroup from "../ui/toggle-group/index.js";
|
|
5
|
+
import * as Tooltip from "../ui/tooltip/index.js";
|
|
6
|
+
|
|
7
|
+
import { errorList, type FieldProps } from "./field-frame.svelte";
|
|
8
|
+
import FieldsetFrame from "./fieldset-frame.svelte";
|
|
9
|
+
import Segment from "./segmented-control/segment.svelte";
|
|
10
|
+
|
|
11
|
+
interface Option {
|
|
12
|
+
value: string;
|
|
13
|
+
label: string;
|
|
14
|
+
icon?: Component<{ class?: string }>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type Props = FieldProps & {
|
|
18
|
+
options: readonly Option[];
|
|
19
|
+
value?: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
let {
|
|
23
|
+
options,
|
|
24
|
+
value = $bindable(""),
|
|
25
|
+
name,
|
|
26
|
+
label,
|
|
27
|
+
description,
|
|
28
|
+
error,
|
|
29
|
+
required = false,
|
|
30
|
+
disabled = false,
|
|
31
|
+
id,
|
|
32
|
+
class: className,
|
|
33
|
+
}: Props = $props();
|
|
34
|
+
|
|
35
|
+
const generatedId = $props.id();
|
|
36
|
+
const controlId = $derived(id ?? generatedId);
|
|
37
|
+
const invalid = $derived(errorList(error).length > 0);
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<FieldsetFrame
|
|
41
|
+
id={controlId}
|
|
42
|
+
{label}
|
|
43
|
+
{description}
|
|
44
|
+
{error}
|
|
45
|
+
{required}
|
|
46
|
+
{disabled}
|
|
47
|
+
class={className}
|
|
48
|
+
>
|
|
49
|
+
<Tooltip.Provider>
|
|
50
|
+
<ToggleGroup.Root
|
|
51
|
+
type="single"
|
|
52
|
+
bind:value={
|
|
53
|
+
() => value,
|
|
54
|
+
(next) => {
|
|
55
|
+
if (typeof next === "string" && next) value = next;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
spacing={2}
|
|
59
|
+
{disabled}
|
|
60
|
+
aria-labelledby={label ? `${controlId}-label` : undefined}
|
|
61
|
+
aria-invalid={invalid || undefined}
|
|
62
|
+
class="w-full"
|
|
63
|
+
>
|
|
64
|
+
{#each options as option (option.value)}
|
|
65
|
+
<Segment value={option.value} label={option.label} icon={option.icon} />
|
|
66
|
+
{/each}
|
|
67
|
+
</ToggleGroup.Root>
|
|
68
|
+
</Tooltip.Provider>
|
|
69
|
+
<input type="hidden" {name} {value} {disabled} />
|
|
70
|
+
</FieldsetFrame>
|