@nqmcreative/ui 0.2.1 → 0.2.3
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/core/index.d.ts +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/trigger.d.ts +6 -0
- package/dist/core/trigger.js +13 -0
- package/dist/styles/matte/Button.svelte +4 -4
- package/dist/styles/matte/Card.svelte +3 -3
- package/dist/styles/matte/Combobox.svelte +2 -2
- package/dist/styles/matte/CommandPalette.svelte +32 -7
- package/dist/styles/matte/CommandPalette.svelte.d.ts +5 -0
- package/dist/styles/matte/DatePicker.svelte +2 -2
- package/dist/styles/matte/Drawer.svelte +2 -2
- package/dist/styles/matte/Dropzone.svelte +1 -1
- package/dist/styles/matte/FeatureCard.svelte +1 -1
- package/dist/styles/matte/Input.svelte +4 -4
- package/dist/styles/matte/MenuItem.svelte +1 -1
- package/dist/styles/matte/Modal.svelte +3 -3
- package/dist/styles/matte/NumberInput.svelte +2 -2
- package/dist/styles/matte/PasswordInput.svelte +2 -2
- package/dist/styles/matte/PricingCard.svelte +3 -3
- package/dist/styles/matte/SegmentedControl.svelte +1 -1
- package/dist/styles/matte/Select.svelte +1 -1
- package/dist/styles/matte/Sidebar.svelte +75 -26
- package/dist/styles/matte/Sidebar.svelte.d.ts +15 -1
- package/dist/styles/matte/StatsBand.svelte +1 -1
- package/dist/styles/matte/Table.svelte +3 -3
- package/dist/styles/matte/Testimonial.svelte +2 -2
- package/dist/styles/matte/Textarea.svelte +1 -1
- package/dist/styles/matte/index.d.ts +1 -1
- package/dist/styles/paper/CommandPalette.svelte +25 -2
- package/dist/styles/paper/CommandPalette.svelte.d.ts +5 -0
- package/dist/styles/paper/Modal.svelte +9 -1
- package/dist/styles/paper/Sidebar.svelte +75 -26
- package/dist/styles/paper/Sidebar.svelte.d.ts +15 -1
- package/dist/styles/paper/fonts.css +34 -7
- package/dist/styles/paper/index.d.ts +1 -1
- package/dist/styles/paper/theme.css +11 -8
- 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 +233 -0
- package/dist/styles/sprout/CommandPalette.svelte.d.ts +37 -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 +127 -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 +255 -0
- package/dist/styles/sprout/Sidebar.svelte.d.ts +50 -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 +77 -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 +139 -66
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
export type SidebarVariant = 'plain' | 'filled' | 'floating';
|
|
1
3
|
export interface SidebarItem {
|
|
2
4
|
/** Stable id used for the active state. Falls back to `href`. */
|
|
3
5
|
id?: string;
|
|
@@ -5,17 +7,27 @@ export interface SidebarItem {
|
|
|
5
7
|
href?: string;
|
|
6
8
|
disabled?: boolean;
|
|
7
9
|
badge?: string | number;
|
|
10
|
+
/**
|
|
11
|
+
* Leading glyph, 16px. The one thing a collapsed rail still shows, so
|
|
12
|
+
* an item that has to survive `collapsed` needs one.
|
|
13
|
+
*/
|
|
14
|
+
icon?: Snippet;
|
|
8
15
|
/** Nested links — the parent becomes a collapsible group. */
|
|
9
16
|
items?: SidebarItem[];
|
|
10
17
|
/** Open the group on first render. */
|
|
11
18
|
open?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* `false` pins a group open: the parent stops being a button and the
|
|
21
|
+
* children are always on show. For navigation a reader should not be
|
|
22
|
+
* able to lose.
|
|
23
|
+
*/
|
|
24
|
+
collapsible?: boolean;
|
|
12
25
|
}
|
|
13
26
|
export interface SidebarSection {
|
|
14
27
|
/** Small heading above a run of items. */
|
|
15
28
|
label?: string;
|
|
16
29
|
items: SidebarItem[];
|
|
17
30
|
}
|
|
18
|
-
import type { Snippet } from 'svelte';
|
|
19
31
|
import { type Tone } from '../../core/tones.js';
|
|
20
32
|
interface Props {
|
|
21
33
|
sections: SidebarSection[];
|
|
@@ -25,6 +37,8 @@ interface Props {
|
|
|
25
37
|
collapsed?: boolean;
|
|
26
38
|
/** Show the collapse button. */
|
|
27
39
|
collapsible?: boolean;
|
|
40
|
+
/** Surface treatment: bare, the framed default, or a detached card. */
|
|
41
|
+
variant?: SidebarVariant;
|
|
28
42
|
tone?: Tone;
|
|
29
43
|
header?: Snippet;
|
|
30
44
|
footer?: Snippet;
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
|
|
42
42
|
<div class="grid {cols} {bordered ? 'gap-px border border-hairline bg-hairline' : 'gap-10'}">
|
|
43
43
|
{#each stats as stat (stat.label)}
|
|
44
|
-
<div class={bordered ? 'bg-bg p-
|
|
44
|
+
<div class={bordered ? 'bg-bg p-5' : ''}>
|
|
45
45
|
<Stat
|
|
46
46
|
label={stat.label}
|
|
47
47
|
value={stat.value}
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
right: 'text-right'
|
|
78
78
|
};
|
|
79
79
|
|
|
80
|
-
const pad = $derived(compact ? 'px-
|
|
80
|
+
const pad = $derived(compact ? 'px-3.5 py-1.5' : 'px-3.5 py-2.5');
|
|
81
81
|
|
|
82
82
|
const sorted = $derived(sortLocally ? sortRows(rows, sort) : rows);
|
|
83
83
|
|
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
<table class="w-full border-collapse font-sans text-[15px]">
|
|
113
113
|
{#if caption}
|
|
114
114
|
<caption
|
|
115
|
-
class="border-b border-hairline px-
|
|
115
|
+
class="border-b border-hairline px-3.5 py-2.5 text-left font-sans text-sm text-text-muted"
|
|
116
116
|
>
|
|
117
117
|
{caption}
|
|
118
118
|
</caption>
|
|
@@ -170,7 +170,7 @@
|
|
|
170
170
|
<tr>
|
|
171
171
|
<td
|
|
172
172
|
colspan={columns.length + (selectable ? 1 : 0)}
|
|
173
|
-
class="px-
|
|
173
|
+
class="px-3.5 py-10 text-center text-sm text-text-muted"
|
|
174
174
|
>
|
|
175
175
|
{#if empty}{@render empty()}{:else}{t.current.noData}{/if}
|
|
176
176
|
</td>
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
|
|
35
35
|
const variants = $derived({
|
|
36
36
|
plain: '',
|
|
37
|
-
card: 'border border-hairline bg-bg p-
|
|
38
|
-
accent: `${toneSurface[tone]} border-l-2 ${toneBorderLeft[tone]} p-
|
|
37
|
+
card: 'border border-hairline bg-bg p-5',
|
|
38
|
+
accent: `${toneSurface[tone]} border-l-2 ${toneBorderLeft[tone]} p-5`
|
|
39
39
|
});
|
|
40
40
|
</script>
|
|
41
41
|
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
bind:value
|
|
49
49
|
{rows}
|
|
50
50
|
aria-invalid={invalid ? 'true' : undefined}
|
|
51
|
-
class="w-full border bg-bg px-
|
|
51
|
+
class="w-full border bg-bg px-3.5 py-2.5 font-sans text-[15px] text-text transition-colors duration-150 ease-brand-out placeholder:text-text-muted focus:outline-none disabled:pointer-events-none disabled:opacity-50
|
|
52
52
|
{autoresize ? 'resize-none overflow-hidden' : 'resize-y'}
|
|
53
53
|
{invalid ? 'border-danger' : `border-hairline ${toneFocusBorder[tone]}`} {className}"
|
|
54
54
|
{...rest}></textarea>
|
|
@@ -76,7 +76,7 @@ export type { StepItem } from './Steps.svelte';
|
|
|
76
76
|
export { default as Navbar } from './Navbar.svelte';
|
|
77
77
|
export type { NavItem } from './Navbar.svelte';
|
|
78
78
|
export { default as Sidebar } from './Sidebar.svelte';
|
|
79
|
-
export type { SidebarItem, SidebarSection } from './Sidebar.svelte';
|
|
79
|
+
export type { SidebarVariant, SidebarItem, SidebarSection } from './Sidebar.svelte';
|
|
80
80
|
export { default as Footer } from './Footer.svelte';
|
|
81
81
|
export type { FooterLink, FooterColumn } from './Footer.svelte';
|
|
82
82
|
export { default as HeroSection } from './HeroSection.svelte';
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
import { ListCursor, groupItems, matchQuery, revealIndex } from '../../core/list.svelte.js';
|
|
22
22
|
import { useLocale } from '../../core/locale.svelte.js';
|
|
23
23
|
import { toneSoft, type Tone } from '../../core/tones.js';
|
|
24
|
+
import { isTypingTarget } from '../../core/trigger.js';
|
|
24
25
|
import Kbd from './Kbd.svelte';
|
|
25
26
|
|
|
26
27
|
interface Props {
|
|
@@ -34,6 +35,11 @@
|
|
|
34
35
|
* Pass `null` to wire your own trigger only.
|
|
35
36
|
*/
|
|
36
37
|
hotkey?: string | null;
|
|
38
|
+
/**
|
|
39
|
+
* Key that opens the palette on its own, pressed anywhere the reader
|
|
40
|
+
* isn't typing. Default `'/'`. Pass `null` to turn it off.
|
|
41
|
+
*/
|
|
42
|
+
quickKey?: string | null;
|
|
37
43
|
onselect?: (item: CommandItem) => void;
|
|
38
44
|
class?: string;
|
|
39
45
|
}
|
|
@@ -45,6 +51,7 @@
|
|
|
45
51
|
emptyText,
|
|
46
52
|
tone = 'brand',
|
|
47
53
|
hotkey = 'k',
|
|
54
|
+
quickKey = '/',
|
|
48
55
|
onselect,
|
|
49
56
|
class: className = ''
|
|
50
57
|
}: Props = $props();
|
|
@@ -82,11 +89,27 @@
|
|
|
82
89
|
});
|
|
83
90
|
|
|
84
91
|
$effect(() => {
|
|
85
|
-
if (!hotkey) return;
|
|
92
|
+
if (!hotkey && !quickKey) return;
|
|
86
93
|
const onKeydown = (event: KeyboardEvent) => {
|
|
87
|
-
|
|
94
|
+
const key = event.key.toLowerCase();
|
|
95
|
+
|
|
96
|
+
if ((event.metaKey || event.ctrlKey) && key === hotkey?.toLowerCase()) {
|
|
88
97
|
event.preventDefault();
|
|
89
98
|
open = !open;
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// A bare key can only open the palette, never close it: while it is
|
|
103
|
+
// open the keystroke belongs to the query field.
|
|
104
|
+
if (
|
|
105
|
+
key === quickKey?.toLowerCase() &&
|
|
106
|
+
!event.metaKey &&
|
|
107
|
+
!event.ctrlKey &&
|
|
108
|
+
!event.altKey &&
|
|
109
|
+
!isTypingTarget(event.target)
|
|
110
|
+
) {
|
|
111
|
+
event.preventDefault();
|
|
112
|
+
open = true;
|
|
90
113
|
}
|
|
91
114
|
};
|
|
92
115
|
window.addEventListener('keydown', onKeydown);
|
|
@@ -24,6 +24,11 @@ interface Props {
|
|
|
24
24
|
* Pass `null` to wire your own trigger only.
|
|
25
25
|
*/
|
|
26
26
|
hotkey?: string | null;
|
|
27
|
+
/**
|
|
28
|
+
* Key that opens the palette on its own, pressed anywhere the reader
|
|
29
|
+
* isn't typing. Default `'/'`. Pass `null` to turn it off.
|
|
30
|
+
*/
|
|
31
|
+
quickKey?: string | null;
|
|
27
32
|
onselect?: (item: CommandItem) => void;
|
|
28
33
|
class?: string;
|
|
29
34
|
}
|
|
@@ -103,7 +103,15 @@
|
|
|
103
103
|
</div>
|
|
104
104
|
{/if}
|
|
105
105
|
|
|
106
|
-
|
|
106
|
+
<!--
|
|
107
|
+
The body's top padding depends on what sits above it: 8px is the right
|
|
108
|
+
gap under a header, and far too tight when there is none — a titleless
|
|
109
|
+
modal, ConfirmDialog among them, would otherwise start against the edge.
|
|
110
|
+
-->
|
|
111
|
+
<div
|
|
112
|
+
class="px-6 pb-5 font-sans text-sm leading-relaxed text-text-secondary
|
|
113
|
+
{title || closable ? 'pt-2' : 'pt-5'}"
|
|
114
|
+
>
|
|
107
115
|
{@render children()}
|
|
108
116
|
</div>
|
|
109
117
|
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
<script module lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
export type SidebarVariant = 'plain' | 'filled' | 'floating';
|
|
5
|
+
|
|
2
6
|
export interface SidebarItem {
|
|
3
7
|
/** Stable id used for the active state. Falls back to `href`. */
|
|
4
8
|
id?: string;
|
|
@@ -6,10 +10,21 @@
|
|
|
6
10
|
href?: string;
|
|
7
11
|
disabled?: boolean;
|
|
8
12
|
badge?: string | number;
|
|
13
|
+
/**
|
|
14
|
+
* Leading glyph, 16px. The one thing a collapsed rail still shows, so
|
|
15
|
+
* an item that has to survive `collapsed` needs one.
|
|
16
|
+
*/
|
|
17
|
+
icon?: Snippet;
|
|
9
18
|
/** Nested links — the parent becomes a collapsible group. */
|
|
10
19
|
items?: SidebarItem[];
|
|
11
20
|
/** Open the group on first render. */
|
|
12
21
|
open?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* `false` pins a group open: the parent stops being a button and the
|
|
24
|
+
* children are always on show. For navigation a reader should not be
|
|
25
|
+
* able to lose.
|
|
26
|
+
*/
|
|
27
|
+
collapsible?: boolean;
|
|
13
28
|
}
|
|
14
29
|
|
|
15
30
|
export interface SidebarSection {
|
|
@@ -20,7 +35,6 @@
|
|
|
20
35
|
</script>
|
|
21
36
|
|
|
22
37
|
<script lang="ts">
|
|
23
|
-
import type { Snippet } from 'svelte';
|
|
24
38
|
import { useLocale } from '../../core/locale.svelte.js';
|
|
25
39
|
import { focusRing, toneRing, toneSoft, type Tone } from '../../core/tones.js';
|
|
26
40
|
|
|
@@ -32,6 +46,8 @@
|
|
|
32
46
|
collapsed?: boolean;
|
|
33
47
|
/** Show the collapse button. */
|
|
34
48
|
collapsible?: boolean;
|
|
49
|
+
/** Surface treatment: bare, the framed default, or a detached card. */
|
|
50
|
+
variant?: SidebarVariant;
|
|
35
51
|
tone?: Tone;
|
|
36
52
|
header?: Snippet;
|
|
37
53
|
footer?: Snippet;
|
|
@@ -44,6 +60,7 @@
|
|
|
44
60
|
value = $bindable(''),
|
|
45
61
|
collapsed = $bindable(false),
|
|
46
62
|
collapsible = true,
|
|
63
|
+
variant = 'filled',
|
|
47
64
|
tone = 'brand',
|
|
48
65
|
header,
|
|
49
66
|
footer,
|
|
@@ -59,7 +76,8 @@
|
|
|
59
76
|
let opened = $state<Record<string, boolean>>({});
|
|
60
77
|
|
|
61
78
|
const isOpen = (item: SidebarItem) =>
|
|
62
|
-
|
|
79
|
+
item.collapsible === false ||
|
|
80
|
+
(opened[keyOf(item)] ?? (item.open || item.items!.some((child) => keyOf(child) === value)));
|
|
63
81
|
|
|
64
82
|
function toggle(item: SidebarItem) {
|
|
65
83
|
opened = { ...opened, [keyOf(item)]: !isOpen(item) };
|
|
@@ -71,13 +89,50 @@
|
|
|
71
89
|
onnavigate?.(item);
|
|
72
90
|
}
|
|
73
91
|
|
|
92
|
+
const surfaces: Record<SidebarVariant, string> = {
|
|
93
|
+
plain: 'h-full bg-transparent',
|
|
94
|
+
filled: 'h-full border-r border-hairline bg-bg-alt',
|
|
95
|
+
floating: 'm-3 h-[calc(100%-1.5rem)] rounded-lg border border-hairline bg-bg-alt shadow-sm'
|
|
96
|
+
};
|
|
97
|
+
|
|
74
98
|
const row =
|
|
75
99
|
'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
100
|
</script>
|
|
77
101
|
|
|
102
|
+
<!-- Nested links. One copy, rendered by both kinds of group. -->
|
|
103
|
+
{#snippet subitems(item: SidebarItem)}
|
|
104
|
+
<div class="ml-4 flex flex-col gap-0.5 border-l border-hairline pl-2">
|
|
105
|
+
{#each item.items as child (keyOf(child))}
|
|
106
|
+
<a
|
|
107
|
+
href={child.href}
|
|
108
|
+
onclick={() => go(child)}
|
|
109
|
+
aria-current={value === keyOf(child) ? 'page' : undefined}
|
|
110
|
+
class="{row} {focusRing} {toneRing[tone]} {value === keyOf(child)
|
|
111
|
+
? `${toneSoft[tone]} font-medium`
|
|
112
|
+
: 'text-text-muted hover:bg-bg-inset hover:text-text'} {child.disabled
|
|
113
|
+
? 'pointer-events-none opacity-40'
|
|
114
|
+
: ''}"
|
|
115
|
+
>
|
|
116
|
+
{@render glyph(child)}
|
|
117
|
+
<span class="min-w-0 flex-1 truncate">{child.label}</span>
|
|
118
|
+
{#if child.badge !== undefined}
|
|
119
|
+
<span class="shrink-0 text-[11px] tabular-nums">{child.badge}</span>
|
|
120
|
+
{/if}
|
|
121
|
+
</a>
|
|
122
|
+
{/each}
|
|
123
|
+
</div>
|
|
124
|
+
{/snippet}
|
|
125
|
+
|
|
126
|
+
<!-- One box for every glyph, so rows line up whether or not an item has one. -->
|
|
127
|
+
{#snippet glyph(item: SidebarItem)}
|
|
128
|
+
{#if item.icon}
|
|
129
|
+
<span class="grid size-4 shrink-0 place-items-center *:size-4">{@render item.icon()}</span>
|
|
130
|
+
{/if}
|
|
131
|
+
{/snippet}
|
|
132
|
+
|
|
78
133
|
<aside
|
|
79
|
-
class="flex
|
|
80
|
-
{collapsed ? 'w-16' : 'w-60'} {className}"
|
|
134
|
+
class="flex shrink-0 flex-col transition-[width] duration-200 ease-brand-out
|
|
135
|
+
{surfaces[variant]} {collapsed ? 'w-16' : 'w-60'} {className}"
|
|
81
136
|
>
|
|
82
137
|
{#if header}
|
|
83
138
|
<div class="flex h-16 shrink-0 items-center gap-3 border-b border-hairline px-3">
|
|
@@ -97,16 +152,25 @@
|
|
|
97
152
|
{/if}
|
|
98
153
|
|
|
99
154
|
{#each section.items as item (keyOf(item))}
|
|
100
|
-
{#if item.items?.length}
|
|
155
|
+
{#if item.items?.length && item.collapsible === false}
|
|
156
|
+
<div class="{row} {collapsed ? 'justify-center' : ''} font-medium text-text-muted">
|
|
157
|
+
{@render glyph(item)}
|
|
158
|
+
<span class="min-w-0 flex-1 truncate {collapsed ? 'sr-only' : ''}">{item.label}</span>
|
|
159
|
+
</div>
|
|
160
|
+
{#if isOpen(item) && !collapsed}
|
|
161
|
+
{@render subitems(item)}
|
|
162
|
+
{/if}
|
|
163
|
+
{:else if item.items?.length}
|
|
101
164
|
<button
|
|
102
165
|
type="button"
|
|
103
166
|
onclick={() => toggle(item)}
|
|
104
167
|
aria-expanded={isOpen(item)}
|
|
105
168
|
title={collapsed ? item.label : undefined}
|
|
106
|
-
class="{row} {focusRing} {toneRing[
|
|
169
|
+
class="{row} {collapsed ? 'justify-center' : ''} {focusRing} {toneRing[
|
|
107
170
|
tone
|
|
108
171
|
]} text-text-secondary hover:bg-bg-inset hover:text-text"
|
|
109
172
|
>
|
|
173
|
+
{@render glyph(item)}
|
|
110
174
|
<span class="min-w-0 flex-1 truncate {collapsed ? 'sr-only' : ''}">{item.label}</span>
|
|
111
175
|
{#if !collapsed}
|
|
112
176
|
<svg
|
|
@@ -128,25 +192,7 @@
|
|
|
128
192
|
</button>
|
|
129
193
|
|
|
130
194
|
{#if isOpen(item) && !collapsed}
|
|
131
|
-
|
|
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>
|
|
195
|
+
{@render subitems(item)}
|
|
150
196
|
{/if}
|
|
151
197
|
{:else}
|
|
152
198
|
<a
|
|
@@ -154,12 +200,15 @@
|
|
|
154
200
|
onclick={() => go(item)}
|
|
155
201
|
aria-current={value === keyOf(item) ? 'page' : undefined}
|
|
156
202
|
title={collapsed ? item.label : undefined}
|
|
157
|
-
class="{row} {focusRing} {toneRing[
|
|
203
|
+
class="{row} {collapsed ? 'justify-center' : ''} {focusRing} {toneRing[
|
|
204
|
+
tone
|
|
205
|
+
]} {value === keyOf(item)
|
|
158
206
|
? `${toneSoft[tone]} font-medium`
|
|
159
207
|
: 'text-text-secondary hover:bg-bg-inset hover:text-text'} {item.disabled
|
|
160
208
|
? 'pointer-events-none opacity-40'
|
|
161
209
|
: ''}"
|
|
162
210
|
>
|
|
211
|
+
{@render glyph(item)}
|
|
163
212
|
<span class="min-w-0 flex-1 truncate {collapsed ? 'sr-only' : ''}">{item.label}</span>
|
|
164
213
|
{#if item.badge !== undefined && !collapsed}
|
|
165
214
|
<span class="shrink-0 text-[11px] text-text-muted tabular-nums">{item.badge}</span>
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
export type SidebarVariant = 'plain' | 'filled' | 'floating';
|
|
1
3
|
export interface SidebarItem {
|
|
2
4
|
/** Stable id used for the active state. Falls back to `href`. */
|
|
3
5
|
id?: string;
|
|
@@ -5,17 +7,27 @@ export interface SidebarItem {
|
|
|
5
7
|
href?: string;
|
|
6
8
|
disabled?: boolean;
|
|
7
9
|
badge?: string | number;
|
|
10
|
+
/**
|
|
11
|
+
* Leading glyph, 16px. The one thing a collapsed rail still shows, so
|
|
12
|
+
* an item that has to survive `collapsed` needs one.
|
|
13
|
+
*/
|
|
14
|
+
icon?: Snippet;
|
|
8
15
|
/** Nested links — the parent becomes a collapsible group. */
|
|
9
16
|
items?: SidebarItem[];
|
|
10
17
|
/** Open the group on first render. */
|
|
11
18
|
open?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* `false` pins a group open: the parent stops being a button and the
|
|
21
|
+
* children are always on show. For navigation a reader should not be
|
|
22
|
+
* able to lose.
|
|
23
|
+
*/
|
|
24
|
+
collapsible?: boolean;
|
|
12
25
|
}
|
|
13
26
|
export interface SidebarSection {
|
|
14
27
|
/** Small heading above a run of items. */
|
|
15
28
|
label?: string;
|
|
16
29
|
items: SidebarItem[];
|
|
17
30
|
}
|
|
18
|
-
import type { Snippet } from 'svelte';
|
|
19
31
|
import { type Tone } from '../../core/tones.js';
|
|
20
32
|
interface Props {
|
|
21
33
|
sections: SidebarSection[];
|
|
@@ -25,6 +37,8 @@ interface Props {
|
|
|
25
37
|
collapsed?: boolean;
|
|
26
38
|
/** Show the collapse button. */
|
|
27
39
|
collapsible?: boolean;
|
|
40
|
+
/** Surface treatment: bare, the framed default, or a detached card. */
|
|
41
|
+
variant?: SidebarVariant;
|
|
28
42
|
tone?: Tone;
|
|
29
43
|
header?: Snippet;
|
|
30
44
|
footer?: Snippet;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Optional:
|
|
3
|
-
* only if your project doesn't
|
|
4
|
-
*
|
|
5
|
-
* harder and colder.
|
|
2
|
+
* Optional: the two faces paper is designed around — Plus Jakarta Sans
|
|
3
|
+
* (headings) and Work Sans (body). Import this only if your project doesn't
|
|
4
|
+
* already self-host or load them another way — without it the tokens fall back
|
|
5
|
+
* to the system stack, which works but reads harder and colder.
|
|
6
6
|
*
|
|
7
7
|
* These are plain `@font-face` rules rather than an `@import` of the Google
|
|
8
8
|
* stylesheet on purpose: `@import` is only valid before every other rule, so a
|
|
@@ -11,13 +11,13 @@
|
|
|
11
11
|
* you put it, and it skips the CSS-to-CSS round trip before the font files
|
|
12
12
|
* start downloading.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* around 49 kB in total. Add
|
|
14
|
+
* Four rules for two families: both are variable fonts, so one file per subset
|
|
15
|
+
* covers every weight from 400 to 700. Latin and Latin Extended only. Add
|
|
17
16
|
* `<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>` to
|
|
18
17
|
* your document head to open the connection earlier.
|
|
19
18
|
*/
|
|
20
19
|
|
|
20
|
+
/* --- Plus Jakarta Sans --- */
|
|
21
21
|
/* latin */
|
|
22
22
|
@font-face {
|
|
23
23
|
font-family: 'Plus Jakarta Sans';
|
|
@@ -44,3 +44,30 @@
|
|
|
44
44
|
U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
|
|
45
45
|
U+A720-A7FF;
|
|
46
46
|
}
|
|
47
|
+
|
|
48
|
+
/* --- Work Sans --- */
|
|
49
|
+
/* latin */
|
|
50
|
+
@font-face {
|
|
51
|
+
font-family: 'Work Sans';
|
|
52
|
+
font-style: normal;
|
|
53
|
+
font-weight: 400 700;
|
|
54
|
+
font-display: swap;
|
|
55
|
+
src: url(https://fonts.gstatic.com/s/worksans/v24/QGYsz_wNahGAdqQ43Rh_fKDp.woff2) format('woff2');
|
|
56
|
+
unicode-range:
|
|
57
|
+
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
|
|
58
|
+
U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* latin-ext */
|
|
62
|
+
@font-face {
|
|
63
|
+
font-family: 'Work Sans';
|
|
64
|
+
font-style: normal;
|
|
65
|
+
font-weight: 400 700;
|
|
66
|
+
font-display: swap;
|
|
67
|
+
src: url(https://fonts.gstatic.com/s/worksans/v24/QGYsz_wNahGAdqQ43Rh_cqDpp_k.woff2)
|
|
68
|
+
format('woff2');
|
|
69
|
+
unicode-range:
|
|
70
|
+
U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329,
|
|
71
|
+
U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
|
|
72
|
+
U+A720-A7FF;
|
|
73
|
+
}
|
|
@@ -76,7 +76,7 @@ export type { StepItem } from './Steps.svelte';
|
|
|
76
76
|
export { default as Navbar } from './Navbar.svelte';
|
|
77
77
|
export type { NavItem } from './Navbar.svelte';
|
|
78
78
|
export { default as Sidebar } from './Sidebar.svelte';
|
|
79
|
-
export type { SidebarItem, SidebarSection } from './Sidebar.svelte';
|
|
79
|
+
export type { SidebarVariant, SidebarItem, SidebarSection } from './Sidebar.svelte';
|
|
80
80
|
export { default as Footer } from './Footer.svelte';
|
|
81
81
|
export type { FooterLink, FooterColumn } from './Footer.svelte';
|
|
82
82
|
export { default as HeroSection } from './HeroSection.svelte';
|
|
@@ -78,16 +78,19 @@
|
|
|
78
78
|
|
|
79
79
|
/* --- type --- */
|
|
80
80
|
/*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
81
|
+
* Work Sans sets running text in every style, so a project that swaps the
|
|
82
|
+
* word after `@nqmcreative/ui/` keeps the same reading rhythm and only the
|
|
83
|
+
* headings change voice.
|
|
84
84
|
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
85
|
+
* Plus Jakarta Sans keeps the headings: rounded terminals, open counters,
|
|
86
|
+
* low contrast — softer than a system stack, which lands on Segoe UI for
|
|
87
|
+
* most readers and gives paper's generous spacing nothing warm to sit in.
|
|
88
|
+
*
|
|
89
|
+
* Both are variable files, one request per subset — see `fonts.css`.
|
|
90
|
+
* Everything degrades to the system stack if that file is not imported, so
|
|
91
|
+
* the webfonts stay optional.
|
|
88
92
|
*/
|
|
89
|
-
--font-sans:
|
|
90
|
-
'Plus Jakarta Sans', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
|
|
93
|
+
--font-sans: 'Work Sans', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
|
|
91
94
|
--font-heading:
|
|
92
95
|
'Plus Jakarta Sans', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
|
|
93
96
|
--font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace;
|
|
@@ -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;
|