@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
|
@@ -12,10 +12,21 @@
|
|
|
12
12
|
chapters: Chapter[];
|
|
13
13
|
label: string;
|
|
14
14
|
onseek: (time: number) => void;
|
|
15
|
+
/** `overlay` over a video, white on dark; `surface` on the theme's own surfaces. */
|
|
16
|
+
variant?: "overlay" | "surface";
|
|
15
17
|
}
|
|
16
18
|
|
|
17
|
-
const {
|
|
18
|
-
|
|
19
|
+
const {
|
|
20
|
+
currentTime,
|
|
21
|
+
duration,
|
|
22
|
+
bufferedEnd,
|
|
23
|
+
chapters,
|
|
24
|
+
label,
|
|
25
|
+
onseek,
|
|
26
|
+
variant = "overlay",
|
|
27
|
+
}: Props = $props();
|
|
28
|
+
|
|
29
|
+
const overlay = $derived(variant === "overlay");
|
|
19
30
|
|
|
20
31
|
let track = $state<HTMLDivElement | null>(null);
|
|
21
32
|
let hoverRatio = $state<number | null>(null);
|
|
@@ -41,43 +52,59 @@
|
|
|
41
52
|
<div
|
|
42
53
|
bind:this={track}
|
|
43
54
|
role="presentation"
|
|
44
|
-
class="group/scrub relative flex h-
|
|
55
|
+
class="group/scrub relative flex h-8 items-center"
|
|
45
56
|
{onpointermove}
|
|
46
57
|
onpointerleave={() => (hoverRatio = null)}
|
|
47
58
|
>
|
|
48
59
|
<div
|
|
49
|
-
class="
|
|
60
|
+
class="pointer-events-none absolute inset-x-0 h-1.5 rounded-full transition-[height] group-hover/scrub:h-2 {overlay
|
|
61
|
+
? 'bg-white/15'
|
|
62
|
+
: 'bg-foreground/15'}"
|
|
50
63
|
></div>
|
|
51
64
|
<div
|
|
52
|
-
class="
|
|
65
|
+
class="pointer-events-none absolute h-1.5 rounded-full transition-[height] group-hover/scrub:h-2 {overlay
|
|
66
|
+
? 'bg-white/25'
|
|
67
|
+
: 'bg-foreground/20'}"
|
|
53
68
|
style:width="{ratio(bufferedEnd) * 100}%"
|
|
54
69
|
></div>
|
|
55
70
|
<div
|
|
56
|
-
class="
|
|
71
|
+
class="pointer-events-none absolute h-1.5 rounded-full transition-[height] group-hover/scrub:h-2 {overlay
|
|
72
|
+
? 'from-primary/60 to-primary bg-linear-to-r shadow-[0_0_12px_var(--color-primary)]'
|
|
73
|
+
: 'bg-primary glow'}"
|
|
57
74
|
style:width="{ratio(currentTime) * 100}%"
|
|
58
75
|
></div>
|
|
59
76
|
{#each chapters as chapter (chapter.start)}
|
|
60
77
|
{#if chapter.start > 0 && chapter.start < duration}
|
|
61
78
|
<div
|
|
62
|
-
class="
|
|
79
|
+
class="pointer-events-none absolute h-3 w-0.5 -translate-x-1/2 rounded-full {overlay
|
|
80
|
+
? 'bg-black/70'
|
|
81
|
+
: 'bg-background'}"
|
|
63
82
|
style:left="{ratio(chapter.start) * 100}%"
|
|
64
83
|
></div>
|
|
65
84
|
{/if}
|
|
66
85
|
{/each}
|
|
67
86
|
<div
|
|
68
|
-
class="
|
|
87
|
+
class="pointer-events-none absolute size-3.5 -translate-x-1/2 rounded-full shadow-md transition-transform group-focus-within/scrub:scale-125 group-hover/scrub:scale-125 {overlay
|
|
88
|
+
? 'ring-primary/40 bg-white ring-4'
|
|
89
|
+
: 'bg-primary ring-background ring-2'}"
|
|
69
90
|
style:left="{ratio(currentTime) * 100}%"
|
|
70
91
|
></div>
|
|
71
92
|
{#if hoverRatio !== null && duration > 0}
|
|
72
93
|
<div
|
|
73
|
-
class="
|
|
94
|
+
class="pointer-events-none absolute bottom-full mb-2 flex -translate-x-1/2 flex-col items-center px-2 py-1 text-xs font-medium tabular {overlay
|
|
95
|
+
? 'rounded-md bg-black/80 text-white ring-1 ring-white/10 backdrop-blur-md'
|
|
96
|
+
: 'bg-popover text-popover-foreground rounded-lg shadow-md'}"
|
|
74
97
|
style:left="{hoverRatio * 100}%"
|
|
75
98
|
>
|
|
76
99
|
{#if hovered?.title}
|
|
77
100
|
<span class="max-w-48 truncate font-semibold">{hovered.title}</span>
|
|
78
101
|
{/if}
|
|
79
|
-
<span
|
|
80
|
-
|
|
102
|
+
<span
|
|
103
|
+
class={hovered?.title
|
|
104
|
+
? overlay
|
|
105
|
+
? "text-white/60"
|
|
106
|
+
: "text-muted-foreground"
|
|
107
|
+
: undefined}>{formatTime(hoverRatio * duration)}</span
|
|
81
108
|
>
|
|
82
109
|
</div>
|
|
83
110
|
{/if}
|
|
@@ -90,6 +117,6 @@
|
|
|
90
117
|
aria-label={label}
|
|
91
118
|
aria-valuetext="{formatTime(currentTime)} / {formatTime(duration)}"
|
|
92
119
|
oninput={(event) => onseek(Number(event.currentTarget.value))}
|
|
93
|
-
class="relative h-
|
|
120
|
+
class="relative h-8 w-full cursor-pointer appearance-none bg-transparent focus-visible:outline-none [&::-moz-range-thumb]:size-5 [&::-moz-range-thumb]:appearance-none [&::-moz-range-thumb]:border-0 [&::-moz-range-thumb]:bg-transparent [&::-webkit-slider-thumb]:size-5 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:bg-transparent"
|
|
94
121
|
/>
|
|
95
122
|
</div>
|
|
@@ -6,6 +6,8 @@ interface Props {
|
|
|
6
6
|
chapters: Chapter[];
|
|
7
7
|
label: string;
|
|
8
8
|
onseek: (time: number) => void;
|
|
9
|
+
/** `overlay` over a video, white on dark; `surface` on the theme's own surfaces. */
|
|
10
|
+
variant?: "overlay" | "surface";
|
|
9
11
|
}
|
|
10
12
|
declare const ScrubBar: import("svelte").Component<Props, {}, "">;
|
|
11
13
|
type ScrubBar = ReturnType<typeof ScrubBar>;
|
|
@@ -255,12 +255,12 @@
|
|
|
255
255
|
|
|
256
256
|
<div
|
|
257
257
|
class={cn(
|
|
258
|
-
"pointer-events-none absolute inset-0 flex flex-col pt-safe-top pr-safe-right pb-safe-bottom pl-safe-left transition-[opacity,visibility] duration-200",
|
|
258
|
+
"pointer-events-none absolute inset-0 flex flex-col bg-linear-to-t from-black/60 via-transparent to-black/60 pt-safe-top pr-safe-right pb-safe-bottom pl-safe-left transition-[opacity,visibility] duration-200",
|
|
259
259
|
controlsShown && !player.failed ? "opacity-100" : "invisible opacity-0",
|
|
260
260
|
)}
|
|
261
261
|
>
|
|
262
262
|
{#if title}
|
|
263
|
-
<div class="
|
|
263
|
+
<div class="px-5 pt-4">
|
|
264
264
|
<p class="truncate font-semibold text-white drop-shadow">
|
|
265
265
|
{title}
|
|
266
266
|
</p>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
export interface AccordionEntry {
|
|
3
|
+
title: string;
|
|
4
|
+
content?: string;
|
|
5
|
+
value?: string;
|
|
6
|
+
}
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<script lang="ts" generics="Item extends AccordionEntry">
|
|
10
|
+
import type { Snippet } from "svelte";
|
|
11
|
+
|
|
12
|
+
import * as Accordion from "../ui/accordion/index.js";
|
|
13
|
+
import { cn } from "../../utils.js";
|
|
14
|
+
|
|
15
|
+
interface Props {
|
|
16
|
+
items: Item[];
|
|
17
|
+
type?: "single" | "multiple";
|
|
18
|
+
value?: string | string[];
|
|
19
|
+
children?: Snippet<[Item]>;
|
|
20
|
+
class?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let {
|
|
24
|
+
items,
|
|
25
|
+
type = "single",
|
|
26
|
+
value = $bindable(),
|
|
27
|
+
children,
|
|
28
|
+
class: className,
|
|
29
|
+
}: Props = $props();
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
{#snippet entries()}
|
|
33
|
+
{#each items as item, index (index)}
|
|
34
|
+
<Accordion.Item value={item.value ?? item.title}>
|
|
35
|
+
<Accordion.Trigger>{item.title}</Accordion.Trigger>
|
|
36
|
+
<Accordion.Content>
|
|
37
|
+
{#if children}
|
|
38
|
+
{@render children(item)}
|
|
39
|
+
{:else}
|
|
40
|
+
<p class="text-muted-foreground whitespace-pre-line">
|
|
41
|
+
{item.content}
|
|
42
|
+
</p>
|
|
43
|
+
{/if}
|
|
44
|
+
</Accordion.Content>
|
|
45
|
+
</Accordion.Item>
|
|
46
|
+
{/each}
|
|
47
|
+
{/snippet}
|
|
48
|
+
|
|
49
|
+
{#if type === "multiple"}
|
|
50
|
+
<Accordion.Root
|
|
51
|
+
type="multiple"
|
|
52
|
+
class={cn("w-full", className)}
|
|
53
|
+
bind:value={
|
|
54
|
+
() => (Array.isArray(value) ? value : value ? [value] : []),
|
|
55
|
+
(next) => (value = next)
|
|
56
|
+
}
|
|
57
|
+
>
|
|
58
|
+
{@render entries()}
|
|
59
|
+
</Accordion.Root>
|
|
60
|
+
{:else}
|
|
61
|
+
<Accordion.Root
|
|
62
|
+
type="single"
|
|
63
|
+
class={cn("w-full", className)}
|
|
64
|
+
bind:value={
|
|
65
|
+
() => (typeof value === "string" ? value : ""), (next) => (value = next)
|
|
66
|
+
}
|
|
67
|
+
>
|
|
68
|
+
{@render entries()}
|
|
69
|
+
</Accordion.Root>
|
|
70
|
+
{/if}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface AccordionEntry {
|
|
2
|
+
title: string;
|
|
3
|
+
content?: string;
|
|
4
|
+
value?: string;
|
|
5
|
+
}
|
|
6
|
+
import type { Snippet } from "svelte";
|
|
7
|
+
import * as Accordion from "../ui/accordion/index.js";
|
|
8
|
+
declare function $$render<Item extends AccordionEntry>(): {
|
|
9
|
+
props: {
|
|
10
|
+
items: Item[];
|
|
11
|
+
type?: "single" | "multiple";
|
|
12
|
+
value?: string | string[];
|
|
13
|
+
children?: Snippet<[Item]>;
|
|
14
|
+
class?: string;
|
|
15
|
+
};
|
|
16
|
+
exports: {};
|
|
17
|
+
bindings: "value";
|
|
18
|
+
slots: {};
|
|
19
|
+
events: {};
|
|
20
|
+
};
|
|
21
|
+
declare class __sveltets_Render<Item extends AccordionEntry> {
|
|
22
|
+
props(): ReturnType<typeof $$render<Item>>['props'];
|
|
23
|
+
events(): ReturnType<typeof $$render<Item>>['events'];
|
|
24
|
+
slots(): ReturnType<typeof $$render<Item>>['slots'];
|
|
25
|
+
bindings(): "value";
|
|
26
|
+
exports(): {};
|
|
27
|
+
}
|
|
28
|
+
interface $$IsomorphicComponent {
|
|
29
|
+
new <Item extends AccordionEntry>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<Item>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<Item>['props']>, ReturnType<__sveltets_Render<Item>['events']>, ReturnType<__sveltets_Render<Item>['slots']>> & {
|
|
30
|
+
$$bindings?: ReturnType<__sveltets_Render<Item>['bindings']>;
|
|
31
|
+
} & ReturnType<__sveltets_Render<Item>['exports']>;
|
|
32
|
+
<Item extends AccordionEntry>(internal: unknown, props: ReturnType<__sveltets_Render<Item>['props']> & {}): ReturnType<__sveltets_Render<Item>['exports']>;
|
|
33
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
34
|
+
}
|
|
35
|
+
declare const Accordion: $$IsomorphicComponent;
|
|
36
|
+
type Accordion<Item extends AccordionEntry> = InstanceType<typeof Accordion<Item>>;
|
|
37
|
+
export default Accordion;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
export interface AvatarPerson {
|
|
3
|
+
name: string;
|
|
4
|
+
src?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function initials(name: string): string {
|
|
8
|
+
return name
|
|
9
|
+
.trim()
|
|
10
|
+
.split(/\s+/)
|
|
11
|
+
.slice(0, 2)
|
|
12
|
+
.map((word) => word.charAt(0))
|
|
13
|
+
.join("")
|
|
14
|
+
.toUpperCase();
|
|
15
|
+
}
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<script lang="ts">
|
|
19
|
+
import * as Avatar from "../ui/avatar/index.js";
|
|
20
|
+
import * as Tooltip from "../ui/tooltip/index.js";
|
|
21
|
+
import { cn } from "../../utils.js";
|
|
22
|
+
|
|
23
|
+
interface Props {
|
|
24
|
+
people: AvatarPerson[];
|
|
25
|
+
max?: number;
|
|
26
|
+
size?: "sm" | "default" | "lg";
|
|
27
|
+
class?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const {
|
|
31
|
+
people,
|
|
32
|
+
max = 4,
|
|
33
|
+
size = "default",
|
|
34
|
+
class: className,
|
|
35
|
+
}: Props = $props();
|
|
36
|
+
|
|
37
|
+
const shown = $derived(people.slice(0, Math.max(0, max)));
|
|
38
|
+
const hidden = $derived(people.slice(shown.length));
|
|
39
|
+
const hiddenNames = $derived(hidden.map((person) => person.name).join(", "));
|
|
40
|
+
|
|
41
|
+
const boxSizes = { sm: "size-6", default: "size-8", lg: "size-10" };
|
|
42
|
+
const textSizes = {
|
|
43
|
+
sm: "text-[0.625rem]",
|
|
44
|
+
default: "text-xs",
|
|
45
|
+
lg: "text-sm",
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const triggerClass =
|
|
49
|
+
"bg-background ring-background focus-visible:ring-ring/50 relative shrink-0 rounded-full ring-2 outline-none focus-visible:ring-3";
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<div class={cn("flex w-fit items-center -space-x-2", className)}>
|
|
53
|
+
{#each shown as person, index (index)}
|
|
54
|
+
<Tooltip.Root>
|
|
55
|
+
<Tooltip.Trigger class={triggerClass} aria-label={person.name}>
|
|
56
|
+
<Avatar.Root {size}>
|
|
57
|
+
{#if person.src}
|
|
58
|
+
<Avatar.Image src={person.src} alt="" />
|
|
59
|
+
{/if}
|
|
60
|
+
<Avatar.Fallback class={cn("font-medium", textSizes[size])}>
|
|
61
|
+
{initials(person.name)}
|
|
62
|
+
</Avatar.Fallback>
|
|
63
|
+
</Avatar.Root>
|
|
64
|
+
</Tooltip.Trigger>
|
|
65
|
+
<Tooltip.Content>{person.name}</Tooltip.Content>
|
|
66
|
+
</Tooltip.Root>
|
|
67
|
+
{/each}
|
|
68
|
+
{#if hidden.length > 0}
|
|
69
|
+
<Tooltip.Root>
|
|
70
|
+
<Tooltip.Trigger class={triggerClass} aria-label={hiddenNames}>
|
|
71
|
+
<span
|
|
72
|
+
class={cn(
|
|
73
|
+
"bg-muted text-muted-foreground tabular flex items-center justify-center rounded-full font-medium",
|
|
74
|
+
boxSizes[size],
|
|
75
|
+
textSizes[size],
|
|
76
|
+
)}
|
|
77
|
+
>
|
|
78
|
+
+{hidden.length}
|
|
79
|
+
</span>
|
|
80
|
+
</Tooltip.Trigger>
|
|
81
|
+
<Tooltip.Content>{hiddenNames}</Tooltip.Content>
|
|
82
|
+
</Tooltip.Root>
|
|
83
|
+
{/if}
|
|
84
|
+
</div>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface AvatarPerson {
|
|
2
|
+
name: string;
|
|
3
|
+
src?: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function initials(name: string): string;
|
|
6
|
+
interface Props {
|
|
7
|
+
people: AvatarPerson[];
|
|
8
|
+
max?: number;
|
|
9
|
+
size?: "sm" | "default" | "lg";
|
|
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,104 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { Component } from "svelte";
|
|
3
|
+
|
|
4
|
+
export interface BarListItem {
|
|
5
|
+
label: string;
|
|
6
|
+
value: number;
|
|
7
|
+
href?: string;
|
|
8
|
+
icon?: Component<{ class?: string }>;
|
|
9
|
+
}
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<script lang="ts">
|
|
13
|
+
import { cn } from "../../utils.js";
|
|
14
|
+
|
|
15
|
+
const defaultMessages = {
|
|
16
|
+
empty: "Nothing to show yet.",
|
|
17
|
+
showAll: "Show all",
|
|
18
|
+
showLess: "Show less",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
interface Props {
|
|
22
|
+
items: BarListItem[];
|
|
23
|
+
format?: (value: number) => string;
|
|
24
|
+
max?: number;
|
|
25
|
+
messages?: Partial<typeof defaultMessages>;
|
|
26
|
+
class?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const {
|
|
30
|
+
items,
|
|
31
|
+
format = (value: number) => value.toLocaleString(),
|
|
32
|
+
max,
|
|
33
|
+
messages = {},
|
|
34
|
+
class: className,
|
|
35
|
+
}: Props = $props();
|
|
36
|
+
|
|
37
|
+
const text = $derived({ ...defaultMessages, ...messages });
|
|
38
|
+
const ranked = $derived(
|
|
39
|
+
items.toSorted((left, right) => right.value - left.value),
|
|
40
|
+
);
|
|
41
|
+
const largest = $derived(Math.max(0, ...ranked.map((item) => item.value)));
|
|
42
|
+
const collapsible = $derived(max !== undefined && ranked.length > max);
|
|
43
|
+
|
|
44
|
+
let expanded = $state(false);
|
|
45
|
+
|
|
46
|
+
const shown = $derived(
|
|
47
|
+
collapsible && !expanded ? ranked.slice(0, max) : ranked,
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
function width(value: number): string {
|
|
51
|
+
return largest > 0 ? `${(Math.max(0, value) / largest) * 100}%` : "0%";
|
|
52
|
+
}
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
{#snippet row(item: BarListItem)}
|
|
56
|
+
<span class="flex items-center gap-2 text-sm">
|
|
57
|
+
{#if item.icon}
|
|
58
|
+
<item.icon class="text-muted-foreground size-4 shrink-0" />
|
|
59
|
+
{/if}
|
|
60
|
+
<span class="min-w-0 flex-1 truncate">{item.label}</span>
|
|
61
|
+
<span class="tabular shrink-0 font-medium">{format(item.value)}</span>
|
|
62
|
+
</span>
|
|
63
|
+
<span class="bg-muted block h-2 rounded-r-full" aria-hidden="true">
|
|
64
|
+
<span
|
|
65
|
+
class="bg-primary block h-full rounded-r-full"
|
|
66
|
+
style:width={width(item.value)}
|
|
67
|
+
></span>
|
|
68
|
+
</span>
|
|
69
|
+
{/snippet}
|
|
70
|
+
|
|
71
|
+
<div class={cn("flex w-full flex-col gap-2", className)}>
|
|
72
|
+
{#if ranked.length === 0}
|
|
73
|
+
<p class="text-muted-foreground text-sm">{text.empty}</p>
|
|
74
|
+
{:else}
|
|
75
|
+
<ol class="flex flex-col gap-1">
|
|
76
|
+
{#each shown as item, index (index)}
|
|
77
|
+
<li>
|
|
78
|
+
{#if item.href}
|
|
79
|
+
<a
|
|
80
|
+
href={item.href}
|
|
81
|
+
class="hover:bg-muted focus-visible:ring-ring/50 flex flex-col gap-1.5 rounded-lg px-2 py-1.5 outline-none focus-visible:ring-3"
|
|
82
|
+
>
|
|
83
|
+
{@render row(item)}
|
|
84
|
+
</a>
|
|
85
|
+
{:else}
|
|
86
|
+
<div class="flex flex-col gap-1.5 px-2 py-1.5">
|
|
87
|
+
{@render row(item)}
|
|
88
|
+
</div>
|
|
89
|
+
{/if}
|
|
90
|
+
</li>
|
|
91
|
+
{/each}
|
|
92
|
+
</ol>
|
|
93
|
+
{#if collapsible}
|
|
94
|
+
<button
|
|
95
|
+
type="button"
|
|
96
|
+
aria-expanded={expanded}
|
|
97
|
+
onclick={() => (expanded = !expanded)}
|
|
98
|
+
class="text-primary focus-visible:ring-ring/50 h-11 self-start rounded-md px-2 text-sm font-medium outline-none hover:underline focus-visible:ring-3 md:h-9"
|
|
99
|
+
>
|
|
100
|
+
{expanded ? text.showLess : text.showAll}
|
|
101
|
+
</button>
|
|
102
|
+
{/if}
|
|
103
|
+
{/if}
|
|
104
|
+
</div>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Component } from "svelte";
|
|
2
|
+
export interface BarListItem {
|
|
3
|
+
label: string;
|
|
4
|
+
value: number;
|
|
5
|
+
href?: string;
|
|
6
|
+
icon?: Component<{
|
|
7
|
+
class?: string;
|
|
8
|
+
}>;
|
|
9
|
+
}
|
|
10
|
+
declare const BarList: Component<{
|
|
11
|
+
items: BarListItem[];
|
|
12
|
+
format?: (value: number) => string;
|
|
13
|
+
max?: number;
|
|
14
|
+
messages?: Partial<{
|
|
15
|
+
empty: string;
|
|
16
|
+
showAll: string;
|
|
17
|
+
showLess: string;
|
|
18
|
+
}>;
|
|
19
|
+
class?: string;
|
|
20
|
+
}, {}, "">;
|
|
21
|
+
type BarList = ReturnType<typeof BarList>;
|
|
22
|
+
export default BarList;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
export interface Crumb {
|
|
3
|
+
label: string;
|
|
4
|
+
href?: string;
|
|
5
|
+
}
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<script lang="ts">
|
|
9
|
+
import ChevronRightIcon from "@lucide/svelte/icons/chevron-right";
|
|
10
|
+
import EllipsisIcon from "@lucide/svelte/icons/ellipsis";
|
|
11
|
+
import { MediaQuery } from "svelte/reactivity";
|
|
12
|
+
|
|
13
|
+
import { cn } from "../../utils.js";
|
|
14
|
+
|
|
15
|
+
import Menu from "./menu.svelte";
|
|
16
|
+
|
|
17
|
+
const defaultMessages = {
|
|
18
|
+
label: "Breadcrumb",
|
|
19
|
+
more: "Show hidden pages",
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
interface Props {
|
|
23
|
+
items: Crumb[];
|
|
24
|
+
class?: string;
|
|
25
|
+
messages?: Partial<typeof defaultMessages>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const { items, class: className, messages = {} }: Props = $props();
|
|
29
|
+
|
|
30
|
+
const desktop = new MediaQuery("(min-width: 768px)");
|
|
31
|
+
const text = $derived({ ...defaultMessages, ...messages });
|
|
32
|
+
|
|
33
|
+
const visibleTail = $derived(desktop.current ? 2 : 1);
|
|
34
|
+
const collapsed = $derived(
|
|
35
|
+
items.length > (desktop.current ? 4 : 2)
|
|
36
|
+
? items.slice(1, items.length - visibleTail)
|
|
37
|
+
: [],
|
|
38
|
+
);
|
|
39
|
+
const shown = $derived(
|
|
40
|
+
collapsed.length > 0
|
|
41
|
+
? [...items.slice(0, 1), ...items.slice(items.length - visibleTail)]
|
|
42
|
+
: items,
|
|
43
|
+
);
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
{#snippet separator()}
|
|
47
|
+
<li role="presentation" aria-hidden="true" class="text-muted-foreground/70">
|
|
48
|
+
<ChevronRightIcon class="size-3.5" />
|
|
49
|
+
</li>
|
|
50
|
+
{/snippet}
|
|
51
|
+
|
|
52
|
+
<nav aria-label={text.label} class={cn("w-full min-w-0", className)}>
|
|
53
|
+
<ol class="text-muted-foreground flex min-w-0 items-center gap-1 text-sm">
|
|
54
|
+
{#each shown as crumb, index (index)}
|
|
55
|
+
{@const current = index === shown.length - 1}
|
|
56
|
+
{#if index > 0}
|
|
57
|
+
{@render separator()}
|
|
58
|
+
{/if}
|
|
59
|
+
<li class={cn("flex min-w-0 items-center", !current && "shrink-2")}>
|
|
60
|
+
{#if current}
|
|
61
|
+
<span aria-current="page" class="text-foreground truncate font-medium"
|
|
62
|
+
>{crumb.label}</span
|
|
63
|
+
>
|
|
64
|
+
{:else if crumb.href}
|
|
65
|
+
<a
|
|
66
|
+
href={crumb.href}
|
|
67
|
+
class="hover:text-foreground focus-visible:ring-ring/50 flex h-11 min-w-0 items-center rounded-md px-1 transition-colors outline-none focus-visible:ring-3 md:h-8"
|
|
68
|
+
>
|
|
69
|
+
<span class="truncate">{crumb.label}</span>
|
|
70
|
+
</a>
|
|
71
|
+
{:else}
|
|
72
|
+
<span class="truncate px-1">{crumb.label}</span>
|
|
73
|
+
{/if}
|
|
74
|
+
</li>
|
|
75
|
+
{#if index === 0 && collapsed.length > 0}
|
|
76
|
+
{@render separator()}
|
|
77
|
+
<li class="flex shrink-0 items-center">
|
|
78
|
+
<Menu
|
|
79
|
+
align="start"
|
|
80
|
+
label={text.more}
|
|
81
|
+
items={collapsed.map((hidden) => ({
|
|
82
|
+
label: hidden.label,
|
|
83
|
+
href: hidden.href,
|
|
84
|
+
disabled: !hidden.href,
|
|
85
|
+
}))}
|
|
86
|
+
>
|
|
87
|
+
{#snippet trigger({ props })}
|
|
88
|
+
<button
|
|
89
|
+
type="button"
|
|
90
|
+
{...props}
|
|
91
|
+
aria-label={text.more}
|
|
92
|
+
class="hover:text-foreground hover:bg-muted focus-visible:ring-ring/50 flex size-11 items-center justify-center rounded-md transition-colors outline-none focus-visible:ring-3 md:size-8"
|
|
93
|
+
>
|
|
94
|
+
<EllipsisIcon class="size-4" />
|
|
95
|
+
</button>
|
|
96
|
+
{/snippet}
|
|
97
|
+
</Menu>
|
|
98
|
+
</li>
|
|
99
|
+
{/if}
|
|
100
|
+
{/each}
|
|
101
|
+
</ol>
|
|
102
|
+
</nav>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface Crumb {
|
|
2
|
+
label: string;
|
|
3
|
+
href?: string;
|
|
4
|
+
}
|
|
5
|
+
declare const Breadcrumbs: import("svelte").Component<{
|
|
6
|
+
items: Crumb[];
|
|
7
|
+
class?: string;
|
|
8
|
+
messages?: Partial<{
|
|
9
|
+
label: string;
|
|
10
|
+
more: string;
|
|
11
|
+
}>;
|
|
12
|
+
}, {}, "">;
|
|
13
|
+
type Breadcrumbs = ReturnType<typeof Breadcrumbs>;
|
|
14
|
+
export default Breadcrumbs;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Checkbox } from "../ui/checkbox/index.js";
|
|
3
|
+
import { cn } from "../../utils.js";
|
|
4
|
+
|
|
5
|
+
import { errorList, type FieldProps } from "./field-frame.svelte";
|
|
6
|
+
import FieldsetFrame from "./fieldset-frame.svelte";
|
|
7
|
+
|
|
8
|
+
interface Option {
|
|
9
|
+
value: string;
|
|
10
|
+
label: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type Props = FieldProps & {
|
|
16
|
+
options: readonly Option[];
|
|
17
|
+
value?: string[];
|
|
18
|
+
orientation?: "vertical" | "horizontal";
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
let {
|
|
22
|
+
options,
|
|
23
|
+
value = $bindable([]),
|
|
24
|
+
orientation = "vertical",
|
|
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 listId = $derived(id ?? generatedId);
|
|
37
|
+
const invalid = $derived(errorList(error).length > 0);
|
|
38
|
+
|
|
39
|
+
function setChecked(optionValue: string, checked: boolean) {
|
|
40
|
+
value = checked
|
|
41
|
+
? [...value, optionValue]
|
|
42
|
+
: value.filter((entry) => entry !== optionValue);
|
|
43
|
+
}
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<FieldsetFrame
|
|
47
|
+
id={listId}
|
|
48
|
+
{label}
|
|
49
|
+
{description}
|
|
50
|
+
{error}
|
|
51
|
+
{required}
|
|
52
|
+
{disabled}
|
|
53
|
+
class={className}
|
|
54
|
+
>
|
|
55
|
+
<div
|
|
56
|
+
class={cn(
|
|
57
|
+
"flex",
|
|
58
|
+
orientation === "horizontal" ? "flex-row flex-wrap gap-x-6" : "flex-col",
|
|
59
|
+
)}
|
|
60
|
+
>
|
|
61
|
+
{#each options as option, index (option.value)}
|
|
62
|
+
{@const optionId = `${listId}-${index}`}
|
|
63
|
+
<div
|
|
64
|
+
class="group/row relative flex min-h-11 items-start gap-3 py-2.5 has-disabled:opacity-50"
|
|
65
|
+
>
|
|
66
|
+
<Checkbox
|
|
67
|
+
id={optionId}
|
|
68
|
+
{name}
|
|
69
|
+
value={option.value}
|
|
70
|
+
disabled={disabled || option.disabled}
|
|
71
|
+
bind:checked={
|
|
72
|
+
() => value.includes(option.value),
|
|
73
|
+
(checked) => setChecked(option.value, checked)
|
|
74
|
+
}
|
|
75
|
+
aria-invalid={invalid || undefined}
|
|
76
|
+
aria-describedby={option.description
|
|
77
|
+
? `${optionId}-description`
|
|
78
|
+
: undefined}
|
|
79
|
+
class="z-10 size-5"
|
|
80
|
+
/>
|
|
81
|
+
<span class="min-w-0 flex-1">
|
|
82
|
+
<label
|
|
83
|
+
for={optionId}
|
|
84
|
+
class="block cursor-pointer text-sm leading-snug font-medium after:absolute after:inset-0 group-has-disabled/row:cursor-not-allowed"
|
|
85
|
+
>{option.label}</label
|
|
86
|
+
>
|
|
87
|
+
{#if option.description}
|
|
88
|
+
<span
|
|
89
|
+
id="{optionId}-description"
|
|
90
|
+
class="text-muted-foreground mt-0.5 block text-sm"
|
|
91
|
+
>{option.description}</span
|
|
92
|
+
>
|
|
93
|
+
{/if}
|
|
94
|
+
</span>
|
|
95
|
+
</div>
|
|
96
|
+
{/each}
|
|
97
|
+
</div>
|
|
98
|
+
</FieldsetFrame>
|