@lyeve-labs/ui-kit 0.36.0 → 0.38.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.
Files changed (32) hide show
  1. package/README.md +147 -0
  2. package/dist/components/AuthShell.svelte +25 -2
  3. package/dist/components/AuthShell.svelte.d.ts +13 -0
  4. package/dist/components/CopyButton.svelte +58 -15
  5. package/dist/components/CopyButton.svelte.d.ts +17 -1
  6. package/dist/components/CopyField.svelte +58 -15
  7. package/dist/components/CopyField.svelte.d.ts +10 -0
  8. package/dist/components/EmptyState.svelte +14 -1
  9. package/dist/components/EmptyState.svelte.d.ts +6 -0
  10. package/dist/components/FilterChips.svelte +132 -26
  11. package/dist/components/FilterChips.svelte.d.ts +28 -1
  12. package/dist/components/Input.svelte +9 -1
  13. package/dist/components/Input.svelte.d.ts +6 -0
  14. package/dist/components/NumberInput.svelte +7 -1
  15. package/dist/components/PageShell.svelte +28 -1
  16. package/dist/components/PageShell.svelte.d.ts +15 -1
  17. package/dist/components/RadioGroup.svelte +19 -4
  18. package/dist/components/RadioGroup.svelte.d.ts +11 -0
  19. package/dist/components/SegmentedControl.svelte +62 -7
  20. package/dist/components/SegmentedControl.svelte.d.ts +18 -0
  21. package/dist/components/Select.svelte +9 -1
  22. package/dist/components/Select.svelte.d.ts +6 -0
  23. package/dist/components/SidebarNav.svelte +8 -1
  24. package/dist/index.d.ts +4 -1
  25. package/dist/index.js +9 -1
  26. package/dist/internal/copy.svelte.d.ts +13 -2
  27. package/dist/internal/copy.svelte.js +13 -3
  28. package/dist/internal/document-title.d.ts +11 -0
  29. package/dist/internal/document-title.js +28 -0
  30. package/dist/internal/field.d.ts +8 -0
  31. package/dist/internal/field.js +10 -0
  32. package/package.json +1 -1
@@ -53,6 +53,7 @@
53
53
  FIELD_WRAP,
54
54
  controlBorder,
55
55
  describedBy,
56
+ joinIds,
56
57
  } from '../internal/field.js';
57
58
  import { createListbox } from '../internal/listbox.svelte.js';
58
59
  import {
@@ -75,6 +76,12 @@
75
76
  required?: boolean;
76
77
  disabled?: boolean;
77
78
  error?: string;
79
+ /**
80
+ * Ids of other elements that describe the control, separated by spaces,
81
+ * such as the message a wrapping Field renders. They are read after the
82
+ * control's own hint or error.
83
+ */
84
+ describedBy?: string;
78
85
  class?: string;
79
86
  /** The rows, as data. */
80
87
  options?: SelectOption[];
@@ -102,6 +109,7 @@
102
109
  required = false,
103
110
  disabled = false,
104
111
  error,
112
+ describedBy: callerIds,
105
113
  class: cls = '',
106
114
  options = [],
107
115
  searchable = false,
@@ -312,7 +320,7 @@
312
320
  }}
313
321
  aria-invalid={error ? 'true' : undefined}
314
322
  aria-required={required ? 'true' : undefined}
315
- aria-describedby={describedBy(fieldId, error, hint)}
323
+ aria-describedby={joinIds(describedBy(fieldId, error, hint), callerIds)}
316
324
  {...box.triggerAttrs}
317
325
  class="{CONTROL_BASE} {controlBorder(!!error)} flex cursor-pointer items-center gap-2 pe-8
318
326
  text-start"
