@nqmcreative/ui 0.2.2 → 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.
Files changed (46) hide show
  1. package/dist/core/index.d.ts +1 -1
  2. package/dist/core/index.js +1 -1
  3. package/dist/core/trigger.d.ts +6 -0
  4. package/dist/core/trigger.js +13 -0
  5. package/dist/styles/matte/Button.svelte +4 -4
  6. package/dist/styles/matte/Card.svelte +3 -3
  7. package/dist/styles/matte/Combobox.svelte +2 -2
  8. package/dist/styles/matte/CommandPalette.svelte +32 -7
  9. package/dist/styles/matte/CommandPalette.svelte.d.ts +5 -0
  10. package/dist/styles/matte/DatePicker.svelte +2 -2
  11. package/dist/styles/matte/Drawer.svelte +2 -2
  12. package/dist/styles/matte/Dropzone.svelte +1 -1
  13. package/dist/styles/matte/FeatureCard.svelte +1 -1
  14. package/dist/styles/matte/Input.svelte +4 -4
  15. package/dist/styles/matte/MenuItem.svelte +1 -1
  16. package/dist/styles/matte/Modal.svelte +3 -3
  17. package/dist/styles/matte/NumberInput.svelte +2 -2
  18. package/dist/styles/matte/PasswordInput.svelte +2 -2
  19. package/dist/styles/matte/PricingCard.svelte +3 -3
  20. package/dist/styles/matte/SegmentedControl.svelte +1 -1
  21. package/dist/styles/matte/Select.svelte +1 -1
  22. package/dist/styles/matte/Sidebar.svelte +75 -26
  23. package/dist/styles/matte/Sidebar.svelte.d.ts +15 -1
  24. package/dist/styles/matte/StatsBand.svelte +1 -1
  25. package/dist/styles/matte/Table.svelte +3 -3
  26. package/dist/styles/matte/Testimonial.svelte +2 -2
  27. package/dist/styles/matte/Textarea.svelte +1 -1
  28. package/dist/styles/matte/index.d.ts +1 -1
  29. package/dist/styles/paper/CommandPalette.svelte +25 -2
  30. package/dist/styles/paper/CommandPalette.svelte.d.ts +5 -0
  31. package/dist/styles/paper/Modal.svelte +9 -1
  32. package/dist/styles/paper/Sidebar.svelte +75 -26
  33. package/dist/styles/paper/Sidebar.svelte.d.ts +15 -1
  34. package/dist/styles/paper/fonts.css +34 -7
  35. package/dist/styles/paper/index.d.ts +1 -1
  36. package/dist/styles/paper/theme.css +11 -8
  37. package/dist/styles/sprout/CommandPalette.svelte +25 -2
  38. package/dist/styles/sprout/CommandPalette.svelte.d.ts +5 -0
  39. package/dist/styles/sprout/Modal.svelte +9 -1
  40. package/dist/styles/sprout/Sidebar.svelte +75 -26
  41. package/dist/styles/sprout/Sidebar.svelte.d.ts +15 -1
  42. package/dist/styles/sprout/fonts.css +7 -8
  43. package/dist/styles/sprout/index.d.ts +1 -1
  44. package/dist/styles/sprout/theme.css +6 -6
  45. package/package.json +1 -1
  46. package/registry.json +2 -1
@@ -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
- opened[keyOf(item)] ?? (item.open || item.items!.some((child) => keyOf(child) === value));
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-2xl border border-hairline bg-bg-alt shadow-sm'
96
+ };
97
+
74
98
  const row =
75
99
  'flex w-full items-center gap-3 rounded-xl 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 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}"
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
- <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>
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[tone]} {value === keyOf(item)
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,6 +1,6 @@
1
1
  /*
2
2
  * Optional: the two faces sprout is designed around — Fredoka (headings) and
3
- * Plus Jakarta Sans (body). Import this only if your project doesn't already
3
+ * Work Sans (body). Import this only if your project doesn't already
4
4
  * self-host or load them another way — without it the tokens fall back to the
5
5
  * system stack, which works but loses the round, hand-drawn warmth the style is
6
6
  * built on.
@@ -14,7 +14,7 @@
14
14
  *
15
15
  * Four rules for two families: both are variable fonts, so one file per subset
16
16
  * covers every weight the style uses — 400 to 600 for Fredoka, 400 to 700 for
17
- * Plus Jakarta Sans. Latin and Latin Extended only, around 90 kB in total. Add
17
+ * Work Sans. Latin and Latin Extended only. Add
18
18
  * `<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>` to
19
19
  * your document head to open the connection earlier.
20
20
  */
@@ -49,15 +49,14 @@
49
49
  U+A720-A7FF;
50
50
  }
