@nqmcreative/ui 0.1.1 → 0.2.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/README.md +89 -34
- package/cli/index.mjs +67 -30
- package/dist/core/calendar.d.ts +3 -0
- package/dist/core/calendar.js +36 -0
- package/dist/core/dialog.svelte.d.ts +14 -0
- package/dist/core/dialog.svelte.js +23 -0
- package/dist/core/files.d.ts +35 -0
- package/dist/core/files.js +62 -0
- package/dist/core/index.d.ts +37 -0
- package/dist/core/index.js +35 -0
- package/dist/core/list.svelte.d.ts +41 -0
- package/dist/core/list.svelte.js +77 -0
- package/dist/core/number.d.ts +7 -0
- package/dist/core/number.js +17 -0
- package/dist/core/pagination.d.ts +6 -0
- package/dist/core/pagination.js +21 -0
- package/dist/core/password.d.ts +8 -0
- package/dist/core/password.js +23 -0
- package/dist/core/table.d.ts +22 -0
- package/dist/core/table.js +41 -0
- package/dist/core/theme.svelte.d.ts +16 -0
- package/dist/core/theme.svelte.js +34 -0
- package/dist/core/trigger.d.ts +35 -0
- package/dist/core/trigger.js +77 -0
- package/dist/index.d.ts +11 -103
- package/dist/index.js +11 -75
- package/dist/{components → styles/matte}/AccordionItem.svelte +1 -1
- package/dist/{components → styles/matte}/AccordionItem.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Alert.svelte +8 -2
- package/dist/{components → styles/matte}/Alert.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Avatar.svelte +1 -1
- package/dist/{components → styles/matte}/Avatar.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Badge.svelte +8 -3
- package/dist/{components → styles/matte}/Badge.svelte.d.ts +1 -3
- package/dist/{components → styles/matte}/Breadcrumb.svelte +2 -2
- package/dist/{components → styles/matte}/Breadcrumb.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Button.svelte +1 -1
- package/dist/{components → styles/matte}/Button.svelte.d.ts +1 -1
- package/dist/styles/matte/CTASection.svelte +76 -0
- package/dist/styles/matte/CTASection.svelte.d.ts +18 -0
- package/dist/{components → styles/matte}/Calendar.svelte +7 -17
- package/dist/{components → styles/matte}/Calendar.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Card.svelte +1 -1
- package/dist/{components → styles/matte}/Card.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Checkbox.svelte +1 -1
- package/dist/{components → styles/matte}/Checkbox.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/CheckboxGroup.svelte +1 -1
- package/dist/{components → styles/matte}/CheckboxGroup.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Combobox.svelte +31 -42
- package/dist/{components → styles/matte}/Combobox.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/CommandPalette.svelte +26 -41
- package/dist/{components → styles/matte}/CommandPalette.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/ConfirmDialog.svelte +2 -2
- package/dist/{components → styles/matte}/ConfirmDialog.svelte.d.ts +2 -2
- package/dist/{components → styles/matte}/ContextMenu.svelte +5 -7
- package/dist/{components → styles/matte}/DatePicker.svelte +7 -11
- package/dist/{components → styles/matte}/DatePicker.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Drawer.svelte +4 -7
- package/dist/{components → styles/matte}/Dropdown.svelte +18 -36
- package/dist/{components → styles/matte}/Dropdown.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Dropzone.svelte +11 -43
- package/dist/{components → styles/matte}/Dropzone.svelte.d.ts +2 -5
- package/dist/{components → styles/matte}/EmptyState.svelte +1 -1
- package/dist/{components → styles/matte}/EmptyState.svelte.d.ts +1 -1
- package/dist/styles/matte/FeatureCard.svelte +58 -0
- package/dist/styles/matte/FeatureCard.svelte.d.ts +16 -0
- package/dist/styles/matte/FeatureGrid.svelte +62 -0
- package/dist/styles/matte/FeatureGrid.svelte.d.ts +16 -0
- package/dist/{components → styles/matte}/Footer.svelte +1 -1
- package/dist/{components → styles/matte}/Footer.svelte.d.ts +1 -1
- package/dist/styles/matte/HeroSection.svelte +80 -0
- package/dist/styles/matte/HeroSection.svelte.d.ts +21 -0
- package/dist/{components → styles/matte}/Input.svelte +1 -1
- package/dist/{components → styles/matte}/Input.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Link.svelte +1 -1
- package/dist/{components → styles/matte}/Link.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/LocaleProvider.svelte +1 -1
- package/dist/{components → styles/matte}/LocaleProvider.svelte.d.ts +1 -1
- package/dist/styles/matte/LogoCloud.svelte +61 -0
- package/dist/styles/matte/LogoCloud.svelte.d.ts +19 -0
- package/dist/{components → styles/matte}/MenuItem.svelte +1 -1
- package/dist/{components → styles/matte}/MenuItem.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Modal.svelte +4 -7
- package/dist/{components → styles/matte}/MultiSelect.svelte +18 -37
- package/dist/{components → styles/matte}/MultiSelect.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Navbar.svelte +2 -2
- package/dist/{components → styles/matte}/Navbar.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/NumberInput.svelte +4 -8
- package/dist/{components → styles/matte}/NumberInput.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Pagination.svelte +4 -15
- package/dist/{components → styles/matte}/Pagination.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/PasswordInput.svelte +4 -13
- package/dist/{components → styles/matte}/PasswordInput.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Popover.svelte +16 -41
- package/dist/{components → styles/matte}/Popover.svelte.d.ts +1 -1
- package/dist/styles/matte/PricingCard.svelte +107 -0
- package/dist/styles/matte/PricingCard.svelte.d.ts +25 -0
- package/dist/{components → styles/matte}/Progress.svelte +3 -2
- package/dist/{components → styles/matte}/Progress.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Radio.svelte +1 -1
- package/dist/{components → styles/matte}/Radio.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/RadioGroup.svelte +1 -1
- package/dist/{components → styles/matte}/RadioGroup.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/SegmentedControl.svelte +1 -1
- package/dist/{components → styles/matte}/SegmentedControl.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Select.svelte +1 -1
- package/dist/{components → styles/matte}/Select.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Sidebar.svelte +2 -2
- package/dist/{components → styles/matte}/Sidebar.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Slider.svelte +3 -2
- package/dist/{components → styles/matte}/Slider.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Spinner.svelte +2 -2
- package/dist/{components → styles/matte}/Spinner.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Stat.svelte +1 -1
- package/dist/{components → styles/matte}/Stat.svelte.d.ts +1 -1
- package/dist/styles/matte/StatsBand.svelte +57 -0
- package/dist/styles/matte/StatsBand.svelte.d.ts +20 -0
- package/dist/{components → styles/matte}/Steps.svelte +1 -1
- package/dist/{components → styles/matte}/Steps.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Switch.svelte +1 -1
- package/dist/{components → styles/matte}/Switch.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Table.svelte +6 -28
- package/dist/{components → styles/matte}/Table.svelte.d.ts +2 -6
- package/dist/{components → styles/matte}/Tabs.svelte +8 -1
- package/dist/{components → styles/matte}/Tabs.svelte.d.ts +1 -1
- package/dist/styles/matte/Testimonial.svelte +67 -0
- package/dist/styles/matte/Testimonial.svelte.d.ts +19 -0
- package/dist/{components → styles/matte}/Textarea.svelte +1 -1
- package/dist/{components → styles/matte}/Textarea.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/ThemeToggle.svelte +14 -27
- package/dist/{components → styles/matte}/ThemeToggle.svelte.d.ts +2 -6
- package/dist/{components → styles/matte}/Toaster.svelte +4 -4
- package/dist/styles/matte/index.d.ts +109 -0
- package/dist/styles/matte/index.js +83 -0
- package/dist/styles/paper/Accordion.svelte +17 -0
- package/dist/styles/paper/Accordion.svelte.d.ts +8 -0
- package/dist/styles/paper/AccordionItem.svelte +46 -0
- package/dist/styles/paper/AccordionItem.svelte.d.ts +14 -0
- package/dist/styles/paper/Alert.svelte +99 -0
- package/dist/styles/paper/Alert.svelte.d.ts +18 -0
- package/dist/styles/paper/Avatar.svelte +60 -0
- package/dist/styles/paper/Avatar.svelte.d.ts +16 -0
- package/dist/styles/paper/AvatarGroup.svelte +17 -0
- package/dist/styles/paper/AvatarGroup.svelte.d.ts +8 -0
- package/dist/styles/paper/Badge.svelte +58 -0
- package/dist/styles/paper/Badge.svelte.d.ts +16 -0
- package/dist/styles/paper/Breadcrumb.svelte +49 -0
- package/dist/styles/paper/Breadcrumb.svelte.d.ts +14 -0
- package/dist/styles/paper/Button.svelte +96 -0
- package/dist/styles/paper/Button.svelte.d.ts +21 -0
- package/dist/styles/paper/CTASection.svelte +76 -0
- package/dist/styles/paper/CTASection.svelte.d.ts +18 -0
- package/dist/styles/paper/Calendar.svelte +202 -0
- package/dist/styles/paper/Calendar.svelte.d.ts +22 -0
- package/dist/styles/paper/Card.svelte +78 -0
- package/dist/styles/paper/Card.svelte.d.ts +21 -0
- package/dist/styles/paper/Checkbox.svelte +75 -0
- package/dist/styles/paper/Checkbox.svelte.d.ts +15 -0
- package/dist/styles/paper/CheckboxGroup.svelte +111 -0
- package/dist/styles/paper/CheckboxGroup.svelte.d.ts +30 -0
- package/dist/styles/paper/Combobox.svelte +266 -0
- package/dist/styles/paper/Combobox.svelte.d.ts +29 -0
- package/dist/styles/paper/CommandPalette.svelte +209 -0
- package/dist/styles/paper/CommandPalette.svelte.d.ts +32 -0
- package/dist/styles/paper/ConfirmDialog.svelte +97 -0
- package/dist/styles/paper/ConfirmDialog.svelte.d.ts +22 -0
- package/dist/styles/paper/ContextMenu.svelte +88 -0
- package/dist/styles/paper/ContextMenu.svelte.d.ts +15 -0
- package/dist/styles/paper/DatePicker.svelte +192 -0
- package/dist/styles/paper/DatePicker.svelte.d.ts +23 -0
- package/dist/styles/paper/Divider.svelte +30 -0
- package/dist/styles/paper/Divider.svelte.d.ts +10 -0
- package/dist/styles/paper/Drawer.svelte +165 -0
- package/dist/styles/paper/Drawer.svelte.d.ts +20 -0
- package/dist/styles/paper/Dropdown.svelte +94 -0
- package/dist/styles/paper/Dropdown.svelte.d.ts +23 -0
- package/dist/styles/paper/Dropzone.svelte +210 -0
- package/dist/styles/paper/Dropzone.svelte.d.ts +29 -0
- package/dist/styles/paper/EmptyState.svelte +71 -0
- package/dist/styles/paper/EmptyState.svelte.d.ts +18 -0
- package/dist/styles/paper/FeatureCard.svelte +58 -0
- package/dist/styles/paper/FeatureCard.svelte.d.ts +16 -0
- package/dist/styles/paper/FeatureGrid.svelte +62 -0
- package/dist/styles/paper/FeatureGrid.svelte.d.ts +16 -0
- package/dist/styles/paper/Field.svelte +42 -0
- package/dist/styles/paper/Field.svelte.d.ts +16 -0
- package/dist/styles/paper/Footer.svelte +91 -0
- package/dist/styles/paper/Footer.svelte.d.ts +25 -0
- package/dist/styles/paper/HeroSection.svelte +86 -0
- package/dist/styles/paper/HeroSection.svelte.d.ts +21 -0
- package/dist/styles/paper/Input.svelte +60 -0
- package/dist/styles/paper/Input.svelte.d.ts +18 -0
- package/dist/styles/paper/InputGroup.svelte +20 -0
- package/dist/styles/paper/InputGroup.svelte.d.ts +11 -0
- package/dist/styles/paper/Kbd.svelte +17 -0
- package/dist/styles/paper/Kbd.svelte.d.ts +8 -0
- package/dist/styles/paper/Label.svelte +16 -0
- package/dist/styles/paper/Label.svelte.d.ts +9 -0
- package/dist/styles/paper/Link.svelte +44 -0
- package/dist/styles/paper/Link.svelte.d.ts +16 -0
- package/dist/styles/paper/LocaleProvider.svelte +24 -0
- package/dist/styles/paper/LocaleProvider.svelte.d.ts +10 -0
- package/dist/styles/paper/LogoCloud.svelte +61 -0
- package/dist/styles/paper/LogoCloud.svelte.d.ts +19 -0
- package/dist/styles/paper/MenuItem.svelte +64 -0
- package/dist/styles/paper/MenuItem.svelte.d.ts +18 -0
- package/dist/styles/paper/MenuSeparator.svelte +22 -0
- package/dist/styles/paper/MenuSeparator.svelte.d.ts +8 -0
- package/dist/styles/paper/Modal.svelte +118 -0
- package/dist/styles/paper/Modal.svelte.d.ts +21 -0
- package/dist/styles/paper/MultiSelect.svelte +287 -0
- package/dist/styles/paper/MultiSelect.svelte.d.ts +31 -0
- package/dist/styles/paper/Navbar.svelte +201 -0
- package/dist/styles/paper/Navbar.svelte.d.ts +33 -0
- package/dist/styles/paper/NumberInput.svelte +96 -0
- package/dist/styles/paper/NumberInput.svelte.d.ts +15 -0
- package/dist/styles/paper/Pagination.svelte +83 -0
- package/dist/styles/paper/Pagination.svelte.d.ts +15 -0
- package/dist/styles/paper/PasswordInput.svelte +100 -0
- package/dist/styles/paper/PasswordInput.svelte.d.ts +12 -0
- package/dist/styles/paper/Popover.svelte +89 -0
- package/dist/styles/paper/Popover.svelte.d.ts +19 -0
- package/dist/styles/paper/PricingCard.svelte +108 -0
- package/dist/styles/paper/PricingCard.svelte.d.ts +25 -0
- package/dist/styles/paper/Progress.svelte +59 -0
- package/dist/styles/paper/Progress.svelte.d.ts +17 -0
- package/dist/styles/paper/Radio.svelte +55 -0
- package/dist/styles/paper/Radio.svelte.d.ts +15 -0
- package/dist/styles/paper/RadioGroup.svelte +95 -0
- package/dist/styles/paper/RadioGroup.svelte.d.ts +27 -0
- package/dist/styles/paper/SegmentedControl.svelte +88 -0
- package/dist/styles/paper/SegmentedControl.svelte.d.ts +25 -0
- package/dist/styles/paper/Select.svelte +61 -0
- package/dist/styles/paper/Select.svelte.d.ts +20 -0
- package/dist/styles/paper/Sidebar.svelte +206 -0
- package/dist/styles/paper/Sidebar.svelte.d.ts +36 -0
- package/dist/styles/paper/Skeleton.svelte +46 -0
- package/dist/styles/paper/Skeleton.svelte.d.ts +13 -0
- package/dist/styles/paper/Slider.svelte +80 -0
- package/dist/styles/paper/Slider.svelte.d.ts +19 -0
- package/dist/styles/paper/Spinner.svelte +34 -0
- package/dist/styles/paper/Spinner.svelte.d.ts +13 -0
- package/dist/styles/paper/Stat.svelte +51 -0
- package/dist/styles/paper/Stat.svelte.d.ts +16 -0
- package/dist/styles/paper/StatsBand.svelte +63 -0
- package/dist/styles/paper/StatsBand.svelte.d.ts +20 -0
- package/dist/styles/paper/Steps.svelte +141 -0
- package/dist/styles/paper/Steps.svelte.d.ts +24 -0
- package/dist/styles/paper/Switch.svelte +64 -0
- package/dist/styles/paper/Switch.svelte.d.ts +14 -0
- package/dist/styles/paper/Table.svelte +217 -0
- package/dist/styles/paper/Table.svelte.d.ts +66 -0
- package/dist/styles/paper/Tabs.svelte +102 -0
- package/dist/styles/paper/Tabs.svelte.d.ts +23 -0
- package/dist/styles/paper/Testimonial.svelte +64 -0
- package/dist/styles/paper/Testimonial.svelte.d.ts +19 -0
- package/dist/styles/paper/Textarea.svelte +54 -0
- package/dist/styles/paper/Textarea.svelte.d.ts +12 -0
- package/dist/styles/paper/ThemeToggle.svelte +130 -0
- package/dist/styles/paper/ThemeToggle.svelte.d.ts +16 -0
- package/dist/styles/paper/Toaster.svelte +132 -0
- package/dist/styles/paper/Toaster.svelte.d.ts +8 -0
- package/dist/styles/paper/Tooltip.svelte +33 -0
- package/dist/styles/paper/Tooltip.svelte.d.ts +12 -0
- package/dist/styles/paper/fonts.css +46 -0
- package/dist/styles/paper/index.d.ts +109 -0
- package/dist/styles/paper/index.js +83 -0
- package/dist/styles/paper/theme.css +222 -0
- package/package.json +531 -201
- package/registry.json +658 -339
- /package/dist/{actions → core/actions}/anchor.d.ts +0 -0
- /package/dist/{actions → core/actions}/anchor.js +0 -0
- /package/dist/{actions → core/actions}/dismissable.d.ts +0 -0
- /package/dist/{actions → core/actions}/dismissable.js +0 -0
- /package/dist/{date.d.ts → core/date.d.ts} +0 -0
- /package/dist/{date.js → core/date.js} +0 -0
- /package/dist/{locale.svelte.d.ts → core/locale.svelte.d.ts} +0 -0
- /package/dist/{locale.svelte.js → core/locale.svelte.js} +0 -0
- /package/dist/{toast.svelte.d.ts → core/toast.svelte.d.ts} +0 -0
- /package/dist/{toast.svelte.js → core/toast.svelte.js} +0 -0
- /package/dist/{tones.d.ts → core/tones.d.ts} +0 -0
- /package/dist/{tones.js → core/tones.js} +0 -0
- /package/dist/{components → styles/matte}/Accordion.svelte +0 -0
- /package/dist/{components → styles/matte}/Accordion.svelte.d.ts +0 -0
- /package/dist/{components → styles/matte}/AvatarGroup.svelte +0 -0
- /package/dist/{components → styles/matte}/AvatarGroup.svelte.d.ts +0 -0
- /package/dist/{components → styles/matte}/ContextMenu.svelte.d.ts +0 -0
- /package/dist/{components → styles/matte}/Divider.svelte +0 -0
- /package/dist/{components → styles/matte}/Divider.svelte.d.ts +0 -0
- /package/dist/{components → styles/matte}/Drawer.svelte.d.ts +0 -0
- /package/dist/{components → styles/matte}/Field.svelte +0 -0
- /package/dist/{components → styles/matte}/Field.svelte.d.ts +0 -0
- /package/dist/{components → styles/matte}/InputGroup.svelte +0 -0
- /package/dist/{components → styles/matte}/InputGroup.svelte.d.ts +0 -0
- /package/dist/{components → styles/matte}/Kbd.svelte +0 -0
- /package/dist/{components → styles/matte}/Kbd.svelte.d.ts +0 -0
- /package/dist/{components → styles/matte}/Label.svelte +0 -0
- /package/dist/{components → styles/matte}/Label.svelte.d.ts +0 -0
- /package/dist/{components → styles/matte}/MenuSeparator.svelte +0 -0
- /package/dist/{components → styles/matte}/MenuSeparator.svelte.d.ts +0 -0
- /package/dist/{components → styles/matte}/Modal.svelte.d.ts +0 -0
- /package/dist/{components → styles/matte}/Skeleton.svelte +0 -0
- /package/dist/{components → styles/matte}/Skeleton.svelte.d.ts +0 -0
- /package/dist/{components → styles/matte}/Toaster.svelte.d.ts +0 -0
- /package/dist/{components → styles/matte}/Tooltip.svelte +0 -0
- /package/dist/{components → styles/matte}/Tooltip.svelte.d.ts +0 -0
- /package/dist/{fonts.css → styles/matte/fonts.css} +0 -0
- /package/dist/{theme.css → styles/matte/theme.css} +0 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
export interface SidebarItem {
|
|
3
|
+
/** Stable id used for the active state. Falls back to `href`. */
|
|
4
|
+
id?: string;
|
|
5
|
+
label: string;
|
|
6
|
+
href?: string;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
badge?: string | number;
|
|
9
|
+
/** Nested links — the parent becomes a collapsible group. */
|
|
10
|
+
items?: SidebarItem[];
|
|
11
|
+
/** Open the group on first render. */
|
|
12
|
+
open?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface SidebarSection {
|
|
16
|
+
/** Small heading above a run of items. */
|
|
17
|
+
label?: string;
|
|
18
|
+
items: SidebarItem[];
|
|
19
|
+
}
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<script lang="ts">
|
|
23
|
+
import type { Snippet } from 'svelte';
|
|
24
|
+
import { useLocale } from '../../core/locale.svelte.js';
|
|
25
|
+
import { focusRing, toneRing, toneSoft, type Tone } from '../../core/tones.js';
|
|
26
|
+
|
|
27
|
+
interface Props {
|
|
28
|
+
sections: SidebarSection[];
|
|
29
|
+
/** Bindable — the `id` (or `href`) of the current item. */
|
|
30
|
+
value?: string;
|
|
31
|
+
/** Bindable — narrow rail mode. */
|
|
32
|
+
collapsed?: boolean;
|
|
33
|
+
/** Show the collapse button. */
|
|
34
|
+
collapsible?: boolean;
|
|
35
|
+
tone?: Tone;
|
|
36
|
+
header?: Snippet;
|
|
37
|
+
footer?: Snippet;
|
|
38
|
+
onnavigate?: (item: SidebarItem) => void;
|
|
39
|
+
class?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let {
|
|
43
|
+
sections,
|
|
44
|
+
value = $bindable(''),
|
|
45
|
+
collapsed = $bindable(false),
|
|
46
|
+
collapsible = true,
|
|
47
|
+
tone = 'brand',
|
|
48
|
+
header,
|
|
49
|
+
footer,
|
|
50
|
+
onnavigate,
|
|
51
|
+
class: className = ''
|
|
52
|
+
}: Props = $props();
|
|
53
|
+
|
|
54
|
+
const t = useLocale();
|
|
55
|
+
|
|
56
|
+
const keyOf = (item: SidebarItem) => item.id ?? item.href ?? item.label;
|
|
57
|
+
|
|
58
|
+
/** Groups start open when asked, or when they contain the active item. */
|
|
59
|
+
let opened = $state<Record<string, boolean>>({});
|
|
60
|
+
|
|
61
|
+
const isOpen = (item: SidebarItem) =>
|
|
62
|
+
opened[keyOf(item)] ?? (item.open || item.items!.some((child) => keyOf(child) === value));
|
|
63
|
+
|
|
64
|
+
function toggle(item: SidebarItem) {
|
|
65
|
+
opened = { ...opened, [keyOf(item)]: !isOpen(item) };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function go(item: SidebarItem) {
|
|
69
|
+
if (item.disabled) return;
|
|
70
|
+
value = keyOf(item);
|
|
71
|
+
onnavigate?.(item);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const row =
|
|
75
|
+
'flex w-full items-center gap-3 rounded-md px-2.5 py-2 text-left font-sans text-sm transition-colors duration-150 ease-brand-out disabled:pointer-events-none disabled:opacity-40';
|
|
76
|
+
</script>
|
|
77
|
+
|
|
78
|
+
<aside
|
|
79
|
+
class="flex h-full shrink-0 flex-col border-r border-hairline bg-bg-alt transition-[width] duration-200 ease-brand-out
|
|
80
|
+
{collapsed ? 'w-16' : 'w-60'} {className}"
|
|
81
|
+
>
|
|
82
|
+
{#if header}
|
|
83
|
+
<div class="flex h-16 shrink-0 items-center gap-3 border-b border-hairline px-3">
|
|
84
|
+
{@render header()}
|
|
85
|
+
</div>
|
|
86
|
+
{/if}
|
|
87
|
+
|
|
88
|
+
<nav aria-label={t.current.navigation} class="flex-1 overflow-y-auto p-2">
|
|
89
|
+
{#each sections as section, i (section.label ?? i)}
|
|
90
|
+
<div class="flex flex-col gap-0.5 {i > 0 ? 'mt-4' : ''}">
|
|
91
|
+
{#if section.label && !collapsed}
|
|
92
|
+
<p class="px-2.5 pb-1 font-sans text-[11px] font-semibold text-text-muted">
|
|
93
|
+
{section.label}
|
|
94
|
+
</p>
|
|
95
|
+
{:else if section.label && collapsed && i > 0}
|
|
96
|
+
<div class="mx-2 mb-1 h-px bg-hairline" role="separator"></div>
|
|
97
|
+
{/if}
|
|
98
|
+
|
|
99
|
+
{#each section.items as item (keyOf(item))}
|
|
100
|
+
{#if item.items?.length}
|
|
101
|
+
<button
|
|
102
|
+
type="button"
|
|
103
|
+
onclick={() => toggle(item)}
|
|
104
|
+
aria-expanded={isOpen(item)}
|
|
105
|
+
title={collapsed ? item.label : undefined}
|
|
106
|
+
class="{row} {focusRing} {toneRing[
|
|
107
|
+
tone
|
|
108
|
+
]} text-text-secondary hover:bg-bg-inset hover:text-text"
|
|
109
|
+
>
|
|
110
|
+
<span class="min-w-0 flex-1 truncate {collapsed ? 'sr-only' : ''}">{item.label}</span>
|
|
111
|
+
{#if !collapsed}
|
|
112
|
+
<svg
|
|
113
|
+
class="size-3.5 shrink-0 transition-transform duration-200 {isOpen(item)
|
|
114
|
+
? 'rotate-90'
|
|
115
|
+
: ''}"
|
|
116
|
+
viewBox="0 0 16 16"
|
|
117
|
+
fill="none"
|
|
118
|
+
aria-hidden="true"
|
|
119
|
+
>
|
|
120
|
+
<path
|
|
121
|
+
d="m6 4 4 4-4 4"
|
|
122
|
+
stroke="currentColor"
|
|
123
|
+
stroke-width="1.6"
|
|
124
|
+
stroke-linecap="round"
|
|
125
|
+
/>
|
|
126
|
+
</svg>
|
|
127
|
+
{/if}
|
|
128
|
+
</button>
|
|
129
|
+
|
|
130
|
+
{#if isOpen(item) && !collapsed}
|
|
131
|
+
<div class="ml-4 flex flex-col gap-0.5 border-l border-hairline pl-2">
|
|
132
|
+
{#each item.items as child (keyOf(child))}
|
|
133
|
+
<a
|
|
134
|
+
href={child.href}
|
|
135
|
+
onclick={() => go(child)}
|
|
136
|
+
aria-current={value === keyOf(child) ? 'page' : undefined}
|
|
137
|
+
class="{row} {focusRing} {toneRing[tone]} {value === keyOf(child)
|
|
138
|
+
? `${toneSoft[tone]} font-medium`
|
|
139
|
+
: 'text-text-muted hover:bg-bg-inset hover:text-text'} {child.disabled
|
|
140
|
+
? 'pointer-events-none opacity-40'
|
|
141
|
+
: ''}"
|
|
142
|
+
>
|
|
143
|
+
<span class="min-w-0 flex-1 truncate">{child.label}</span>
|
|
144
|
+
{#if child.badge !== undefined}
|
|
145
|
+
<span class="shrink-0 text-[11px] tabular-nums">{child.badge}</span>
|
|
146
|
+
{/if}
|
|
147
|
+
</a>
|
|
148
|
+
{/each}
|
|
149
|
+
</div>
|
|
150
|
+
{/if}
|
|
151
|
+
{:else}
|
|
152
|
+
<a
|
|
153
|
+
href={item.href}
|
|
154
|
+
onclick={() => go(item)}
|
|
155
|
+
aria-current={value === keyOf(item) ? 'page' : undefined}
|
|
156
|
+
title={collapsed ? item.label : undefined}
|
|
157
|
+
class="{row} {focusRing} {toneRing[tone]} {value === keyOf(item)
|
|
158
|
+
? `${toneSoft[tone]} font-medium`
|
|
159
|
+
: 'text-text-secondary hover:bg-bg-inset hover:text-text'} {item.disabled
|
|
160
|
+
? 'pointer-events-none opacity-40'
|
|
161
|
+
: ''}"
|
|
162
|
+
>
|
|
163
|
+
<span class="min-w-0 flex-1 truncate {collapsed ? 'sr-only' : ''}">{item.label}</span>
|
|
164
|
+
{#if item.badge !== undefined && !collapsed}
|
|
165
|
+
<span class="shrink-0 text-[11px] text-text-muted tabular-nums">{item.badge}</span>
|
|
166
|
+
{/if}
|
|
167
|
+
</a>
|
|
168
|
+
{/if}
|
|
169
|
+
{/each}
|
|
170
|
+
</div>
|
|
171
|
+
{/each}
|
|
172
|
+
</nav>
|
|
173
|
+
|
|
174
|
+
{#if footer || collapsible}
|
|
175
|
+
<div class="flex shrink-0 flex-col gap-2 border-t border-hairline p-2">
|
|
176
|
+
{#if footer}
|
|
177
|
+
{@render footer()}
|
|
178
|
+
{/if}
|
|
179
|
+
{#if collapsible}
|
|
180
|
+
<button
|
|
181
|
+
type="button"
|
|
182
|
+
onclick={() => (collapsed = !collapsed)}
|
|
183
|
+
aria-label={collapsed ? t.current.expandSidebar : t.current.collapseSidebar}
|
|
184
|
+
class="inline-flex h-8 items-center justify-center gap-2 rounded-md font-sans text-xs text-text-muted transition-colors duration-150 hover:bg-bg-inset hover:text-text {focusRing} {toneRing[
|
|
185
|
+
tone
|
|
186
|
+
]}"
|
|
187
|
+
>
|
|
188
|
+
<svg
|
|
189
|
+
class="size-4 transition-transform duration-200 {collapsed ? 'rotate-180' : ''}"
|
|
190
|
+
viewBox="0 0 16 16"
|
|
191
|
+
fill="none"
|
|
192
|
+
aria-hidden="true"
|
|
193
|
+
>
|
|
194
|
+
<path
|
|
195
|
+
d="m10 4-4 4 4 4"
|
|
196
|
+
stroke="currentColor"
|
|
197
|
+
stroke-width="1.6"
|
|
198
|
+
stroke-linecap="round"
|
|
199
|
+
/>
|
|
200
|
+
</svg>
|
|
201
|
+
{#if !collapsed}<span>{t.current.collapseSidebar}</span>{/if}
|
|
202
|
+
</button>
|
|
203
|
+
{/if}
|
|
204
|
+
</div>
|
|
205
|
+
{/if}
|
|
206
|
+
</aside>
|
|
@@ -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 '../../core/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 rounded-md',
|
|
26
|
+
block: 'h-24 w-full rounded-lg',
|
|
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 rounded-md 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,80 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
3
|
+
import { toneText, type Tone } from '../../core/tones.js';
|
|
4
|
+
import { percentOf } from '../../core/number.js';
|
|
5
|
+
|
|
6
|
+
interface Props extends Omit<HTMLInputAttributes, 'size' | 'type' | 'value' | 'min' | 'max'> {
|
|
7
|
+
value?: number;
|
|
8
|
+
min?: number;
|
|
9
|
+
max?: number;
|
|
10
|
+
step?: number;
|
|
11
|
+
tone?: Tone;
|
|
12
|
+
label?: string;
|
|
13
|
+
/** Prints the current value on the right of the label row. */
|
|
14
|
+
showValue?: boolean;
|
|
15
|
+
/** Appended to the printed value, e.g. `'%'`. */
|
|
16
|
+
unit?: string;
|
|
17
|
+
/** Tick labels under the track — usually just `[min, max]`. */
|
|
18
|
+
marks?: (number | string)[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let {
|
|
22
|
+
value = $bindable(50),
|
|
23
|
+
min = 0,
|
|
24
|
+
max = 100,
|
|
25
|
+
step = 1,
|
|
26
|
+
tone = 'brand',
|
|
27
|
+
label,
|
|
28
|
+
showValue = false,
|
|
29
|
+
unit = '',
|
|
30
|
+
marks,
|
|
31
|
+
disabled = false,
|
|
32
|
+
class: className = '',
|
|
33
|
+
...rest
|
|
34
|
+
}: Props = $props();
|
|
35
|
+
|
|
36
|
+
const pct = $derived(percentOf(value, min, max));
|
|
37
|
+
|
|
38
|
+
// The filled part of the track and the thumb both paint with `currentColor`,
|
|
39
|
+
// so a single tone class on the input drives every vendor pseudo-element.
|
|
40
|
+
// The gradient classes below are spelled out twice on purpose: Tailwind only
|
|
41
|
+
// sees literal class strings, so a shared JS constant would produce nothing.
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<div class="flex w-full flex-col gap-2 {disabled ? 'opacity-50' : ''} {className}">
|
|
45
|
+
{#if label || showValue}
|
|
46
|
+
<div class="flex items-baseline justify-between gap-4 font-sans text-sm">
|
|
47
|
+
{#if label}<span class="text-text-secondary">{label}</span>{/if}
|
|
48
|
+
{#if showValue}
|
|
49
|
+
<span class="text-sm font-medium tabular-nums {toneText[tone]}">{value}{unit}</span>
|
|
50
|
+
{/if}
|
|
51
|
+
</div>
|
|
52
|
+
{/if}
|
|
53
|
+
|
|
54
|
+
<input
|
|
55
|
+
type="range"
|
|
56
|
+
bind:value
|
|
57
|
+
{min}
|
|
58
|
+
{max}
|
|
59
|
+
{step}
|
|
60
|
+
{disabled}
|
|
61
|
+
aria-label={label}
|
|
62
|
+
style="--pct:{pct}%"
|
|
63
|
+
class="h-5 w-full cursor-pointer appearance-none bg-transparent focus:outline-none disabled:cursor-not-allowed
|
|
64
|
+
{toneText[tone]}
|
|
65
|
+
focus-visible:outline-2 focus-visible:outline-offset-4
|
|
66
|
+
[&::-moz-range-thumb]:size-4 [&::-moz-range-thumb]:rounded-full
|
|
67
|
+
[&::-moz-range-thumb]:border-2 [&::-moz-range-thumb]:border-bg [&::-moz-range-thumb]:bg-current [&::-moz-range-thumb]:shadow-sm [&::-moz-range-track]:h-1.5 [&::-moz-range-track]:rounded-full [&::-moz-range-track]:bg-[linear-gradient(to_right,currentColor_var(--pct),var(--color-bg-inset)_var(--pct))] [&::-webkit-slider-runnable-track]:h-1.5 [&::-webkit-slider-runnable-track]:rounded-full [&::-webkit-slider-runnable-track]:bg-[linear-gradient(to_right,currentColor_var(--pct),var(--color-bg-inset)_var(--pct))]
|
|
68
|
+
[&::-webkit-slider-thumb]:-mt-[5px] [&::-webkit-slider-thumb]:size-4 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:border-2
|
|
69
|
+
[&::-webkit-slider-thumb]:border-bg [&::-webkit-slider-thumb]:bg-current [&::-webkit-slider-thumb]:shadow-sm"
|
|
70
|
+
{...rest}
|
|
71
|
+
/>
|
|
72
|
+
|
|
73
|
+
{#if marks?.length}
|
|
74
|
+
<div class="flex justify-between text-[11px] text-text-muted tabular-nums">
|
|
75
|
+
{#each marks as mark (mark)}
|
|
76
|
+
<span>{mark}</span>
|
|
77
|
+
{/each}
|
|
78
|
+
</div>
|
|
79
|
+
{/if}
|
|
80
|
+
</div>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
2
|
+
import { type Tone } from '../../core/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 '../../core/tones.js';
|
|
4
|
+
import { useLocale } from '../../core/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-[1.5px]',
|
|
22
|
+
sm: 'size-4 border-2',
|
|
23
|
+
md: 'size-5 border-2',
|
|
24
|
+
lg: 'size-8 border-[3px]'
|
|
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/25 border-t-current
|
|
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 '../../core/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,51 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import { toneText, type Tone } from '../../core/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="text-sm font-medium text-text-muted">{label}</span>
|
|
35
|
+
<div class="flex items-baseline gap-2">
|
|
36
|
+
<span
|
|
37
|
+
class="font-heading text-3xl font-semibold tracking-tight tabular-nums {tone
|
|
38
|
+
? toneText[tone]
|
|
39
|
+
: 'text-text'}"
|
|
40
|
+
>
|
|
41
|
+
{value}
|
|
42
|
+
</span>
|
|
43
|
+
{#if delta}
|
|
44
|
+
<span class="text-sm font-medium {toneText[trendTone[trend]]}">
|
|
45
|
+
{trendGlyph[trend]}
|
|
46
|
+
{delta}
|
|
47
|
+
</span>
|
|
48
|
+
{/if}
|
|
49
|
+
</div>
|
|
50
|
+
{#if hint}<span class="text-sm text-text-muted">{hint}</span>{/if}
|
|
51
|
+
</div>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
import { type Tone } from '../../core/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,63 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import Stat from './Stat.svelte';
|
|
4
|
+
import type { StatTrend } from './Stat.svelte';
|
|
5
|
+
import type { Tone } from '../../core/tones.js';
|
|
6
|
+
|
|
7
|
+
export interface BandStat {
|
|
8
|
+
label: string;
|
|
9
|
+
value: string | number;
|
|
10
|
+
delta?: string;
|
|
11
|
+
trend?: StatTrend;
|
|
12
|
+
hint?: string;
|
|
13
|
+
tone?: Tone;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface Props extends HTMLAttributes<HTMLElement> {
|
|
17
|
+
stats: BandStat[];
|
|
18
|
+
title?: string;
|
|
19
|
+
/** Figures in one bordered card rather than free-standing. */
|
|
20
|
+
bordered?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let { stats, title, bordered = true, class: className = '', ...rest }: Props = $props();
|
|
24
|
+
|
|
25
|
+
const cols = $derived(
|
|
26
|
+
stats.length === 2
|
|
27
|
+
? 'sm:grid-cols-2'
|
|
28
|
+
: stats.length === 4
|
|
29
|
+
? 'sm:grid-cols-2 lg:grid-cols-4'
|
|
30
|
+
: 'sm:grid-cols-3'
|
|
31
|
+
);
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<section class="w-full px-6 py-16 {className}" {...rest}>
|
|
35
|
+
<div class="mx-auto flex w-full max-w-6xl flex-col gap-8">
|
|
36
|
+
{#if title}
|
|
37
|
+
<h2
|
|
38
|
+
class="max-w-xl font-heading text-3xl font-semibold tracking-tight text-balance text-text"
|
|
39
|
+
>
|
|
40
|
+
{title}
|
|
41
|
+
</h2>
|
|
42
|
+
{/if}
|
|
43
|
+
|
|
44
|
+
<div
|
|
45
|
+
class="grid {cols} {bordered
|
|
46
|
+
? 'gap-px overflow-hidden rounded-lg border border-hairline bg-hairline shadow-xs'
|
|
47
|
+
: 'gap-8'}"
|
|
48
|
+
>
|
|
49
|
+
{#each stats as stat (stat.label)}
|
|
50
|
+
<div class={bordered ? 'bg-bg p-6' : ''}>
|
|
51
|
+
<Stat
|
|
52
|
+
label={stat.label}
|
|
53
|
+
value={stat.value}
|
|
54
|
+
delta={stat.delta}
|
|
55
|
+
trend={stat.trend}
|
|
56
|
+
hint={stat.hint}
|
|
57
|
+
tone={stat.tone}
|
|
58
|
+
/>
|
|
59
|
+
</div>
|
|
60
|
+
{/each}
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
</section>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
import type { StatTrend } from './Stat.svelte';
|
|
3
|
+
import type { Tone } from '../../core/tones.js';
|
|
4
|
+
export interface BandStat {
|
|
5
|
+
label: string;
|
|
6
|
+
value: string | number;
|
|
7
|
+
delta?: string;
|
|
8
|
+
trend?: StatTrend;
|
|
9
|
+
hint?: string;
|
|
10
|
+
tone?: Tone;
|
|
11
|
+
}
|
|
12
|
+
interface Props extends HTMLAttributes<HTMLElement> {
|
|
13
|
+
stats: BandStat[];
|
|
14
|
+
title?: string;
|
|
15
|
+
/** Figures in one bordered card rather than free-standing. */
|
|
16
|
+
bordered?: boolean;
|
|
17
|
+
}
|
|
18
|
+
declare const StatsBand: import("svelte").Component<Props, {}, "">;
|
|
19
|
+
type StatsBand = ReturnType<typeof StatsBand>;
|
|
20
|
+
export default StatsBand;
|