@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,210 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
type CalendarDate,
|
|
4
|
+
type DateValue,
|
|
5
|
+
getLocalTimeZone,
|
|
6
|
+
parseDate,
|
|
7
|
+
today,
|
|
8
|
+
} from "@internationalized/date";
|
|
9
|
+
import CalendarIcon from "@lucide/svelte/icons/calendar";
|
|
10
|
+
import { MediaQuery } from "svelte/reactivity";
|
|
11
|
+
|
|
12
|
+
import { Button } from "../ui/button/index.js";
|
|
13
|
+
import { Calendar } from "../ui/calendar/index.js";
|
|
14
|
+
import * as Drawer from "../ui/drawer/index.js";
|
|
15
|
+
import * as Popover from "../ui/popover/index.js";
|
|
16
|
+
import { cn } from "../../utils.js";
|
|
17
|
+
|
|
18
|
+
import FieldFrame, { errorList, type FieldProps } from "./field-frame.svelte";
|
|
19
|
+
|
|
20
|
+
const defaultMessages = {
|
|
21
|
+
placeholder: "Pick a date",
|
|
22
|
+
today: "Today",
|
|
23
|
+
yesterday: "Yesterday",
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type Props = FieldProps & {
|
|
27
|
+
value?: string;
|
|
28
|
+
min?: string;
|
|
29
|
+
max?: string;
|
|
30
|
+
placeholder?: string;
|
|
31
|
+
messages?: Partial<typeof defaultMessages>;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
let {
|
|
35
|
+
value = $bindable(""),
|
|
36
|
+
min,
|
|
37
|
+
max,
|
|
38
|
+
placeholder,
|
|
39
|
+
messages = {},
|
|
40
|
+
name,
|
|
41
|
+
label,
|
|
42
|
+
description,
|
|
43
|
+
error,
|
|
44
|
+
required = false,
|
|
45
|
+
disabled = false,
|
|
46
|
+
id,
|
|
47
|
+
class: className,
|
|
48
|
+
}: Props = $props();
|
|
49
|
+
|
|
50
|
+
const desktop = new MediaQuery("(min-width: 768px)");
|
|
51
|
+
const nested = Drawer.isInsideDrawer();
|
|
52
|
+
const generatedId = $props.id();
|
|
53
|
+
const locale =
|
|
54
|
+
typeof navigator === "undefined" ? "en-US" : navigator.language;
|
|
55
|
+
|
|
56
|
+
let open = $state(false);
|
|
57
|
+
|
|
58
|
+
function toDate(iso: string | undefined): CalendarDate | undefined {
|
|
59
|
+
if (!iso) {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
return parseDate(iso);
|
|
64
|
+
} catch {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const text = $derived({ ...defaultMessages, ...messages });
|
|
70
|
+
const triggerId = $derived(id ?? generatedId);
|
|
71
|
+
const invalid = $derived(errorList(error).length > 0);
|
|
72
|
+
const describedBy = $derived(
|
|
73
|
+
invalid
|
|
74
|
+
? `${triggerId}-error`
|
|
75
|
+
: description
|
|
76
|
+
? `${triggerId}-description`
|
|
77
|
+
: undefined,
|
|
78
|
+
);
|
|
79
|
+
const selected = $derived(toDate(value));
|
|
80
|
+
const minValue = $derived(toDate(min));
|
|
81
|
+
const maxValue = $derived(toDate(max));
|
|
82
|
+
const todayValue = $derived(today(getLocalTimeZone()));
|
|
83
|
+
const shortcuts = $derived(
|
|
84
|
+
[
|
|
85
|
+
{ label: text.today, date: todayValue },
|
|
86
|
+
{ label: text.yesterday, date: todayValue.subtract({ days: 1 }) },
|
|
87
|
+
].filter(
|
|
88
|
+
(shortcut) =>
|
|
89
|
+
(!minValue || shortcut.date.compare(minValue) >= 0) &&
|
|
90
|
+
(!maxValue || shortcut.date.compare(maxValue) <= 0),
|
|
91
|
+
),
|
|
92
|
+
);
|
|
93
|
+
const display = $derived(
|
|
94
|
+
selected
|
|
95
|
+
? selected.toDate(getLocalTimeZone()).toLocaleDateString(locale, {
|
|
96
|
+
weekday: "short",
|
|
97
|
+
day: "numeric",
|
|
98
|
+
month: "long",
|
|
99
|
+
year: "numeric",
|
|
100
|
+
})
|
|
101
|
+
: undefined,
|
|
102
|
+
);
|
|
103
|
+
const triggerClass =
|
|
104
|
+
"border-input dark:bg-input/30 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 flex h-11 w-full items-center gap-2 rounded-md border bg-white px-3 text-left text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-3 aria-invalid:ring-3 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm";
|
|
105
|
+
|
|
106
|
+
function choose(next: DateValue | undefined) {
|
|
107
|
+
if (next) {
|
|
108
|
+
value = next.toString();
|
|
109
|
+
open = false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
</script>
|
|
113
|
+
|
|
114
|
+
{#snippet picker(touch: boolean)}
|
|
115
|
+
{#if shortcuts.length > 0}
|
|
116
|
+
<div class={cn("flex gap-2", touch ? "px-4 pb-1" : "px-3 pt-3")}>
|
|
117
|
+
{#each shortcuts as shortcut (shortcut.label)}
|
|
118
|
+
<Button
|
|
119
|
+
variant={selected?.compare(shortcut.date) === 0
|
|
120
|
+
? "default"
|
|
121
|
+
: "outline"}
|
|
122
|
+
size="sm"
|
|
123
|
+
class={cn("rounded-full", touch && "h-11 flex-1 text-base")}
|
|
124
|
+
onclick={() => choose(shortcut.date)}
|
|
125
|
+
>
|
|
126
|
+
{shortcut.label}
|
|
127
|
+
</Button>
|
|
128
|
+
{/each}
|
|
129
|
+
</div>
|
|
130
|
+
{/if}
|
|
131
|
+
<Calendar
|
|
132
|
+
type="single"
|
|
133
|
+
value={selected}
|
|
134
|
+
onValueChange={choose}
|
|
135
|
+
{minValue}
|
|
136
|
+
{maxValue}
|
|
137
|
+
{locale}
|
|
138
|
+
weekdayFormat="short"
|
|
139
|
+
class={cn(
|
|
140
|
+
"mx-auto bg-transparent",
|
|
141
|
+
touch && "[--cell-size:--spacing(11)] text-base",
|
|
142
|
+
)}
|
|
143
|
+
/>
|
|
144
|
+
{/snippet}
|
|
145
|
+
|
|
146
|
+
{#snippet trigger(props: Record<string, unknown>)}
|
|
147
|
+
<button
|
|
148
|
+
{...props}
|
|
149
|
+
type="button"
|
|
150
|
+
id={triggerId}
|
|
151
|
+
{disabled}
|
|
152
|
+
role="combobox"
|
|
153
|
+
aria-expanded={open}
|
|
154
|
+
aria-invalid={invalid || undefined}
|
|
155
|
+
aria-describedby={describedBy}
|
|
156
|
+
aria-required={required || undefined}
|
|
157
|
+
class={triggerClass}
|
|
158
|
+
>
|
|
159
|
+
<CalendarIcon class="text-muted-foreground size-4 shrink-0" />
|
|
160
|
+
<span
|
|
161
|
+
class={cn("min-w-0 flex-1 truncate", !display && "text-muted-foreground")}
|
|
162
|
+
>{display ?? placeholder ?? text.placeholder}</span
|
|
163
|
+
>
|
|
164
|
+
</button>
|
|
165
|
+
{/snippet}
|
|
166
|
+
|
|
167
|
+
{#snippet drawerContent()}
|
|
168
|
+
<Drawer.Content id="{triggerId}-drawer">
|
|
169
|
+
<Drawer.Header class="px-5 text-left">
|
|
170
|
+
<Drawer.Title>{label ?? placeholder ?? text.placeholder}</Drawer.Title>
|
|
171
|
+
</Drawer.Header>
|
|
172
|
+
{@render picker(true)}
|
|
173
|
+
</Drawer.Content>
|
|
174
|
+
{/snippet}
|
|
175
|
+
|
|
176
|
+
<FieldFrame
|
|
177
|
+
id={triggerId}
|
|
178
|
+
{label}
|
|
179
|
+
{description}
|
|
180
|
+
{error}
|
|
181
|
+
{required}
|
|
182
|
+
class={className}
|
|
183
|
+
>
|
|
184
|
+
{#if name}
|
|
185
|
+
<input type="hidden" {name} {value} {disabled} />
|
|
186
|
+
{/if}
|
|
187
|
+
{#if desktop.current}
|
|
188
|
+
<Popover.Root bind:open>
|
|
189
|
+
<Popover.Trigger {disabled}>
|
|
190
|
+
{#snippet child({ props })}
|
|
191
|
+
{@render trigger(props)}
|
|
192
|
+
{/snippet}
|
|
193
|
+
</Popover.Trigger>
|
|
194
|
+
<Popover.Content align="start" class="w-auto rounded-xl p-0">
|
|
195
|
+
{@render picker(false)}
|
|
196
|
+
</Popover.Content>
|
|
197
|
+
</Popover.Root>
|
|
198
|
+
{:else}
|
|
199
|
+
{@render trigger({
|
|
200
|
+
onclick: () => (open = true),
|
|
201
|
+
"aria-haspopup": "dialog",
|
|
202
|
+
"aria-controls": `${triggerId}-drawer`,
|
|
203
|
+
})}
|
|
204
|
+
{#if nested}
|
|
205
|
+
<Drawer.NestedRoot bind:open>{@render drawerContent()}</Drawer.NestedRoot>
|
|
206
|
+
{:else}
|
|
207
|
+
<Drawer.Root bind:open>{@render drawerContent()}</Drawer.Root>
|
|
208
|
+
{/if}
|
|
209
|
+
{/if}
|
|
210
|
+
</FieldFrame>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type FieldProps } from "./field-frame.svelte";
|
|
2
|
+
declare const DateField: import("svelte").Component<FieldProps & {
|
|
3
|
+
value?: string;
|
|
4
|
+
min?: string;
|
|
5
|
+
max?: string;
|
|
6
|
+
placeholder?: string;
|
|
7
|
+
messages?: Partial<{
|
|
8
|
+
placeholder: string;
|
|
9
|
+
today: string;
|
|
10
|
+
yesterday: string;
|
|
11
|
+
}>;
|
|
12
|
+
}, {}, "value">;
|
|
13
|
+
type DateField = ReturnType<typeof DateField>;
|
|
14
|
+
export default DateField;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
/** What every form component in the kit takes on top of its own props, so `Form` can render any of them. */
|
|
3
|
+
export interface FieldProps {
|
|
4
|
+
name?: string;
|
|
5
|
+
label?: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
error?: string | string[];
|
|
8
|
+
required?: boolean;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
id?: string;
|
|
11
|
+
class?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function errorList(error: string | string[] | undefined): string[] {
|
|
15
|
+
return error === undefined ? [] : [error].flat().filter(Boolean);
|
|
16
|
+
}
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<script lang="ts">
|
|
20
|
+
import type { Snippet } from "svelte";
|
|
21
|
+
|
|
22
|
+
import * as Field from "../ui/field/index.js";
|
|
23
|
+
|
|
24
|
+
interface Props {
|
|
25
|
+
id: string;
|
|
26
|
+
label?: string;
|
|
27
|
+
description?: string;
|
|
28
|
+
error?: string | string[];
|
|
29
|
+
required?: boolean;
|
|
30
|
+
orientation?: "vertical" | "horizontal";
|
|
31
|
+
class?: string;
|
|
32
|
+
children: Snippet;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const {
|
|
36
|
+
id,
|
|
37
|
+
label,
|
|
38
|
+
description,
|
|
39
|
+
error,
|
|
40
|
+
required = false,
|
|
41
|
+
orientation = "vertical",
|
|
42
|
+
class: className,
|
|
43
|
+
children,
|
|
44
|
+
}: Props = $props();
|
|
45
|
+
|
|
46
|
+
const errors = $derived(errorList(error));
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<Field.Field
|
|
50
|
+
{orientation}
|
|
51
|
+
class={className}
|
|
52
|
+
data-invalid={errors.length > 0 || undefined}
|
|
53
|
+
>
|
|
54
|
+
{#if label}
|
|
55
|
+
<Field.Label for={id}>
|
|
56
|
+
{label}
|
|
57
|
+
{#if required}
|
|
58
|
+
<span class="text-destructive" aria-hidden="true">*</span>
|
|
59
|
+
{/if}
|
|
60
|
+
</Field.Label>
|
|
61
|
+
{/if}
|
|
62
|
+
{@render children()}
|
|
63
|
+
{#if errors.length > 0}
|
|
64
|
+
<Field.Error
|
|
65
|
+
id="{id}-error"
|
|
66
|
+
errors={errors.map((message) => ({ message }))}
|
|
67
|
+
/>
|
|
68
|
+
{:else if description}
|
|
69
|
+
<Field.Description id="{id}-description">{description}</Field.Description>
|
|
70
|
+
{/if}
|
|
71
|
+
</Field.Field>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** What every form component in the kit takes on top of its own props, so `Form` can render any of them. */
|
|
2
|
+
export interface FieldProps {
|
|
3
|
+
name?: string;
|
|
4
|
+
label?: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
error?: string | string[];
|
|
7
|
+
required?: boolean;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
id?: string;
|
|
10
|
+
class?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function errorList(error: string | string[] | undefined): string[];
|
|
13
|
+
import type { Snippet } from "svelte";
|
|
14
|
+
interface Props {
|
|
15
|
+
id: string;
|
|
16
|
+
label?: string;
|
|
17
|
+
description?: string;
|
|
18
|
+
error?: string | string[];
|
|
19
|
+
required?: boolean;
|
|
20
|
+
orientation?: "vertical" | "horizontal";
|
|
21
|
+
class?: string;
|
|
22
|
+
children: Snippet;
|
|
23
|
+
}
|
|
24
|
+
declare const FieldFrame: import("svelte").Component<Props, {}, "">;
|
|
25
|
+
type FieldFrame = ReturnType<typeof FieldFrame>;
|
|
26
|
+
export default FieldFrame;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
|
|
4
|
+
import * as Field from "../ui/field/index.js";
|
|
5
|
+
import { cn } from "../../utils.js";
|
|
6
|
+
|
|
7
|
+
import { errorList } from "./field-frame.svelte";
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
id: string;
|
|
11
|
+
label?: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
error?: string | string[];
|
|
14
|
+
required?: boolean;
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
class?: string;
|
|
17
|
+
children: Snippet;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const {
|
|
21
|
+
id,
|
|
22
|
+
label,
|
|
23
|
+
description,
|
|
24
|
+
error,
|
|
25
|
+
required = false,
|
|
26
|
+
disabled = false,
|
|
27
|
+
class: className,
|
|
28
|
+
children,
|
|
29
|
+
}: Props = $props();
|
|
30
|
+
|
|
31
|
+
const errors = $derived(errorList(error));
|
|
32
|
+
const describedBy = $derived(
|
|
33
|
+
errors.length > 0
|
|
34
|
+
? `${id}-error`
|
|
35
|
+
: description
|
|
36
|
+
? `${id}-description`
|
|
37
|
+
: undefined,
|
|
38
|
+
);
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<Field.Set
|
|
42
|
+
{id}
|
|
43
|
+
{disabled}
|
|
44
|
+
aria-describedby={describedBy}
|
|
45
|
+
data-invalid={errors.length > 0 || undefined}
|
|
46
|
+
class={cn("w-full min-w-0 gap-3", className)}
|
|
47
|
+
>
|
|
48
|
+
{#if label}
|
|
49
|
+
<Field.Legend
|
|
50
|
+
id="{id}-label"
|
|
51
|
+
variant="label"
|
|
52
|
+
class={cn(errors.length > 0 && "text-destructive")}
|
|
53
|
+
>
|
|
54
|
+
{label}
|
|
55
|
+
{#if required}
|
|
56
|
+
<span class="text-destructive" aria-hidden="true">*</span>
|
|
57
|
+
{/if}
|
|
58
|
+
</Field.Legend>
|
|
59
|
+
{/if}
|
|
60
|
+
{@render children()}
|
|
61
|
+
{#if errors.length > 0}
|
|
62
|
+
<Field.Error
|
|
63
|
+
id="{id}-error"
|
|
64
|
+
errors={errors.map((message) => ({ message }))}
|
|
65
|
+
/>
|
|
66
|
+
{:else if description}
|
|
67
|
+
<Field.Description id="{id}-description">{description}</Field.Description>
|
|
68
|
+
{/if}
|
|
69
|
+
</Field.Set>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
interface Props {
|
|
3
|
+
id: string;
|
|
4
|
+
label?: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
error?: string | string[];
|
|
7
|
+
required?: boolean;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
class?: string;
|
|
10
|
+
children: Snippet;
|
|
11
|
+
}
|
|
12
|
+
declare const FieldsetFrame: import("svelte").Component<Props, {}, "">;
|
|
13
|
+
type FieldsetFrame = ReturnType<typeof FieldsetFrame>;
|
|
14
|
+
export default FieldsetFrame;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type FileRejection = "too-large" | "too-many" | "type";
|
|
2
|
+
export interface FileLimits {
|
|
3
|
+
accept?: string;
|
|
4
|
+
maxFiles?: number;
|
|
5
|
+
maxFileSize?: number;
|
|
6
|
+
}
|
|
7
|
+
/** A byte count as a short size, like 12 KB or 3.4 MB. */
|
|
8
|
+
export declare function displaySize(bytes: number): string;
|
|
9
|
+
/** Whether `file` matches an `accept` list of extensions (`.pdf`), wildcards (`image/*`) and types (`text/csv`). */
|
|
10
|
+
export declare function accepts(file: File, accept: string | undefined): boolean;
|
|
11
|
+
/** Why `file`, the `position`-th file counting the ones already kept, can't be added, or nothing when it can. */
|
|
12
|
+
export declare function rejectionOf(file: File, position: number, limits: FileLimits): FileRejection | undefined;
|
|
13
|
+
/** Every file under a dropped folder entry, with its path inside the folder kept as `webkitRelativePath`. */
|
|
14
|
+
export declare function filesIn(entry: FileSystemEntry): Promise<File[]>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const KILOBYTE = 1024;
|
|
2
|
+
const MEGABYTE = 1024 * KILOBYTE;
|
|
3
|
+
const GIGABYTE = 1024 * MEGABYTE;
|
|
4
|
+
/** A byte count as a short size, like 12 KB or 3.4 MB. */
|
|
5
|
+
export function displaySize(bytes) {
|
|
6
|
+
if (bytes < KILOBYTE) {
|
|
7
|
+
return `${bytes} B`;
|
|
8
|
+
}
|
|
9
|
+
if (bytes < MEGABYTE) {
|
|
10
|
+
return `${Math.round(bytes / KILOBYTE)} KB`;
|
|
11
|
+
}
|
|
12
|
+
if (bytes < GIGABYTE) {
|
|
13
|
+
return `${(bytes / MEGABYTE).toFixed(1).replace(/\.0$/, "")} MB`;
|
|
14
|
+
}
|
|
15
|
+
return `${(bytes / GIGABYTE).toFixed(1).replace(/\.0$/, "")} GB`;
|
|
16
|
+
}
|
|
17
|
+
/** Whether `file` matches an `accept` list of extensions (`.pdf`), wildcards (`image/*`) and types (`text/csv`). */
|
|
18
|
+
export function accepts(file, accept) {
|
|
19
|
+
if (!accept) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
const type = file.type.toLowerCase();
|
|
23
|
+
const name = file.name.toLowerCase();
|
|
24
|
+
return accept
|
|
25
|
+
.split(",")
|
|
26
|
+
.map((pattern) => pattern.trim().toLowerCase())
|
|
27
|
+
.some((pattern) => {
|
|
28
|
+
if (pattern.startsWith(".")) {
|
|
29
|
+
return name.endsWith(pattern);
|
|
30
|
+
}
|
|
31
|
+
if (pattern.endsWith("/*")) {
|
|
32
|
+
return type.startsWith(pattern.slice(0, -1));
|
|
33
|
+
}
|
|
34
|
+
return type === pattern;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/** Why `file`, the `position`-th file counting the ones already kept, can't be added, or nothing when it can. */
|
|
38
|
+
export function rejectionOf(file, position, limits) {
|
|
39
|
+
if (limits.maxFiles !== undefined && position > limits.maxFiles) {
|
|
40
|
+
return "too-many";
|
|
41
|
+
}
|
|
42
|
+
if (limits.maxFileSize !== undefined && file.size > limits.maxFileSize) {
|
|
43
|
+
return "too-large";
|
|
44
|
+
}
|
|
45
|
+
return accepts(file, limits.accept) ? undefined : "type";
|
|
46
|
+
}
|
|
47
|
+
/** Every file under a dropped folder entry, with its path inside the folder kept as `webkitRelativePath`. */
|
|
48
|
+
export async function filesIn(entry) {
|
|
49
|
+
if (entry.isFile) {
|
|
50
|
+
const file = await new Promise((resolve, reject) => entry.file(resolve, reject));
|
|
51
|
+
Object.defineProperty(file, "webkitRelativePath", {
|
|
52
|
+
value: entry.fullPath.slice(1),
|
|
53
|
+
});
|
|
54
|
+
return [file];
|
|
55
|
+
}
|
|
56
|
+
if (!entry.isDirectory) {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
const reader = entry.createReader();
|
|
60
|
+
const children = [];
|
|
61
|
+
for (;;) {
|
|
62
|
+
const batch = await new Promise((resolve, reject) => reader.readEntries(resolve, reject));
|
|
63
|
+
if (batch.length === 0) {
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
children.push(...batch);
|
|
67
|
+
}
|
|
68
|
+
return (await Promise.all(children.map(filesIn))).flat();
|
|
69
|
+
}
|