@nqmcreative/ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +507 -0
- package/cli/index.mjs +410 -0
- package/dist/actions/anchor.d.ts +29 -0
- package/dist/actions/anchor.js +122 -0
- package/dist/actions/dismissable.d.ts +44 -0
- package/dist/actions/dismissable.js +147 -0
- package/dist/components/Accordion.svelte +14 -0
- package/dist/components/Accordion.svelte.d.ts +8 -0
- package/dist/components/AccordionItem.svelte +46 -0
- package/dist/components/AccordionItem.svelte.d.ts +14 -0
- package/dist/components/Alert.svelte +88 -0
- package/dist/components/Alert.svelte.d.ts +18 -0
- package/dist/components/Avatar.svelte +58 -0
- package/dist/components/Avatar.svelte.d.ts +16 -0
- package/dist/components/AvatarGroup.svelte +17 -0
- package/dist/components/AvatarGroup.svelte.d.ts +8 -0
- package/dist/components/Badge.svelte +53 -0
- package/dist/components/Badge.svelte.d.ts +18 -0
- package/dist/components/Breadcrumb.svelte +46 -0
- package/dist/components/Breadcrumb.svelte.d.ts +14 -0
- package/dist/components/Button.svelte +94 -0
- package/dist/components/Button.svelte.d.ts +21 -0
- package/dist/components/Calendar.svelte +206 -0
- package/dist/components/Calendar.svelte.d.ts +22 -0
- package/dist/components/Card.svelte +76 -0
- package/dist/components/Card.svelte.d.ts +21 -0
- package/dist/components/Checkbox.svelte +69 -0
- package/dist/components/Checkbox.svelte.d.ts +15 -0
- package/dist/components/CheckboxGroup.svelte +109 -0
- package/dist/components/CheckboxGroup.svelte.d.ts +30 -0
- package/dist/components/Combobox.svelte +268 -0
- package/dist/components/Combobox.svelte.d.ts +29 -0
- package/dist/components/CommandPalette.svelte +226 -0
- package/dist/components/CommandPalette.svelte.d.ts +32 -0
- package/dist/components/ConfirmDialog.svelte +97 -0
- package/dist/components/ConfirmDialog.svelte.d.ts +22 -0
- package/dist/components/ContextMenu.svelte +90 -0
- package/dist/components/ContextMenu.svelte.d.ts +15 -0
- package/dist/components/DatePicker.svelte +186 -0
- package/dist/components/DatePicker.svelte.d.ts +23 -0
- package/dist/components/Divider.svelte +30 -0
- package/dist/components/Divider.svelte.d.ts +10 -0
- package/dist/components/Drawer.svelte +161 -0
- package/dist/components/Drawer.svelte.d.ts +20 -0
- package/dist/components/Dropdown.svelte +112 -0
- package/dist/components/Dropdown.svelte.d.ts +23 -0
- package/dist/components/Dropzone.svelte +226 -0
- package/dist/components/Dropzone.svelte.d.ts +32 -0
- package/dist/components/EmptyState.svelte +57 -0
- package/dist/components/EmptyState.svelte.d.ts +18 -0
- package/dist/components/Field.svelte +42 -0
- package/dist/components/Field.svelte.d.ts +16 -0
- package/dist/components/Footer.svelte +93 -0
- package/dist/components/Footer.svelte.d.ts +25 -0
- package/dist/components/Input.svelte +60 -0
- package/dist/components/Input.svelte.d.ts +18 -0
- package/dist/components/InputGroup.svelte +19 -0
- package/dist/components/InputGroup.svelte.d.ts +10 -0
- package/dist/components/Kbd.svelte +17 -0
- package/dist/components/Kbd.svelte.d.ts +8 -0
- package/dist/components/Label.svelte +16 -0
- package/dist/components/Label.svelte.d.ts +9 -0
- package/dist/components/Link.svelte +44 -0
- package/dist/components/Link.svelte.d.ts +16 -0
- package/dist/components/LocaleProvider.svelte +24 -0
- package/dist/components/LocaleProvider.svelte.d.ts +10 -0
- package/dist/components/MenuItem.svelte +58 -0
- package/dist/components/MenuItem.svelte.d.ts +18 -0
- package/dist/components/MenuSeparator.svelte +22 -0
- package/dist/components/MenuSeparator.svelte.d.ts +8 -0
- package/dist/components/Modal.svelte +114 -0
- package/dist/components/Modal.svelte.d.ts +21 -0
- package/dist/components/MultiSelect.svelte +292 -0
- package/dist/components/MultiSelect.svelte.d.ts +31 -0
- package/dist/components/Navbar.svelte +190 -0
- package/dist/components/Navbar.svelte.d.ts +33 -0
- package/dist/components/NumberInput.svelte +100 -0
- package/dist/components/NumberInput.svelte.d.ts +15 -0
- package/dist/components/Pagination.svelte +90 -0
- package/dist/components/Pagination.svelte.d.ts +15 -0
- package/dist/components/PasswordInput.svelte +104 -0
- package/dist/components/PasswordInput.svelte.d.ts +12 -0
- package/dist/components/Popover.svelte +114 -0
- package/dist/components/Popover.svelte.d.ts +19 -0
- package/dist/components/Progress.svelte +56 -0
- package/dist/components/Progress.svelte.d.ts +17 -0
- package/dist/components/Radio.svelte +55 -0
- package/dist/components/Radio.svelte.d.ts +15 -0
- package/dist/components/RadioGroup.svelte +93 -0
- package/dist/components/RadioGroup.svelte.d.ts +27 -0
- package/dist/components/SegmentedControl.svelte +88 -0
- package/dist/components/SegmentedControl.svelte.d.ts +25 -0
- package/dist/components/Select.svelte +61 -0
- package/dist/components/Select.svelte.d.ts +20 -0
- package/dist/components/Sidebar.svelte +206 -0
- package/dist/components/Sidebar.svelte.d.ts +36 -0
- package/dist/components/Skeleton.svelte +46 -0
- package/dist/components/Skeleton.svelte.d.ts +13 -0
- package/dist/components/Slider.svelte +79 -0
- package/dist/components/Slider.svelte.d.ts +19 -0
- package/dist/components/Spinner.svelte +34 -0
- package/dist/components/Spinner.svelte.d.ts +13 -0
- package/dist/components/Stat.svelte +49 -0
- package/dist/components/Stat.svelte.d.ts +16 -0
- package/dist/components/Steps.svelte +136 -0
- package/dist/components/Steps.svelte.d.ts +24 -0
- package/dist/components/Switch.svelte +64 -0
- package/dist/components/Switch.svelte.d.ts +14 -0
- package/dist/components/Table.svelte +236 -0
- package/dist/components/Table.svelte.d.ts +70 -0
- package/dist/components/Tabs.svelte +91 -0
- package/dist/components/Tabs.svelte.d.ts +23 -0
- package/dist/components/Textarea.svelte +54 -0
- package/dist/components/Textarea.svelte.d.ts +12 -0
- package/dist/components/ThemeToggle.svelte +133 -0
- package/dist/components/ThemeToggle.svelte.d.ts +20 -0
- package/dist/components/Toaster.svelte +110 -0
- package/dist/components/Toaster.svelte.d.ts +8 -0
- package/dist/components/Tooltip.svelte +33 -0
- package/dist/components/Tooltip.svelte.d.ts +12 -0
- package/dist/date.d.ts +35 -0
- package/dist/date.js +108 -0
- package/dist/fonts.css +193 -0
- package/dist/index.d.ts +105 -0
- package/dist/index.js +77 -0
- package/dist/locale.svelte.d.ts +93 -0
- package/dist/locale.svelte.js +153 -0
- package/dist/theme.css +210 -0
- package/dist/toast.svelte.d.ts +47 -0
- package/dist/toast.svelte.js +60 -0
- package/dist/tones.d.ts +48 -0
- package/dist/tones.js +209 -0
- package/package.json +349 -0
- package/registry.json +669 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export interface SidebarItem {
|
|
2
|
+
/** Stable id used for the active state. Falls back to `href`. */
|
|
3
|
+
id?: string;
|
|
4
|
+
label: string;
|
|
5
|
+
href?: string;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
badge?: string | number;
|
|
8
|
+
/** Nested links — the parent becomes a collapsible group. */
|
|
9
|
+
items?: SidebarItem[];
|
|
10
|
+
/** Open the group on first render. */
|
|
11
|
+
open?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface SidebarSection {
|
|
14
|
+
/** Small heading above a run of items. */
|
|
15
|
+
label?: string;
|
|
16
|
+
items: SidebarItem[];
|
|
17
|
+
}
|
|
18
|
+
import type { Snippet } from 'svelte';
|
|
19
|
+
import { type Tone } from '../tones.js';
|
|
20
|
+
interface Props {
|
|
21
|
+
sections: SidebarSection[];
|
|
22
|
+
/** Bindable — the `id` (or `href`) of the current item. */
|
|
23
|
+
value?: string;
|
|
24
|
+
/** Bindable — narrow rail mode. */
|
|
25
|
+
collapsed?: boolean;
|
|
26
|
+
/** Show the collapse button. */
|
|
27
|
+
collapsible?: boolean;
|
|
28
|
+
tone?: Tone;
|
|
29
|
+
header?: Snippet;
|
|
30
|
+
footer?: Snippet;
|
|
31
|
+
onnavigate?: (item: SidebarItem) => void;
|
|
32
|
+
class?: string;
|
|
33
|
+
}
|
|
34
|
+
declare const Sidebar: import("svelte").Component<Props, {}, "value" | "collapsed">;
|
|
35
|
+
type Sidebar = ReturnType<typeof Sidebar>;
|
|
36
|
+
export default Sidebar;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
|
|
4
|
+
export type SkeletonVariant = 'text' | 'block' | 'circle';
|
|
5
|
+
|
|
6
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
7
|
+
variant?: SkeletonVariant;
|
|
8
|
+
/** `text` only — number of stacked lines, the last one runs short. */
|
|
9
|
+
lines?: number;
|
|
10
|
+
/** Any CSS length, e.g. `'12rem'` or `'60%'`. */
|
|
11
|
+
width?: string;
|
|
12
|
+
height?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let {
|
|
16
|
+
variant = 'text',
|
|
17
|
+
lines = 1,
|
|
18
|
+
width,
|
|
19
|
+
height,
|
|
20
|
+
class: className = '',
|
|
21
|
+
...rest
|
|
22
|
+
}: Props = $props();
|
|
23
|
+
|
|
24
|
+
const variants: Record<SkeletonVariant, string> = {
|
|
25
|
+
text: 'h-4 w-full',
|
|
26
|
+
block: 'h-24 w-full',
|
|
27
|
+
circle: 'size-10 rounded-full'
|
|
28
|
+
};
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
{#if variant === 'text' && lines > 1}
|
|
32
|
+
<div class="flex w-full flex-col gap-2 {className}" {...rest}>
|
|
33
|
+
{#each { length: lines }, i (i)}
|
|
34
|
+
<div
|
|
35
|
+
class="h-4 animate-pulse bg-bg-inset {i === lines - 1 ? 'w-2/3' : 'w-full'}"
|
|
36
|
+
style={width && i !== lines - 1 ? `width:${width}` : undefined}
|
|
37
|
+
></div>
|
|
38
|
+
{/each}
|
|
39
|
+
</div>
|
|
40
|
+
{:else}
|
|
41
|
+
<div
|
|
42
|
+
class="animate-pulse bg-bg-inset {variants[variant]} {className}"
|
|
43
|
+
style="{width ? `width:${width};` : ''}{height ? `height:${height};` : ''}"
|
|
44
|
+
{...rest}
|
|
45
|
+
></div>
|
|
46
|
+
{/if}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
export type SkeletonVariant = 'text' | 'block' | 'circle';
|
|
3
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
4
|
+
variant?: SkeletonVariant;
|
|
5
|
+
/** `text` only — number of stacked lines, the last one runs short. */
|
|
6
|
+
lines?: number;
|
|
7
|
+
/** Any CSS length, e.g. `'12rem'` or `'60%'`. */
|
|
8
|
+
width?: string;
|
|
9
|
+
height?: string;
|
|
10
|
+
}
|
|
11
|
+
declare const Skeleton: import("svelte").Component<Props, {}, "">;
|
|
12
|
+
type Skeleton = ReturnType<typeof Skeleton>;
|
|
13
|
+
export default Skeleton;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
3
|
+
import { toneText, type Tone } from '../tones.js';
|
|
4
|
+
|
|
5
|
+
interface Props extends Omit<HTMLInputAttributes, 'size' | 'type' | 'value' | 'min' | 'max'> {
|
|
6
|
+
value?: number;
|
|
7
|
+
min?: number;
|
|
8
|
+
max?: number;
|
|
9
|
+
step?: number;
|
|
10
|
+
tone?: Tone;
|
|
11
|
+
label?: string;
|
|
12
|
+
/** Prints the current value on the right of the label row. */
|
|
13
|
+
showValue?: boolean;
|
|
14
|
+
/** Appended to the printed value, e.g. `'%'`. */
|
|
15
|
+
unit?: string;
|
|
16
|
+
/** Tick labels under the track — usually just `[min, max]`. */
|
|
17
|
+
marks?: (number | string)[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let {
|
|
21
|
+
value = $bindable(50),
|
|
22
|
+
min = 0,
|
|
23
|
+
max = 100,
|
|
24
|
+
step = 1,
|
|
25
|
+
tone = 'brand',
|
|
26
|
+
label,
|
|
27
|
+
showValue = false,
|
|
28
|
+
unit = '',
|
|
29
|
+
marks,
|
|
30
|
+
disabled = false,
|
|
31
|
+
class: className = '',
|
|
32
|
+
...rest
|
|
33
|
+
}: Props = $props();
|
|
34
|
+
|
|
35
|
+
const pct = $derived(Math.min(100, Math.max(0, ((value - min) / (max - min)) * 100)));
|
|
36
|
+
|
|
37
|
+
// The filled part of the track and the thumb both paint with `currentColor`,
|
|
38
|
+
// so a single tone class on the input drives every vendor pseudo-element.
|
|
39
|
+
// The gradient classes below are spelled out twice on purpose: Tailwind only
|
|
40
|
+
// sees literal class strings, so a shared JS constant would produce nothing.
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
<div class="flex w-full flex-col gap-2 {disabled ? 'opacity-50' : ''} {className}">
|
|
44
|
+
{#if label || showValue}
|
|
45
|
+
<div class="flex items-baseline justify-between gap-4 font-sans text-sm">
|
|
46
|
+
{#if label}<span class="text-text-secondary">{label}</span>{/if}
|
|
47
|
+
{#if showValue}
|
|
48
|
+
<span class="font-mono text-xs tabular-nums {toneText[tone]}">{value}{unit}</span>
|
|
49
|
+
{/if}
|
|
50
|
+
</div>
|
|
51
|
+
{/if}
|
|
52
|
+
|
|
53
|
+
<input
|
|
54
|
+
type="range"
|
|
55
|
+
bind:value
|
|
56
|
+
{min}
|
|
57
|
+
{max}
|
|
58
|
+
{step}
|
|
59
|
+
{disabled}
|
|
60
|
+
aria-label={label}
|
|
61
|
+
style="--pct:{pct}%"
|
|
62
|
+
class="h-5 w-full cursor-pointer appearance-none bg-transparent focus:outline-none disabled:cursor-not-allowed
|
|
63
|
+
{toneText[tone]}
|
|
64
|
+
focus-visible:outline-2 focus-visible:outline-offset-4
|
|
65
|
+
[&::-moz-range-thumb]:size-4 [&::-moz-range-thumb]:rounded-full
|
|
66
|
+
[&::-moz-range-thumb]:border-2 [&::-moz-range-thumb]:border-bg [&::-moz-range-thumb]:bg-current [&::-moz-range-track]:h-1 [&::-moz-range-track]:bg-[linear-gradient(to_right,currentColor_var(--pct),var(--color-bg-inset)_var(--pct))] [&::-webkit-slider-runnable-track]:h-1 [&::-webkit-slider-runnable-track]:bg-[linear-gradient(to_right,currentColor_var(--pct),var(--color-bg-inset)_var(--pct))]
|
|
67
|
+
[&::-webkit-slider-thumb]:-mt-1.5 [&::-webkit-slider-thumb]:size-4 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:border-2
|
|
68
|
+
[&::-webkit-slider-thumb]:border-bg [&::-webkit-slider-thumb]:bg-current"
|
|
69
|
+
{...rest}
|
|
70
|
+
/>
|
|
71
|
+
|
|
72
|
+
{#if marks?.length}
|
|
73
|
+
<div class="flex justify-between font-mono text-[11px] text-text-muted">
|
|
74
|
+
{#each marks as mark (mark)}
|
|
75
|
+
<span>{mark}</span>
|
|
76
|
+
{/each}
|
|
77
|
+
</div>
|
|
78
|
+
{/if}
|
|
79
|
+
</div>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
2
|
+
import { type Tone } from '../tones.js';
|
|
3
|
+
interface Props extends Omit<HTMLInputAttributes, 'size' | 'type' | 'value' | 'min' | 'max'> {
|
|
4
|
+
value?: number;
|
|
5
|
+
min?: number;
|
|
6
|
+
max?: number;
|
|
7
|
+
step?: number;
|
|
8
|
+
tone?: Tone;
|
|
9
|
+
label?: string;
|
|
10
|
+
/** Prints the current value on the right of the label row. */
|
|
11
|
+
showValue?: boolean;
|
|
12
|
+
/** Appended to the printed value, e.g. `'%'`. */
|
|
13
|
+
unit?: string;
|
|
14
|
+
/** Tick labels under the track — usually just `[min, max]`. */
|
|
15
|
+
marks?: (number | string)[];
|
|
16
|
+
}
|
|
17
|
+
declare const Slider: import("svelte").Component<Props, {}, "value">;
|
|
18
|
+
type Slider = ReturnType<typeof Slider>;
|
|
19
|
+
export default Slider;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import { toneText, type Tone } from '../tones.js';
|
|
4
|
+
import { useLocale } from '../locale.svelte.js';
|
|
5
|
+
|
|
6
|
+
export type SpinnerSize = 'xs' | 'sm' | 'md' | 'lg';
|
|
7
|
+
|
|
8
|
+
interface Props extends HTMLAttributes<HTMLSpanElement> {
|
|
9
|
+
size?: SpinnerSize;
|
|
10
|
+
/** Omit to inherit the parent's `currentColor` (e.g. inside a Button). */
|
|
11
|
+
tone?: Tone;
|
|
12
|
+
/** Defaults to the locale's loading string; pass `''` to hide it. */
|
|
13
|
+
label?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const t = useLocale();
|
|
17
|
+
|
|
18
|
+
let { size = 'md', tone, label, class: className = '', ...rest }: Props = $props();
|
|
19
|
+
|
|
20
|
+
const sizes: Record<SpinnerSize, string> = {
|
|
21
|
+
xs: 'size-3 border',
|
|
22
|
+
sm: 'size-4 border',
|
|
23
|
+
md: 'size-5 border-2',
|
|
24
|
+
lg: 'size-8 border-2'
|
|
25
|
+
};
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<span
|
|
29
|
+
role="status"
|
|
30
|
+
aria-label={label ?? t.current.loading}
|
|
31
|
+
class="inline-block shrink-0 animate-spin rounded-full border-current border-t-transparent
|
|
32
|
+
{sizes[size]} {tone ? toneText[tone] : ''} {className}"
|
|
33
|
+
{...rest}
|
|
34
|
+
></span>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
import { type Tone } from '../tones.js';
|
|
3
|
+
export type SpinnerSize = 'xs' | 'sm' | 'md' | 'lg';
|
|
4
|
+
interface Props extends HTMLAttributes<HTMLSpanElement> {
|
|
5
|
+
size?: SpinnerSize;
|
|
6
|
+
/** Omit to inherit the parent's `currentColor` (e.g. inside a Button). */
|
|
7
|
+
tone?: Tone;
|
|
8
|
+
/** Defaults to the locale's loading string; pass `''` to hide it. */
|
|
9
|
+
label?: string;
|
|
10
|
+
}
|
|
11
|
+
declare const Spinner: import("svelte").Component<Props, {}, "">;
|
|
12
|
+
type Spinner = ReturnType<typeof Spinner>;
|
|
13
|
+
export default Spinner;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import { toneText, type Tone } from '../tones.js';
|
|
4
|
+
|
|
5
|
+
export type StatTrend = 'up' | 'down' | 'flat';
|
|
6
|
+
|
|
7
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
8
|
+
label: string;
|
|
9
|
+
value: string | number;
|
|
10
|
+
/** Secondary figure, e.g. `'+12.4%'`. */
|
|
11
|
+
delta?: string;
|
|
12
|
+
trend?: StatTrend;
|
|
13
|
+
hint?: string;
|
|
14
|
+
/** Colour of the value itself. Leave unset for plain `text`. */
|
|
15
|
+
tone?: Tone;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let {
|
|
19
|
+
label,
|
|
20
|
+
value,
|
|
21
|
+
delta,
|
|
22
|
+
trend = 'flat',
|
|
23
|
+
hint,
|
|
24
|
+
tone,
|
|
25
|
+
class: className = '',
|
|
26
|
+
...rest
|
|
27
|
+
}: Props = $props();
|
|
28
|
+
|
|
29
|
+
const trendTone: Record<StatTrend, Tone> = { up: 'success', down: 'danger', flat: 'neutral' };
|
|
30
|
+
const trendGlyph: Record<StatTrend, string> = { up: '↑', down: '↓', flat: '→' };
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<div class="flex flex-col gap-1 font-sans {className}" {...rest}>
|
|
34
|
+
<span class="font-mono text-xs tracking-wide text-text-muted uppercase">{label}</span>
|
|
35
|
+
<div class="flex items-baseline gap-2">
|
|
36
|
+
<span
|
|
37
|
+
class="font-heading text-3xl font-medium tracking-tight {tone ? toneText[tone] : 'text-text'}"
|
|
38
|
+
>
|
|
39
|
+
{value}
|
|
40
|
+
</span>
|
|
41
|
+
{#if delta}
|
|
42
|
+
<span class="font-mono text-xs {toneText[trendTone[trend]]}">
|
|
43
|
+
{trendGlyph[trend]}
|
|
44
|
+
{delta}
|
|
45
|
+
</span>
|
|
46
|
+
{/if}
|
|
47
|
+
</div>
|
|
48
|
+
{#if hint}<span class="text-sm text-text-muted">{hint}</span>{/if}
|
|
49
|
+
</div>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
import { type Tone } from '../tones.js';
|
|
3
|
+
export type StatTrend = 'up' | 'down' | 'flat';
|
|
4
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
label: string;
|
|
6
|
+
value: string | number;
|
|
7
|
+
/** Secondary figure, e.g. `'+12.4%'`. */
|
|
8
|
+
delta?: string;
|
|
9
|
+
trend?: StatTrend;
|
|
10
|
+
hint?: string;
|
|
11
|
+
/** Colour of the value itself. Leave unset for plain `text`. */
|
|
12
|
+
tone?: Tone;
|
|
13
|
+
}
|
|
14
|
+
declare const Stat: import("svelte").Component<Props, {}, "">;
|
|
15
|
+
type Stat = ReturnType<typeof Stat>;
|
|
16
|
+
export default Stat;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
export interface StepItem {
|
|
3
|
+
label: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
/** Marks the step as failed — shown in `danger` regardless of position. */
|
|
6
|
+
error?: boolean;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
}
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<script lang="ts">
|
|
12
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
13
|
+
import { focusRing, toneFill, toneRing, toneText, type Tone } from '../tones.js';
|
|
14
|
+
|
|
15
|
+
interface Props extends Omit<HTMLAttributes<HTMLElement>, 'onchange'> {
|
|
16
|
+
items: StepItem[];
|
|
17
|
+
/** Bindable, zero-based. Steps before it count as done. */
|
|
18
|
+
current?: number;
|
|
19
|
+
orientation?: 'horizontal' | 'vertical';
|
|
20
|
+
tone?: Tone;
|
|
21
|
+
/** Let the user jump to a completed step by clicking it. */
|
|
22
|
+
clickable?: boolean;
|
|
23
|
+
/** Hide the connecting rule between steps. */
|
|
24
|
+
bare?: boolean;
|
|
25
|
+
onchange?: (index: number) => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let {
|
|
29
|
+
items,
|
|
30
|
+
current = $bindable(0),
|
|
31
|
+
orientation = 'horizontal',
|
|
32
|
+
tone = 'brand',
|
|
33
|
+
clickable = false,
|
|
34
|
+
bare = false,
|
|
35
|
+
class: className = '',
|
|
36
|
+
onchange,
|
|
37
|
+
...rest
|
|
38
|
+
}: Props = $props();
|
|
39
|
+
|
|
40
|
+
function status(index: number): 'done' | 'current' | 'todo' {
|
|
41
|
+
if (index < current) return 'done';
|
|
42
|
+
if (index === current) return 'current';
|
|
43
|
+
return 'todo';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const marker =
|
|
47
|
+
'flex size-7 shrink-0 items-center justify-center rounded-full border font-mono text-[11px] transition-colors duration-150 ease-brand-out';
|
|
48
|
+
|
|
49
|
+
function markerTone(state: 'done' | 'current' | 'todo', item: StepItem) {
|
|
50
|
+
if (item.error) return 'border-danger bg-danger-light text-danger';
|
|
51
|
+
if (state === 'done') return `${toneFill[tone]} border-transparent text-text-inverse`;
|
|
52
|
+
if (state === 'current') return `border-current bg-bg ${toneText[tone]}`;
|
|
53
|
+
return 'border-hairline-strong bg-bg text-text-muted';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function go(index: number, item: StepItem) {
|
|
57
|
+
if (!clickable || item.disabled || index > current) return;
|
|
58
|
+
current = index;
|
|
59
|
+
onchange?.(index);
|
|
60
|
+
}
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
{#snippet glyph(index: number, state: 'done' | 'current' | 'todo', item: StepItem)}
|
|
64
|
+
{#if item.error}
|
|
65
|
+
!
|
|
66
|
+
{:else if state === 'done'}
|
|
67
|
+
<svg class="size-3.5" viewBox="0 0 14 14" fill="none" aria-hidden="true">
|
|
68
|
+
<path d="m2 7 3.2 3.2L12 3.6" stroke="currentColor" stroke-width="1.8" />
|
|
69
|
+
</svg>
|
|
70
|
+
{:else}
|
|
71
|
+
{index + 1}
|
|
72
|
+
{/if}
|
|
73
|
+
{/snippet}
|
|
74
|
+
|
|
75
|
+
<nav
|
|
76
|
+
aria-label="Progress"
|
|
77
|
+
class="flex w-full {orientation === 'vertical' ? 'flex-col gap-0' : 'flex-row'} {className}"
|
|
78
|
+
{...rest}
|
|
79
|
+
>
|
|
80
|
+
{#each items as item, i (item.label)}
|
|
81
|
+
{@const state = status(i)}
|
|
82
|
+
<div
|
|
83
|
+
class="flex min-w-0 {orientation === 'vertical' ? 'flex-row gap-3' : 'flex-1 flex-col gap-2'}"
|
|
84
|
+
>
|
|
85
|
+
<!-- marker + rule -->
|
|
86
|
+
<div
|
|
87
|
+
class="flex shrink-0 items-center {orientation === 'vertical'
|
|
88
|
+
? 'flex-col self-stretch'
|
|
89
|
+
: 'w-full flex-row'}"
|
|
90
|
+
>
|
|
91
|
+
{#if clickable && state !== 'todo' && !item.disabled}
|
|
92
|
+
<button
|
|
93
|
+
type="button"
|
|
94
|
+
onclick={() => go(i, item)}
|
|
95
|
+
aria-current={state === 'current' ? 'step' : undefined}
|
|
96
|
+
class="{marker} cursor-pointer {markerTone(state, item)} {focusRing} {toneRing[tone]}"
|
|
97
|
+
>
|
|
98
|
+
{@render glyph(i, state, item)}
|
|
99
|
+
</button>
|
|
100
|
+
{:else}
|
|
101
|
+
<div
|
|
102
|
+
aria-current={state === 'current' ? 'step' : undefined}
|
|
103
|
+
class="{marker} {markerTone(state, item)}"
|
|
104
|
+
>
|
|
105
|
+
{@render glyph(i, state, item)}
|
|
106
|
+
</div>
|
|
107
|
+
{/if}
|
|
108
|
+
|
|
109
|
+
{#if !bare && i < items.length - 1}
|
|
110
|
+
<span
|
|
111
|
+
class="{orientation === 'vertical' ? 'my-1 w-px flex-1' : 'mx-3 h-px flex-1'} {i <
|
|
112
|
+
current
|
|
113
|
+
? toneFill[tone]
|
|
114
|
+
: 'bg-hairline'}"
|
|
115
|
+
></span>
|
|
116
|
+
{/if}
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<!-- copy -->
|
|
120
|
+
<div class="flex min-w-0 flex-col gap-0.5 {orientation === 'vertical' ? 'pb-6' : 'pr-3'}">
|
|
121
|
+
<span
|
|
122
|
+
class="font-sans text-sm leading-snug break-words {state === 'todo'
|
|
123
|
+
? 'text-text-muted'
|
|
124
|
+
: 'font-medium text-text'}"
|
|
125
|
+
>
|
|
126
|
+
{item.label}
|
|
127
|
+
</span>
|
|
128
|
+
{#if item.description}
|
|
129
|
+
<span class="font-sans text-xs leading-snug break-words text-text-muted">
|
|
130
|
+
{item.description}
|
|
131
|
+
</span>
|
|
132
|
+
{/if}
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
{/each}
|
|
136
|
+
</nav>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface StepItem {
|
|
2
|
+
label: string;
|
|
3
|
+
description?: string;
|
|
4
|
+
/** Marks the step as failed — shown in `danger` regardless of position. */
|
|
5
|
+
error?: boolean;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
}
|
|
8
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
9
|
+
import { type Tone } from '../tones.js';
|
|
10
|
+
interface Props extends Omit<HTMLAttributes<HTMLElement>, 'onchange'> {
|
|
11
|
+
items: StepItem[];
|
|
12
|
+
/** Bindable, zero-based. Steps before it count as done. */
|
|
13
|
+
current?: number;
|
|
14
|
+
orientation?: 'horizontal' | 'vertical';
|
|
15
|
+
tone?: Tone;
|
|
16
|
+
/** Let the user jump to a completed step by clicking it. */
|
|
17
|
+
clickable?: boolean;
|
|
18
|
+
/** Hide the connecting rule between steps. */
|
|
19
|
+
bare?: boolean;
|
|
20
|
+
onchange?: (index: number) => void;
|
|
21
|
+
}
|
|
22
|
+
declare const Steps: import("svelte").Component<Props, {}, "current">;
|
|
23
|
+
type Steps = ReturnType<typeof Steps>;
|
|
24
|
+
export default Steps;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
3
|
+
import { focusRing, toneFill, toneRing, type Tone } from '../tones.js';
|
|
4
|
+
|
|
5
|
+
export type SwitchSize = 'sm' | 'md';
|
|
6
|
+
|
|
7
|
+
interface Props extends Omit<HTMLButtonAttributes, 'onclick' | 'onchange'> {
|
|
8
|
+
checked?: boolean;
|
|
9
|
+
label?: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
tone?: Tone;
|
|
12
|
+
size?: SwitchSize;
|
|
13
|
+
onchange?: (checked: boolean) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let {
|
|
17
|
+
checked = $bindable(false),
|
|
18
|
+
label,
|
|
19
|
+
description,
|
|
20
|
+
tone = 'brand',
|
|
21
|
+
size = 'md',
|
|
22
|
+
disabled = false,
|
|
23
|
+
onchange,
|
|
24
|
+
class: className = '',
|
|
25
|
+
...rest
|
|
26
|
+
}: Props = $props();
|
|
27
|
+
|
|
28
|
+
const sizes: Record<SwitchSize, { track: string; thumb: string; travel: string }> = {
|
|
29
|
+
sm: { track: 'h-5 w-9', thumb: 'size-4', travel: 'translate-x-4' },
|
|
30
|
+
md: { track: 'h-6 w-11', thumb: 'size-5', travel: 'translate-x-5' }
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
function toggle() {
|
|
34
|
+
checked = !checked;
|
|
35
|
+
onchange?.(checked);
|
|
36
|
+
}
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<div class="inline-flex items-start gap-3 {disabled ? 'opacity-50' : ''} {className}">
|
|
40
|
+
<button
|
|
41
|
+
type="button"
|
|
42
|
+
role="switch"
|
|
43
|
+
aria-checked={checked}
|
|
44
|
+
aria-label={label}
|
|
45
|
+
{disabled}
|
|
46
|
+
onclick={toggle}
|
|
47
|
+
class="relative mt-0.5 inline-flex shrink-0 items-center rounded-full p-0.5 transition-colors duration-200 ease-brand-out disabled:pointer-events-none
|
|
48
|
+
{sizes[size].track} {checked ? toneFill[tone] : 'bg-hairline-strong'} {focusRing} {toneRing[tone]}"
|
|
49
|
+
{...rest}
|
|
50
|
+
>
|
|
51
|
+
<span
|
|
52
|
+
class="rounded-full bg-bg transition-transform duration-200 ease-brand-out
|
|
53
|
+
{sizes[size].thumb} {checked ? sizes[size].travel : 'translate-x-0'}"
|
|
54
|
+
></span>
|
|
55
|
+
</button>
|
|
56
|
+
{#if label || description}
|
|
57
|
+
<span class="flex flex-col gap-0.5 font-sans text-[15px]">
|
|
58
|
+
{#if label}<span class="leading-snug text-text">{label}</span>{/if}
|
|
59
|
+
{#if description}
|
|
60
|
+
<span class="text-sm leading-snug text-text-muted">{description}</span>
|
|
61
|
+
{/if}
|
|
62
|
+
</span>
|
|
63
|
+
{/if}
|
|
64
|
+
</div>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
2
|
+
import { type Tone } from '../tones.js';
|
|
3
|
+
export type SwitchSize = 'sm' | 'md';
|
|
4
|
+
interface Props extends Omit<HTMLButtonAttributes, 'onclick' | 'onchange'> {
|
|
5
|
+
checked?: boolean;
|
|
6
|
+
label?: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
tone?: Tone;
|
|
9
|
+
size?: SwitchSize;
|
|
10
|
+
onchange?: (checked: boolean) => void;
|
|
11
|
+
}
|
|
12
|
+
declare const Switch: import("svelte").Component<Props, {}, "checked">;
|
|
13
|
+
type Switch = ReturnType<typeof Switch>;
|
|
14
|
+
export default Switch;
|