@nqmcreative/ui 0.2.1 → 0.2.2
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 +7 -5
- package/dist/styles/sprout/Accordion.svelte +17 -0
- package/dist/styles/sprout/Accordion.svelte.d.ts +8 -0
- package/dist/styles/sprout/AccordionItem.svelte +46 -0
- package/dist/styles/sprout/AccordionItem.svelte.d.ts +14 -0
- package/dist/styles/sprout/Alert.svelte +100 -0
- package/dist/styles/sprout/Alert.svelte.d.ts +18 -0
- package/dist/styles/sprout/Avatar.svelte +60 -0
- package/dist/styles/sprout/Avatar.svelte.d.ts +16 -0
- package/dist/styles/sprout/AvatarGroup.svelte +17 -0
- package/dist/styles/sprout/AvatarGroup.svelte.d.ts +8 -0
- package/dist/styles/sprout/Badge.svelte +58 -0
- package/dist/styles/sprout/Badge.svelte.d.ts +16 -0
- package/dist/styles/sprout/Breadcrumb.svelte +49 -0
- package/dist/styles/sprout/Breadcrumb.svelte.d.ts +14 -0
- package/dist/styles/sprout/Button.svelte +105 -0
- package/dist/styles/sprout/Button.svelte.d.ts +21 -0
- package/dist/styles/sprout/CTASection.svelte +77 -0
- package/dist/styles/sprout/CTASection.svelte.d.ts +18 -0
- package/dist/styles/sprout/Calendar.svelte +202 -0
- package/dist/styles/sprout/Calendar.svelte.d.ts +22 -0
- package/dist/styles/sprout/Card.svelte +82 -0
- package/dist/styles/sprout/Card.svelte.d.ts +21 -0
- package/dist/styles/sprout/Checkbox.svelte +76 -0
- package/dist/styles/sprout/Checkbox.svelte.d.ts +15 -0
- package/dist/styles/sprout/CheckboxGroup.svelte +112 -0
- package/dist/styles/sprout/CheckboxGroup.svelte.d.ts +30 -0
- package/dist/styles/sprout/Combobox.svelte +267 -0
- package/dist/styles/sprout/Combobox.svelte.d.ts +29 -0
- package/dist/styles/sprout/CommandPalette.svelte +210 -0
- package/dist/styles/sprout/CommandPalette.svelte.d.ts +32 -0
- package/dist/styles/sprout/ConfirmDialog.svelte +97 -0
- package/dist/styles/sprout/ConfirmDialog.svelte.d.ts +22 -0
- package/dist/styles/sprout/ContextMenu.svelte +89 -0
- package/dist/styles/sprout/ContextMenu.svelte.d.ts +15 -0
- package/dist/styles/sprout/DatePicker.svelte +193 -0
- package/dist/styles/sprout/DatePicker.svelte.d.ts +23 -0
- package/dist/styles/sprout/Divider.svelte +30 -0
- package/dist/styles/sprout/Divider.svelte.d.ts +10 -0
- package/dist/styles/sprout/Drawer.svelte +166 -0
- package/dist/styles/sprout/Drawer.svelte.d.ts +20 -0
- package/dist/styles/sprout/Dropdown.svelte +95 -0
- package/dist/styles/sprout/Dropdown.svelte.d.ts +23 -0
- package/dist/styles/sprout/Dropzone.svelte +211 -0
- package/dist/styles/sprout/Dropzone.svelte.d.ts +29 -0
- package/dist/styles/sprout/EmptyState.svelte +71 -0
- package/dist/styles/sprout/EmptyState.svelte.d.ts +18 -0
- package/dist/styles/sprout/FeatureCard.svelte +61 -0
- package/dist/styles/sprout/FeatureCard.svelte.d.ts +16 -0
- package/dist/styles/sprout/FeatureGrid.svelte +62 -0
- package/dist/styles/sprout/FeatureGrid.svelte.d.ts +16 -0
- package/dist/styles/sprout/Field.svelte +42 -0
- package/dist/styles/sprout/Field.svelte.d.ts +16 -0
- package/dist/styles/sprout/Footer.svelte +91 -0
- package/dist/styles/sprout/Footer.svelte.d.ts +25 -0
- package/dist/styles/sprout/HeroSection.svelte +86 -0
- package/dist/styles/sprout/HeroSection.svelte.d.ts +21 -0
- package/dist/styles/sprout/Input.svelte +61 -0
- package/dist/styles/sprout/Input.svelte.d.ts +18 -0
- package/dist/styles/sprout/InputGroup.svelte +20 -0
- package/dist/styles/sprout/InputGroup.svelte.d.ts +11 -0
- package/dist/styles/sprout/Kbd.svelte +18 -0
- package/dist/styles/sprout/Kbd.svelte.d.ts +8 -0
- package/dist/styles/sprout/Label.svelte +16 -0
- package/dist/styles/sprout/Label.svelte.d.ts +9 -0
- package/dist/styles/sprout/Link.svelte +44 -0
- package/dist/styles/sprout/Link.svelte.d.ts +16 -0
- package/dist/styles/sprout/LocaleProvider.svelte +24 -0
- package/dist/styles/sprout/LocaleProvider.svelte.d.ts +10 -0
- package/dist/styles/sprout/LogoCloud.svelte +61 -0
- package/dist/styles/sprout/LogoCloud.svelte.d.ts +19 -0
- package/dist/styles/sprout/MenuItem.svelte +64 -0
- package/dist/styles/sprout/MenuItem.svelte.d.ts +18 -0
- package/dist/styles/sprout/MenuSeparator.svelte +22 -0
- package/dist/styles/sprout/MenuSeparator.svelte.d.ts +8 -0
- package/dist/styles/sprout/Modal.svelte +119 -0
- package/dist/styles/sprout/Modal.svelte.d.ts +21 -0
- package/dist/styles/sprout/MultiSelect.svelte +288 -0
- package/dist/styles/sprout/MultiSelect.svelte.d.ts +31 -0
- package/dist/styles/sprout/Navbar.svelte +201 -0
- package/dist/styles/sprout/Navbar.svelte.d.ts +33 -0
- package/dist/styles/sprout/NumberInput.svelte +97 -0
- package/dist/styles/sprout/NumberInput.svelte.d.ts +15 -0
- package/dist/styles/sprout/Pagination.svelte +84 -0
- package/dist/styles/sprout/Pagination.svelte.d.ts +15 -0
- package/dist/styles/sprout/PasswordInput.svelte +101 -0
- package/dist/styles/sprout/PasswordInput.svelte.d.ts +12 -0
- package/dist/styles/sprout/Popover.svelte +90 -0
- package/dist/styles/sprout/Popover.svelte.d.ts +19 -0
- package/dist/styles/sprout/PricingCard.svelte +109 -0
- package/dist/styles/sprout/PricingCard.svelte.d.ts +25 -0
- package/dist/styles/sprout/Progress.svelte +59 -0
- package/dist/styles/sprout/Progress.svelte.d.ts +17 -0
- package/dist/styles/sprout/Radio.svelte +56 -0
- package/dist/styles/sprout/Radio.svelte.d.ts +15 -0
- package/dist/styles/sprout/RadioGroup.svelte +96 -0
- package/dist/styles/sprout/RadioGroup.svelte.d.ts +27 -0
- package/dist/styles/sprout/SegmentedControl.svelte +89 -0
- package/dist/styles/sprout/SegmentedControl.svelte.d.ts +25 -0
- package/dist/styles/sprout/Select.svelte +62 -0
- package/dist/styles/sprout/Select.svelte.d.ts +20 -0
- package/dist/styles/sprout/Sidebar.svelte +206 -0
- package/dist/styles/sprout/Sidebar.svelte.d.ts +36 -0
- package/dist/styles/sprout/Skeleton.svelte +46 -0
- package/dist/styles/sprout/Skeleton.svelte.d.ts +13 -0
- package/dist/styles/sprout/Slider.svelte +80 -0
- package/dist/styles/sprout/Slider.svelte.d.ts +19 -0
- package/dist/styles/sprout/Spinner.svelte +34 -0
- package/dist/styles/sprout/Spinner.svelte.d.ts +13 -0
- package/dist/styles/sprout/Stat.svelte +51 -0
- package/dist/styles/sprout/Stat.svelte.d.ts +16 -0
- package/dist/styles/sprout/StatsBand.svelte +64 -0
- package/dist/styles/sprout/StatsBand.svelte.d.ts +20 -0
- package/dist/styles/sprout/Steps.svelte +143 -0
- package/dist/styles/sprout/Steps.svelte.d.ts +24 -0
- package/dist/styles/sprout/Switch.svelte +65 -0
- package/dist/styles/sprout/Switch.svelte.d.ts +14 -0
- package/dist/styles/sprout/Table.svelte +218 -0
- package/dist/styles/sprout/Table.svelte.d.ts +66 -0
- package/dist/styles/sprout/Tabs.svelte +103 -0
- package/dist/styles/sprout/Tabs.svelte.d.ts +23 -0
- package/dist/styles/sprout/Testimonial.svelte +65 -0
- package/dist/styles/sprout/Testimonial.svelte.d.ts +19 -0
- package/dist/styles/sprout/Textarea.svelte +55 -0
- package/dist/styles/sprout/Textarea.svelte.d.ts +12 -0
- package/dist/styles/sprout/ThemeToggle.svelte +131 -0
- package/dist/styles/sprout/ThemeToggle.svelte.d.ts +16 -0
- package/dist/styles/sprout/Toaster.svelte +133 -0
- package/dist/styles/sprout/Toaster.svelte.d.ts +8 -0
- package/dist/styles/sprout/Tooltip.svelte +34 -0
- package/dist/styles/sprout/Tooltip.svelte.d.ts +12 -0
- package/dist/styles/sprout/fonts.css +78 -0
- package/dist/styles/sprout/index.d.ts +109 -0
- package/dist/styles/sprout/index.js +83 -0
- package/dist/styles/sprout/lift.d.ts +40 -0
- package/dist/styles/sprout/lift.js +36 -0
- package/dist/styles/sprout/theme.css +255 -0
- package/package.json +269 -3
- package/registry.json +137 -65
package/README.md
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
# @nqmcreative/ui
|
|
2
2
|
|
|
3
|
-
Svelte 5 (runes) + Tailwind CSS v4 component library. One package, **
|
|
3
|
+
Svelte 5 (runes) + Tailwind CSS v4 component library. One package, **three
|
|
4
4
|
styles**, 65 components each — the same props, the same behaviour, a different
|
|
5
5
|
look. Eight tones, light and dark, no runtime dependencies.
|
|
6
6
|
|
|
7
|
-
| style
|
|
8
|
-
|
|
|
9
|
-
| `matte`
|
|
10
|
-
| `paper`
|
|
7
|
+
| style | import | character |
|
|
8
|
+
| -------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------ |
|
|
9
|
+
| `matte` | `@nqmcreative/ui/matte` | Flat and sharp-cornered — no radius, no shadow, no gloss. Hairline rules and mono micro-labels do the work borders usually do. |
|
|
10
|
+
| `paper` | `@nqmcreative/ui/paper` | The neutral one. Start here if unsure: soft corners, light shadows, generous spacing, and nothing that fights the content. |
|
|
11
|
+
| `sprout` | `@nqmcreative/ui/sprout` | Warm and fully rounded — leaf green on cream, pill buttons, and a round display face. For school, community and family sites. |
|
|
11
12
|
|
|
12
13
|
The docs site lives in this repo — every component with a live demo, plus
|
|
13
14
|
theming, locale and contributing guides. `bun run dev` to read it.
|
|
@@ -132,6 +133,7 @@ Under SSR use `<LocaleProvider>` instead, so the locale is scoped to a request.
|
|
|
132
133
|
src/lib/core/ behaviour, a11y, types — no markup, no classes
|
|
133
134
|
src/lib/styles/matte/ 65 components
|
|
134
135
|
src/lib/styles/paper/ 65 components
|
|
136
|
+
src/lib/styles/sprout/ 65 components
|
|
135
137
|
scripts/catalogue.mjs the list every style must implement
|
|
136
138
|
```
|
|
137
139
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
|
|
5
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
6
|
+
children: Snippet;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let { class: className = '', children, ...rest }: Props = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<div
|
|
13
|
+
class="flex flex-col overflow-hidden rounded-[20px] border border-hairline bg-bg {className}"
|
|
14
|
+
{...rest}
|
|
15
|
+
>
|
|
16
|
+
{@render children()}
|
|
17
|
+
</div>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
4
|
+
children: Snippet;
|
|
5
|
+
}
|
|
6
|
+
declare const Accordion: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type Accordion = ReturnType<typeof Accordion>;
|
|
8
|
+
export default Accordion;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
import { toneText, type Tone } from '../../core/tones.js';
|
|
5
|
+
|
|
6
|
+
interface Props extends HTMLAttributes<HTMLDetailsElement> {
|
|
7
|
+
title: string;
|
|
8
|
+
open?: boolean;
|
|
9
|
+
tone?: Tone;
|
|
10
|
+
/** Short text on the right of the summary row. */
|
|
11
|
+
meta?: string;
|
|
12
|
+
children: Snippet;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let {
|
|
16
|
+
title,
|
|
17
|
+
open = $bindable(false),
|
|
18
|
+
tone = 'brand',
|
|
19
|
+
meta,
|
|
20
|
+
class: className = '',
|
|
21
|
+
children,
|
|
22
|
+
...rest
|
|
23
|
+
}: Props = $props();
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<details bind:open class="group border-b border-hairline last:border-b-0 {className}" {...rest}>
|
|
27
|
+
<summary
|
|
28
|
+
class="flex cursor-pointer list-none items-center gap-4 px-4 py-3.5 font-sans text-sm font-medium text-text transition-colors duration-150 ease-brand-out select-none hover:bg-bg-alt focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-current [&::-webkit-details-marker]:hidden"
|
|
29
|
+
>
|
|
30
|
+
<span class="flex-1">{title}</span>
|
|
31
|
+
{#if meta}<span class="text-[13px] font-normal text-text-muted">{meta}</span>{/if}
|
|
32
|
+
<svg
|
|
33
|
+
class="size-4 shrink-0 transition-transform duration-200 ease-brand-out group-open:rotate-180 {toneText[
|
|
34
|
+
tone
|
|
35
|
+
]}"
|
|
36
|
+
viewBox="0 0 16 16"
|
|
37
|
+
fill="none"
|
|
38
|
+
aria-hidden="true"
|
|
39
|
+
>
|
|
40
|
+
<path d="m4 6 4 4 4-4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" />
|
|
41
|
+
</svg>
|
|
42
|
+
</summary>
|
|
43
|
+
<div class="px-4 pb-4 font-sans text-sm leading-relaxed text-text-secondary">
|
|
44
|
+
{@render children()}
|
|
45
|
+
</div>
|
|
46
|
+
</details>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import { type Tone } from '../../core/tones.js';
|
|
4
|
+
interface Props extends HTMLAttributes<HTMLDetailsElement> {
|
|
5
|
+
title: string;
|
|
6
|
+
open?: boolean;
|
|
7
|
+
tone?: Tone;
|
|
8
|
+
/** Short text on the right of the summary row. */
|
|
9
|
+
meta?: string;
|
|
10
|
+
children: Snippet;
|
|
11
|
+
}
|
|
12
|
+
declare const AccordionItem: import("svelte").Component<Props, {}, "open">;
|
|
13
|
+
type AccordionItem = ReturnType<typeof AccordionItem>;
|
|
14
|
+
export default AccordionItem;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
import {
|
|
5
|
+
toneBorder,
|
|
6
|
+
toneBorderSoft,
|
|
7
|
+
toneSurface,
|
|
8
|
+
toneText,
|
|
9
|
+
type Tone
|
|
10
|
+
} from '../../core/tones.js';
|
|
11
|
+
import { useLocale } from '../../core/locale.svelte.js';
|
|
12
|
+
import { edge } from './lift.js';
|
|
13
|
+
|
|
14
|
+
export type AlertVariant = 'soft' | 'outline' | 'accent';
|
|
15
|
+
|
|
16
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
17
|
+
tone?: Tone;
|
|
18
|
+
variant?: AlertVariant;
|
|
19
|
+
title?: string;
|
|
20
|
+
/** Shows a close button and fires `ondismiss`. */
|
|
21
|
+
dismissible?: boolean;
|
|
22
|
+
ondismiss?: () => void;
|
|
23
|
+
/** Replaces the built-in status glyph. */
|
|
24
|
+
icon?: Snippet;
|
|
25
|
+
children?: Snippet;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let {
|
|
29
|
+
tone = 'info',
|
|
30
|
+
variant = 'soft',
|
|
31
|
+
title,
|
|
32
|
+
dismissible = false,
|
|
33
|
+
ondismiss,
|
|
34
|
+
icon,
|
|
35
|
+
class: className = '',
|
|
36
|
+
children,
|
|
37
|
+
...rest
|
|
38
|
+
}: Props = $props();
|
|
39
|
+
|
|
40
|
+
const t = useLocale();
|
|
41
|
+
let open = $state(true);
|
|
42
|
+
|
|
43
|
+
const variants = $derived({
|
|
44
|
+
soft: `${toneSurface[tone]} border ${toneBorderSoft[tone]}`,
|
|
45
|
+
outline: `bg-bg ${edge} border ${toneBorderSoft[tone]}`,
|
|
46
|
+
accent: `${toneSurface[tone]} border-l-4 ${toneBorder[tone]}`
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
function dismiss() {
|
|
50
|
+
open = false;
|
|
51
|
+
ondismiss?.();
|
|
52
|
+
}
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
{#if open}
|
|
56
|
+
<div
|
|
57
|
+
role={tone === 'danger' ? 'alert' : 'status'}
|
|
58
|
+
class="flex items-start gap-3 rounded-[20px] p-4 font-sans {variants[variant]} {className}"
|
|
59
|
+
{...rest}
|
|
60
|
+
>
|
|
61
|
+
<span class="mt-0.5 shrink-0 {toneText[tone]}">
|
|
62
|
+
{#if icon}
|
|
63
|
+
{@render icon()}
|
|
64
|
+
{:else}
|
|
65
|
+
<svg class="size-4" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
|
66
|
+
<circle cx="8" cy="8" r="6.5" stroke="currentColor" stroke-width="1.5" />
|
|
67
|
+
<path d="M8 4.75v4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
|
|
68
|
+
<circle cx="8" cy="11.25" r="0.85" fill="currentColor" />
|
|
69
|
+
</svg>
|
|
70
|
+
{/if}
|
|
71
|
+
</span>
|
|
72
|
+
|
|
73
|
+
<div class="flex min-w-0 flex-1 flex-col gap-1">
|
|
74
|
+
{#if title}
|
|
75
|
+
<p class="text-sm leading-snug font-semibold {toneText[tone]}">{title}</p>
|
|
76
|
+
{/if}
|
|
77
|
+
{#if children}
|
|
78
|
+
<div class="text-[13px] leading-relaxed text-text-secondary">{@render children()}</div>
|
|
79
|
+
{/if}
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
{#if dismissible}
|
|
83
|
+
<button
|
|
84
|
+
type="button"
|
|
85
|
+
onclick={dismiss}
|
|
86
|
+
aria-label={t.current.dismiss}
|
|
87
|
+
class="-m-1 shrink-0 rounded p-1 text-text-muted transition-colors duration-150 hover:bg-bg/60 hover:text-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current"
|
|
88
|
+
>
|
|
89
|
+
<svg class="size-3.5" viewBox="0 0 14 14" fill="none" aria-hidden="true">
|
|
90
|
+
<path
|
|
91
|
+
d="m2 2 10 10M12 2 2 12"
|
|
92
|
+
stroke="currentColor"
|
|
93
|
+
stroke-width="1.6"
|
|
94
|
+
stroke-linecap="round"
|
|
95
|
+
/>
|
|
96
|
+
</svg>
|
|
97
|
+
</button>
|
|
98
|
+
{/if}
|
|
99
|
+
</div>
|
|
100
|
+
{/if}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import { type Tone } from '../../core/tones.js';
|
|
4
|
+
export type AlertVariant = 'soft' | 'outline' | 'accent';
|
|
5
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
6
|
+
tone?: Tone;
|
|
7
|
+
variant?: AlertVariant;
|
|
8
|
+
title?: string;
|
|
9
|
+
/** Shows a close button and fires `ondismiss`. */
|
|
10
|
+
dismissible?: boolean;
|
|
11
|
+
ondismiss?: () => void;
|
|
12
|
+
/** Replaces the built-in status glyph. */
|
|
13
|
+
icon?: Snippet;
|
|
14
|
+
children?: Snippet;
|
|
15
|
+
}
|
|
16
|
+
declare const Alert: import("svelte").Component<Props, {}, "">;
|
|
17
|
+
type Alert = ReturnType<typeof Alert>;
|
|
18
|
+
export default Alert;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import { toneSoft, type Tone } from '../../core/tones.js';
|
|
4
|
+
|
|
5
|
+
export type AvatarSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
6
|
+
|
|
7
|
+
interface Props extends HTMLAttributes<HTMLSpanElement> {
|
|
8
|
+
src?: string;
|
|
9
|
+
alt?: string;
|
|
10
|
+
/** Falls back to the initials of this name when there is no `src`. */
|
|
11
|
+
name?: string;
|
|
12
|
+
size?: AvatarSize;
|
|
13
|
+
tone?: Tone;
|
|
14
|
+
/** Rounded square instead of the default circle. */
|
|
15
|
+
squared?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let {
|
|
19
|
+
src,
|
|
20
|
+
alt,
|
|
21
|
+
name = '',
|
|
22
|
+
size = 'md',
|
|
23
|
+
tone = 'brand',
|
|
24
|
+
squared = false,
|
|
25
|
+
class: className = '',
|
|
26
|
+
...rest
|
|
27
|
+
}: Props = $props();
|
|
28
|
+
|
|
29
|
+
const sizes: Record<AvatarSize, string> = {
|
|
30
|
+
xs: 'size-6 text-[10px]',
|
|
31
|
+
sm: 'size-8 text-xs',
|
|
32
|
+
md: 'size-10 text-sm',
|
|
33
|
+
lg: 'size-12 text-base',
|
|
34
|
+
xl: 'size-16 text-lg'
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const initials = $derived(
|
|
38
|
+
name
|
|
39
|
+
.trim()
|
|
40
|
+
.split(/\s+/)
|
|
41
|
+
.slice(0, 2)
|
|
42
|
+
.map((part) => part[0] ?? '')
|
|
43
|
+
.join('')
|
|
44
|
+
.toUpperCase()
|
|
45
|
+
);
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<span
|
|
49
|
+
class="inline-flex shrink-0 items-center justify-center overflow-hidden font-sans font-semibold select-none
|
|
50
|
+
{squared ? 'rounded-2xl' : 'rounded-full'} {sizes[size]} {src
|
|
51
|
+
? 'bg-bg-inset'
|
|
52
|
+
: toneSoft[tone]} {className}"
|
|
53
|
+
{...rest}
|
|
54
|
+
>
|
|
55
|
+
{#if src}
|
|
56
|
+
<img {src} alt={alt ?? name} class="size-full object-cover" />
|
|
57
|
+
{:else}
|
|
58
|
+
{initials}
|
|
59
|
+
{/if}
|
|
60
|
+
</span>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
import { type Tone } from '../../core/tones.js';
|
|
3
|
+
export type AvatarSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
4
|
+
interface Props extends HTMLAttributes<HTMLSpanElement> {
|
|
5
|
+
src?: string;
|
|
6
|
+
alt?: string;
|
|
7
|
+
/** Falls back to the initials of this name when there is no `src`. */
|
|
8
|
+
name?: string;
|
|
9
|
+
size?: AvatarSize;
|
|
10
|
+
tone?: Tone;
|
|
11
|
+
/** Rounded square instead of the default circle. */
|
|
12
|
+
squared?: boolean;
|
|
13
|
+
}
|
|
14
|
+
declare const Avatar: import("svelte").Component<Props, {}, "">;
|
|
15
|
+
type Avatar = ReturnType<typeof Avatar>;
|
|
16
|
+
export default Avatar;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
|
|
5
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
6
|
+
children: Snippet;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let { class: className = '', children, ...rest }: Props = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<div
|
|
13
|
+
class="flex items-center *:-ml-2 *:ring-2 *:ring-bg [&>*:first-child]:ml-0 {className}"
|
|
14
|
+
{...rest}
|
|
15
|
+
>
|
|
16
|
+
{@render children()}
|
|
17
|
+
</div>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
4
|
+
children: Snippet;
|
|
5
|
+
}
|
|
6
|
+
declare const AvatarGroup: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type AvatarGroup = ReturnType<typeof AvatarGroup>;
|
|
8
|
+
export default AvatarGroup;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
import {
|
|
5
|
+
toneBorderSoft,
|
|
6
|
+
toneFill,
|
|
7
|
+
toneSoft,
|
|
8
|
+
toneSolid,
|
|
9
|
+
toneText,
|
|
10
|
+
type Tone
|
|
11
|
+
} from '../../core/tones.js';
|
|
12
|
+
|
|
13
|
+
export type BadgeVariant = 'soft' | 'solid' | 'outline';
|
|
14
|
+
export type BadgeSize = 'sm' | 'md';
|
|
15
|
+
|
|
16
|
+
interface Props extends HTMLAttributes<HTMLSpanElement> {
|
|
17
|
+
tone?: Tone;
|
|
18
|
+
variant?: BadgeVariant;
|
|
19
|
+
size?: BadgeSize;
|
|
20
|
+
/** Small filled circle before the label. */
|
|
21
|
+
dot?: boolean;
|
|
22
|
+
children: Snippet;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let {
|
|
26
|
+
tone = 'brand',
|
|
27
|
+
variant = 'soft',
|
|
28
|
+
size = 'md',
|
|
29
|
+
dot = false,
|
|
30
|
+
class: className = '',
|
|
31
|
+
children,
|
|
32
|
+
...rest
|
|
33
|
+
}: Props = $props();
|
|
34
|
+
|
|
35
|
+
const variants: Record<BadgeVariant, string> = $derived({
|
|
36
|
+
soft: toneSoft[tone],
|
|
37
|
+
solid: toneSolid[tone],
|
|
38
|
+
outline: `border ${toneBorderSoft[tone]} ${toneText[tone]} bg-bg`
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const sizes: Record<BadgeSize, string> = {
|
|
42
|
+
sm: 'px-2.5 py-0.5 text-[11px]',
|
|
43
|
+
md: 'px-3 py-1 text-xs'
|
|
44
|
+
};
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<span
|
|
48
|
+
class="inline-flex items-center gap-1.5 rounded-full font-heading font-medium tracking-[0.04em]
|
|
49
|
+
{variants[variant]} {sizes[size]} {className}"
|
|
50
|
+
{...rest}
|
|
51
|
+
>
|
|
52
|
+
{#if dot}
|
|
53
|
+
<span
|
|
54
|
+
class="size-1.5 shrink-0 rounded-full {variant === 'solid' ? 'bg-current' : toneFill[tone]}"
|
|
55
|
+
></span>
|
|
56
|
+
{/if}
|
|
57
|
+
{@render children()}
|
|
58
|
+
</span>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import { type Tone } from '../../core/tones.js';
|
|
4
|
+
export type BadgeVariant = 'soft' | 'solid' | 'outline';
|
|
5
|
+
export type BadgeSize = 'sm' | 'md';
|
|
6
|
+
interface Props extends HTMLAttributes<HTMLSpanElement> {
|
|
7
|
+
tone?: Tone;
|
|
8
|
+
variant?: BadgeVariant;
|
|
9
|
+
size?: BadgeSize;
|
|
10
|
+
/** Small filled circle before the label. */
|
|
11
|
+
dot?: boolean;
|
|
12
|
+
children: Snippet;
|
|
13
|
+
}
|
|
14
|
+
declare const Badge: import("svelte").Component<Props, {}, "">;
|
|
15
|
+
type Badge = ReturnType<typeof Badge>;
|
|
16
|
+
export default Badge;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import { toneHoverText, type Tone } from '../../core/tones.js';
|
|
4
|
+
import { useLocale } from '../../core/locale.svelte.js';
|
|
5
|
+
|
|
6
|
+
export interface BreadcrumbItem {
|
|
7
|
+
label: string;
|
|
8
|
+
href?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface Props extends HTMLAttributes<HTMLElement> {
|
|
12
|
+
items: BreadcrumbItem[];
|
|
13
|
+
separator?: string;
|
|
14
|
+
tone?: Tone;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let { items, separator = '/', tone = 'brand', class: className = '', ...rest }: Props = $props();
|
|
18
|
+
|
|
19
|
+
const t = useLocale();
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<nav aria-label={t.current.breadcrumb} class="font-sans text-sm {className}" {...rest}>
|
|
23
|
+
<ol class="flex flex-wrap items-center gap-1.5">
|
|
24
|
+
{#each items as item, i (item.label)}
|
|
25
|
+
<li class="flex items-center gap-1.5">
|
|
26
|
+
{#if i > 0}
|
|
27
|
+
<span class="text-hairline-strong select-none" aria-hidden="true">{separator}</span>
|
|
28
|
+
{/if}
|
|
29
|
+
{#if item.href && i < items.length - 1}
|
|
30
|
+
<a
|
|
31
|
+
href={item.href}
|
|
32
|
+
class="rounded px-1 py-0.5 text-text-muted transition-colors duration-150 ease-brand-out hover:bg-bg-inset {toneHoverText[
|
|
33
|
+
tone
|
|
34
|
+
]}"
|
|
35
|
+
>
|
|
36
|
+
{item.label}
|
|
37
|
+
</a>
|
|
38
|
+
{:else}
|
|
39
|
+
<span
|
|
40
|
+
class="px-1 py-0.5 font-medium text-text"
|
|
41
|
+
aria-current={i === items.length - 1 ? 'page' : undefined}
|
|
42
|
+
>
|
|
43
|
+
{item.label}
|
|
44
|
+
</span>
|
|
45
|
+
{/if}
|
|
46
|
+
</li>
|
|
47
|
+
{/each}
|
|
48
|
+
</ol>
|
|
49
|
+
</nav>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
import { type Tone } from '../../core/tones.js';
|
|
3
|
+
export interface BreadcrumbItem {
|
|
4
|
+
label: string;
|
|
5
|
+
href?: string;
|
|
6
|
+
}
|
|
7
|
+
interface Props extends HTMLAttributes<HTMLElement> {
|
|
8
|
+
items: BreadcrumbItem[];
|
|
9
|
+
separator?: string;
|
|
10
|
+
tone?: Tone;
|
|
11
|
+
}
|
|
12
|
+
declare const Breadcrumb: import("svelte").Component<Props, {}, "">;
|
|
13
|
+
type Breadcrumb = ReturnType<typeof Breadcrumb>;
|
|
14
|
+
export default Breadcrumb;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
4
|
+
import Spinner from './Spinner.svelte';
|
|
5
|
+
import {
|
|
6
|
+
focusRing,
|
|
7
|
+
toneBorderSoft,
|
|
8
|
+
toneRing,
|
|
9
|
+
toneSoft,
|
|
10
|
+
toneSoftHover,
|
|
11
|
+
toneSolid,
|
|
12
|
+
toneSolidHover,
|
|
13
|
+
toneText,
|
|
14
|
+
type Tone
|
|
15
|
+
} from '../../core/tones.js';
|
|
16
|
+
import { edge, lift } from './lift.js';
|
|
17
|
+
|
|
18
|
+
export type ButtonVariant = 'solid' | 'soft' | 'outline' | 'ghost' | 'link' | 'primary';
|
|
19
|
+
export type ButtonSize = 'sm' | 'md' | 'lg' | 'xl';
|
|
20
|
+
|
|
21
|
+
interface Props extends HTMLButtonAttributes {
|
|
22
|
+
variant?: ButtonVariant;
|
|
23
|
+
tone?: Tone;
|
|
24
|
+
size?: ButtonSize;
|
|
25
|
+
/** Renders an `<a>` instead of a `<button>`. */
|
|
26
|
+
href?: string;
|
|
27
|
+
target?: string;
|
|
28
|
+
rel?: string;
|
|
29
|
+
loading?: boolean;
|
|
30
|
+
/** Stretch to the container's full width. */
|
|
31
|
+
block?: boolean;
|
|
32
|
+
children: Snippet;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let {
|
|
36
|
+
variant = 'solid',
|
|
37
|
+
tone = 'brand',
|
|
38
|
+
size = 'md',
|
|
39
|
+
href,
|
|
40
|
+
loading = false,
|
|
41
|
+
block = false,
|
|
42
|
+
disabled = false,
|
|
43
|
+
type = 'button',
|
|
44
|
+
class: className = '',
|
|
45
|
+
children,
|
|
46
|
+
...rest
|
|
47
|
+
}: Props = $props();
|
|
48
|
+
|
|
49
|
+
// Fully round, in the heading face, and standing on a solid lip it sinks
|
|
50
|
+
// into when pressed: the three things that make a sprout button read as
|
|
51
|
+
// something to touch rather than something to click. The 3px it travels is
|
|
52
|
+
// exactly what the lip loses, so the top face lands where the shadow was.
|
|
53
|
+
const base =
|
|
54
|
+
'inline-flex items-center justify-center gap-2 rounded-full border font-heading font-semibold whitespace-nowrap transition-[color,background-color,border-color,box-shadow,transform] duration-150 ease-brand-out active:translate-y-[3px] disabled:opacity-50 disabled:pointer-events-none disabled:active:translate-y-0 aria-disabled:opacity-50 aria-disabled:pointer-events-none';
|
|
55
|
+
|
|
56
|
+
// Roomier than the other styles' — a pill needs horizontal padding to keep its label
|
|
57
|
+
// off the curve, and the extra width suits a touch target.
|
|
58
|
+
const sizes: Record<ButtonSize, string> = {
|
|
59
|
+
sm: 'h-9 px-4 text-[13px]',
|
|
60
|
+
md: 'h-11 px-5 text-sm',
|
|
61
|
+
lg: 'h-12 px-6 text-[15px]',
|
|
62
|
+
xl: 'h-14 px-8 text-base'
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const variantClass = $derived.by(() => {
|
|
66
|
+
switch (variant) {
|
|
67
|
+
case 'primary':
|
|
68
|
+
case 'solid':
|
|
69
|
+
return `border-transparent ${toneSolid[tone]} ${toneSolidHover[tone]} ${lift[tone]}`;
|
|
70
|
+
case 'soft':
|
|
71
|
+
return `border-transparent ${toneSoft[tone]} ${toneSoftHover[tone]} ${edge}`;
|
|
72
|
+
// Outline keeps its own colours on hover — swapping to a solid fill is
|
|
73
|
+
// matte's move, and it fights sprout's soft surfaces.
|
|
74
|
+
case 'outline':
|
|
75
|
+
return `bg-bg ${edge} ${toneText[tone]} ${toneBorderSoft[tone]} hover:bg-bg-alt`;
|
|
76
|
+
// Nothing raised, so nothing to sink: ghost stays flat on the page.
|
|
77
|
+
case 'ghost':
|
|
78
|
+
return `border-transparent bg-transparent ${toneText[tone]} ${toneSoftHover[tone]} active:translate-y-0`;
|
|
79
|
+
// No pill and no dip: this variant is a sentence, not a target.
|
|
80
|
+
case 'link':
|
|
81
|
+
return `border-transparent ${toneText[tone]} h-auto p-0 text-sm underline-offset-4 hover:underline active:translate-y-0`;
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const isLink = $derived(variant === 'link');
|
|
86
|
+
const inert = $derived(disabled || loading);
|
|
87
|
+
</script>
|
|
88
|
+
|
|
89
|
+
<svelte:element
|
|
90
|
+
this={href ? 'a' : 'button'}
|
|
91
|
+
{href}
|
|
92
|
+
type={href ? undefined : type}
|
|
93
|
+
disabled={href ? undefined : inert}
|
|
94
|
+
aria-disabled={href && inert ? 'true' : undefined}
|
|
95
|
+
aria-busy={loading ? 'true' : undefined}
|
|
96
|
+
role={href ? 'button' : undefined}
|
|
97
|
+
class="{base} {focusRing} {toneRing[tone]} {variantClass} {isLink ? '' : sizes[size]}
|
|
98
|
+
{block ? 'w-full' : ''} {className}"
|
|
99
|
+
{...rest}
|
|
100
|
+
>
|
|
101
|
+
{#if loading}
|
|
102
|
+
<Spinner size={size === 'sm' ? 'xs' : 'sm'} label="" />
|
|
103
|
+
{/if}
|
|
104
|
+
{@render children()}
|
|
105
|
+
</svelte:element>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
3
|
+
import { type Tone } from '../../core/tones.js';
|
|
4
|
+
export type ButtonVariant = 'solid' | 'soft' | 'outline' | 'ghost' | 'link' | 'primary';
|
|
5
|
+
export type ButtonSize = 'sm' | 'md' | 'lg' | 'xl';
|
|
6
|
+
interface Props extends HTMLButtonAttributes {
|
|
7
|
+
variant?: ButtonVariant;
|
|
8
|
+
tone?: Tone;
|
|
9
|
+
size?: ButtonSize;
|
|
10
|
+
/** Renders an `<a>` instead of a `<button>`. */
|
|
11
|
+
href?: string;
|
|
12
|
+
target?: string;
|
|
13
|
+
rel?: string;
|
|
14
|
+
loading?: boolean;
|
|
15
|
+
/** Stretch to the container's full width. */
|
|
16
|
+
block?: boolean;
|
|
17
|
+
children: Snippet;
|
|
18
|
+
}
|
|
19
|
+
declare const Button: import("svelte").Component<Props, {}, "">;
|
|
20
|
+
type Button = ReturnType<typeof Button>;
|
|
21
|
+
export default Button;
|