@@ -53,6 +53,12 @@ interface Props {
53
53
  required?: boolean;
54
54
  disabled?: boolean;
55
55
  error?: string;
56
+ /**
57
+ * Ids of other elements that describe the control, separated by spaces,
58
+ * such as the message a wrapping Field renders. They are read after the
59
+ * control's own hint or error.
60
+ */
61
+ describedBy?: string;
56
62
  class?: string;
57
63
  /** The rows, as data. */
58
64
  options?: SelectOption[];
@@ -166,7 +166,14 @@
166
166
  {@render body(node)}
167
167
  </span>
168
168
  {:else if node.href && branch}
169
- <div class="flex items-center gap-1">
169
+ <!--
170
+ The rail stacks the disclosure under the link, under a mouse and under
171
+ a finger alike, so the link keeps the rail's width. Side by side the two
172
+ did not fit. Under a finger the pair took 92px and scrolled the nav
173
+ sideways. Under a mouse the link shrank to its own padding with its icon
174
+ under the chevron, and a rail with a gutter scrolled as well.
175
+ -->
176
+ <div class={collapsed ? 'flex flex-col items-start gap-1' : 'flex items-center gap-1'}>
170
177
  <a
171
178
  href={node.href}
172
179
  aria-current={current ? 'page' : undefined}
package/dist/index.d.ts CHANGED
@@ -25,6 +25,7 @@ export type { CellFit } from './components/Table.svelte';
25
25
  export type { SidebarState } from './components/AppShell.svelte';
26
26
  export type { PageWidth, SectionVariant } from './internal/layout.js';
27
27
  export type { TreeNode, TriState } from './internal/tree.js';
28
+ export { setDocumentTitleFormat, type DocumentTitleFormat } from './internal/document-title.js';
28
29
  export { default as Button } from './components/Button.svelte';
29
30
  export { default as ButtonGroup } from './components/ButtonGroup.svelte';
30
31
  export { default as Input } from './components/Input.svelte';
@@ -79,10 +80,12 @@ export { default as CheckoutSteps } from './components/CheckoutSteps.svelte';
79
80
  export { default as PluginCart } from './components/PluginCart.svelte';
80
81
  export type { QuickPick } from './components/PluginCart.svelte';
81
82
  export { billedCount, formatCents, ladderRows, ladderState, type BillingPeriod, type CartCatalog, type CartPlugin, type CartQuote, type CartQuoteItem, type DiscountBracket, type LadderState, } from './internal/pricing.js';
83
+ export type { AccentTone, StatusTone } from './internal/tone.js';
82
84
  export { default as Stat } from './components/Stat.svelte';
83
85
  export { default as Kbd } from './components/Kbd.svelte';
84
86
  export { default as CopyButton } from './components/CopyButton.svelte';
85
87
  export { default as CopyField } from './components/CopyField.svelte';
88
+ export { CopyState, copyToClipboard, type CopyStatus } from './internal/copy.svelte.js';
86
89
  export { default as Avatar } from './components/Avatar.svelte';
87
90
  export { default as AvatarGroup } from './components/AvatarGroup.svelte';
88
91
  export { default as Logo } from './components/Logo.svelte';
@@ -100,4 +103,4 @@ export { cn, type ClassValue } from './utils/cn.js';
100
103
  export { getTheme, getThemePreference, nextThemePreference, resolveTheme, setTheme, setThemePreference, systemTheme, themeBootScript, watchThemePreference, toggleTheme, watchSystemTheme, THEME_PREFERENCES, type Theme, type ThemePreference, } from './utils/theme.js';
101
104
  export * as motion from './motion.js';
102
105
  export type { Rung as MotionRung, Curve as MotionCurve } from './motion.js';
103
- export declare const VERSION = "0.36.0";
106
+ export declare const VERSION = "0.38.0";
package/dist/index.js CHANGED
@@ -22,6 +22,10 @@ export { default as Table } from './components/Table.svelte';
22
22
  export { default as DescriptionList } from './components/DescriptionList.svelte';
23
23
  export { default as Toolbar } from './components/Toolbar.svelte';
24
24
  export { default as TreeView } from './components/TreeView.svelte';
25
+ // Stated once in an app's root layout, the format turns every PageShell's
26
+ // title into the browser tab's. An app that never states one gets no title
27
+ // element from the kit.
28
+ export { setDocumentTitleFormat } from './internal/document-title.js';
25
29
  // ── Forms & inputs ─────────────────────────────────────────────────────────
26
30
  export { default as Button } from './components/Button.svelte';
27
31
  export { default as ButtonGroup } from './components/ButtonGroup.svelte';
@@ -79,6 +83,10 @@ export { default as Stat } from './components/Stat.svelte';
79
83
  export { default as Kbd } from './components/Kbd.svelte';
80
84
  export { default as CopyButton } from './components/CopyButton.svelte';
81
85
  export { default as CopyField } from './components/CopyField.svelte';
86
+ // The clipboard write and the confirmation both copy controls run on, for a
87
+ // control the kit does not ship. A menu item that copies then falls back the
88
+ // same way on an insecure origin and confirms on the same clock.
89
+ export { CopyState, copyToClipboard } from './internal/copy.svelte.js';
82
90
  // ── Media ──────────────────────────────────────────────────────────────────
83
91
  export { default as Avatar } from './components/Avatar.svelte';
84
92
  export { default as AvatarGroup } from './components/AvatarGroup.svelte';
@@ -108,4 +116,4 @@ export * as motion from './motion.js';
108
116
  // ── Version ────────────────────────────────────────────────────────────────
109
117
  // Generated from package.json by `pnpm version:sync`. Bump package.json, never
110
118
  // this line. The build and the test suite fail when the two disagree.
111
- export const VERSION = '0.36.0';
119
+ export const VERSION = '0.38.0';
@@ -3,7 +3,10 @@
3
3
  *
4
4
  * Both controls report the same three states on the same clock, so a value
5
5
  * copied from a field and one copied from a code block confirm the same way.
6
+ * The package entry point exports the write and the state too, so a control
7
+ * the kit does not ship confirms the same way as well.
6
8
  */
9
+ /** Where the last copy stands: at rest, confirmed, or refused. */
7
10
  export type CopyStatus = 'idle' | 'copied' | 'failed';
8
11
  /** How long the confirmation holds before the control returns to rest. */
9
12
  export declare const COPY_REVERT_MS = 1500;
@@ -16,9 +19,17 @@ export declare const COPY_FAILED_MESSAGE = "Copy failed";
16
19
  * writeText rejects while the document is not focused. Either way the value
17
20
  * goes through a selected, off-screen textarea and the legacy copy command,
18
21
  * which the browser still honors inside the click. Only when that also
19
- * refuses is it a failure the control reports. It never throws.
22
+ * refuses does it resolve to false. It never throws.
23
+ */
24
+ export declare function copyToClipboard(value: string): Promise<boolean>;
25
+ /**
26
+ * A copy and its outcome, on the clock CopyButton and CopyField confirm on.
27
+ *
28
+ * `status` reads `copied` or `failed` for 1.5 seconds after `copy` and `idle`
29
+ * otherwise. It is reactive, so markup that reads it follows it. Create one
30
+ * per control while the component initializes, and call `dispose` when that
31
+ * component unmounts, so the revert timer cannot fire after it.
20
32
  */
21
- export declare function writeClipboard(value: string): Promise<boolean>;
22
33
  export declare class CopyState {
23
34
  #private;
24
35
  status: CopyStatus;
@@ -3,6 +3,8 @@
3
3
  *
4
4
  * Both controls report the same three states on the same clock, so a value
5
5
  * copied from a field and one copied from a code block confirm the same way.
6
+ * The package entry point exports the write and the state too, so a control
7
+ * the kit does not ship confirms the same way as well.
6
8
  */
7
9
  /** How long the confirmation holds before the control returns to rest. */
8
10
  export const COPY_REVERT_MS = 1500;
@@ -15,9 +17,9 @@ export const COPY_FAILED_MESSAGE = 'Copy failed';
15
17
  * writeText rejects while the document is not focused. Either way the value
16
18
  * goes through a selected, off-screen textarea and the legacy copy command,
17
19
  * which the browser still honors inside the click. Only when that also
18
- * refuses is it a failure the control reports. It never throws.
20
+ * refuses does it resolve to false. It never throws.
19
21
  */
20
- export async function writeClipboard(value) {
22
+ export async function copyToClipboard(value) {
21
23
  const clipboard = typeof navigator === 'undefined' ? undefined : navigator.clipboard;
22
24
  if (clipboard) {
23
25
  try {
@@ -50,6 +52,14 @@ function legacyCopy(value) {
50
52
  area.remove();
51
53
  }
52
54
  }
55
+ /**
56
+ * A copy and its outcome, on the clock CopyButton and CopyField confirm on.
57
+ *
58
+ * `status` reads `copied` or `failed` for 1.5 seconds after `copy` and `idle`
59
+ * otherwise. It is reactive, so markup that reads it follows it. Create one
60
+ * per control while the component initializes, and call `dispose` when that
61
+ * component unmounts, so the revert timer cannot fire after it.
62
+ */
53
63
  export class CopyState {
54
64
  status = $state('idle');
55
65
  #timer;
@@ -57,7 +67,7 @@ export class CopyState {
57
67
  // A second press restarts the window. Without this the first press's timer
58
68
  // reverts the second copy part way through its own.
59
69
  clearTimeout(this.#timer);
60
- this.status = (await writeClipboard(value)) ? 'copied' : 'failed';
70
+ this.status = (await copyToClipboard(value)) ? 'copied' : 'failed';
61
71
  this.#timer = setTimeout(() => (this.status = 'idle'), COPY_REVERT_MS);
62
72
  }
63
73
  dispose() {
@@ -0,0 +1,11 @@
1
+ /** Turns a page's name into the words its browser tab shows. */
2
+ export type DocumentTitleFormat = (title: string) => string;
3
+ /**
4
+ * States the format every PageShell below the calling component renders its
5
+ * document title in. Call it while a layout initializes, the way any context
6
+ * is set. A format that reads reactive state is run again when that state
7
+ * changes.
8
+ */
9
+ export declare function setDocumentTitleFormat(format: DocumentTitleFormat): void;
10
+ /** The format a layout above stated, or undefined when none did. */
11
+ export declare function getDocumentTitleFormat(): DocumentTitleFormat | undefined;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The words a browser tab shows for a page, stated once for a whole app.
3
+ *
4
+ * A console rendered its own title element on every page, and each page spelled
5
+ * the suffix after the page name for itself. PageShell already holds the page's
6
+ * name, so an app that states the format once in its root layout gets every
7
+ * PageShell page titled the same way. An app that states none gets no title
8
+ * from the kit at all, and its pages keep whatever they render themselves.
9
+ *
10
+ * Context rather than a module variable: one server process renders pages for
11
+ * every visitor, and a format that reads the request (a tenant's name, say)
12
+ * would otherwise leak from one render into the next.
13
+ */
14
+ import { getContext, setContext } from 'svelte';
15
+ const FORMAT = Symbol('document-title-format');
16
+ /**
17
+ * States the format every PageShell below the calling component renders its
18
+ * document title in. Call it while a layout initializes, the way any context
19
+ * is set. A format that reads reactive state is run again when that state
20
+ * changes.
21
+ */
22
+ export function setDocumentTitleFormat(format) {
23
+ setContext(FORMAT, format);
24
+ }
25
+ /** The format a layout above stated, or undefined when none did. */
26
+ export function getDocumentTitleFormat() {
27
+ return getContext(FORMAT);
28
+ }
@@ -105,3 +105,11 @@ export declare function segmentedBorder(error: boolean): string;
105
105
  * by the wrapper's own rounding.
106
106
  */
107
107
  export declare const CONTROL_SEGMENT: string;
108
+ /**
109
+ * Joins a control's own message id with the ids its caller names.
110
+ *
111
+ * A control inside a Field, or explained by text elsewhere on the page, has to
112
+ * point at that text as well as at its own hint or error. Returns undefined
113
+ * when there is nothing to point at, for the reason describedBy does.
114
+ */
115
+ export declare function joinIds(...ids: (string | undefined)[]): string | undefined;
@@ -134,3 +134,13 @@ export function segmentedBorder(error) {
134
134
  export const CONTROL_SEGMENT = 'shrink-0 rounded-sm border-0 bg-transparent p-0 text-center text-sm text-fg tabular-nums ' +
135
135
  'outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand ' +
136
136
  'disabled:cursor-not-allowed';
137
+ /**
138
+ * Joins a control's own message id with the ids its caller names.
139
+ *
140
+ * A control inside a Field, or explained by text elsewhere on the page, has to
141
+ * point at that text as well as at its own hint or error. Returns undefined
142
+ * when there is nothing to point at, for the reason describedBy does.
143
+ */
144
+ export function joinIds(...ids) {
145
+ return ids.filter(Boolean).join(' ') || undefined;
146
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lyeve-labs/ui-kit",
3
- "version": "0.36.0",
3
+ "version": "0.38.0",
4
4
  "description": "A clean, accessible, palette-aware Svelte 5 component library. The design system behind LyEve.",
5
5
  "license": "MIT",
6
6
  "author": "LyEve Labs <hello@lyeve.com>",