51
51
 
52
- /* --- Plus Jakarta Sans --- */
52
+ /* --- Work Sans --- */
53
53
  /* latin */
54
54
  @font-face {
55
- font-family: 'Plus Jakarta Sans';
55
+ font-family: 'Work Sans';
56
56
  font-style: normal;
57
57
  font-weight: 400 700;
58
58
  font-display: swap;
59
- src: url(https://fonts.gstatic.com/s/plusjakartasans/v12/LDIoaomQNQcsA88c7O9yZ4KMCoOg4Ko20yw.woff2)
60
- format('woff2');
59
+ src: url(https://fonts.gstatic.com/s/worksans/v24/QGYsz_wNahGAdqQ43Rh_fKDp.woff2) format('woff2');
61
60
  unicode-range:
62
61
  U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
63
62
  U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
@@ -65,11 +64,11 @@
65
64
 
66
65
  /* latin-ext */
67
66
  @font-face {
68
- font-family: 'Plus Jakarta Sans';
67
+ font-family: 'Work Sans';
69
68
  font-style: normal;
70
69
  font-weight: 400 700;
71
70
  font-display: swap;
72
- src: url(https://fonts.gstatic.com/s/plusjakartasans/v12/LDIoaomQNQcsA88c7O9yZ4KMCoOg4Ko40yyygA.woff2)
71
+ src: url(https://fonts.gstatic.com/s/worksans/v24/QGYsz_wNahGAdqQ43Rh_cqDpp_k.woff2)
73
72
  format('woff2');
74
73
  unicode-range:
75
74
  U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329,
@@ -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';
@@ -102,18 +102,18 @@
102
102
  /* --- type --- */
103
103
  /*
104
104
  * Two faces, and the split is the point. Fredoka sets headings: geometric,
105
- * round-terminalled, drawn wide — the same warmth the shapes have. Plus
106
- * Jakarta Sans sets everything a reader has to get through, because Fredoka
107
- * at 15px in a paragraph is charm at the cost of the sentence.
105
+ * round-terminalled, drawn wide — the same warmth the shapes have. Work
106
+ * Sans sets everything a reader has to get through, because Fredoka at 15px
107
+ * in a paragraph is charm at the cost of the sentence — and it is the body
108
+ * face in every style, so only the headings change voice when you swap.
108
109
  *
109
110
  * Both are variable files, one request per subset — see `fonts.css`.
110
111
  * Everything degrades to the system stack if that file is not imported, so
111
112
  * the webfonts stay optional.
112
113
  */
113
- --font-sans:
114
- 'Plus Jakarta Sans', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
114
+ --font-sans: 'Work Sans', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
115
115
  --font-heading:
116
- 'Fredoka', 'Plus Jakarta Sans', ui-rounded, ui-sans-serif, system-ui, -apple-system, sans-serif;
116
+ 'Fredoka', 'Work Sans', ui-rounded, ui-sans-serif, system-ui, -apple-system, sans-serif;
117
117
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace;
118
118
 
119
119
  /* --- motion --- */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nqmcreative/ui",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Svelte 5 + Tailwind CSS v4 component library — three styles (matte, paper, sprout), 65 components each, one shared behaviour core. No runtime dependencies.",
5
5
  "scripts": {
6
6
  "dev": "vite dev",
package/registry.json CHANGED
@@ -942,7 +942,8 @@
942
942
  "dialog",
943
943
  "list",
944
944
  "locale",
945
- "tones"
945
+ "tones",
946
+ "trigger"
946
947
  ]
947
948
  },
948
949
  {