@loidolt/theme-svelte 0.2.0 → 0.4.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 (92) hide show
  1. package/README.md +21 -0
  2. package/dist/components/Accordion.svelte +100 -0
  3. package/dist/components/Accordion.svelte.d.ts +50 -0
  4. package/dist/components/Alert.svelte +6 -3
  5. package/dist/components/Alert.svelte.d.ts +3 -2
  6. package/dist/components/AlertDialog.svelte +130 -0
  7. package/dist/components/AlertDialog.svelte.d.ts +35 -0
  8. package/dist/components/AppShell.svelte +2 -1
  9. package/dist/components/AppShell.svelte.d.ts +1 -0
  10. package/dist/components/Badge.svelte +9 -2
  11. package/dist/components/Badge.svelte.d.ts +2 -1
  12. package/dist/components/Brand.svelte +29 -15
  13. package/dist/components/Brand.svelte.d.ts +8 -4
  14. package/dist/components/Breadcrumbs.svelte +53 -0
  15. package/dist/components/Breadcrumbs.svelte.d.ts +18 -0
  16. package/dist/components/Button.svelte +43 -21
  17. package/dist/components/Button.svelte.d.ts +9 -4
  18. package/dist/components/Card.svelte +23 -5
  19. package/dist/components/Card.svelte.d.ts +11 -4
  20. package/dist/components/CodeBlock.svelte +55 -0
  21. package/dist/components/CodeBlock.svelte.d.ts +14 -0
  22. package/dist/components/CommentList.svelte +67 -0
  23. package/dist/components/CommentList.svelte.d.ts +53 -0
  24. package/dist/components/Dialog.svelte +25 -3
  25. package/dist/components/Dialog.svelte.d.ts +4 -1
  26. package/dist/components/Drawer.svelte +105 -0
  27. package/dist/components/Drawer.svelte.d.ts +31 -0
  28. package/dist/components/DropdownMenu.svelte +2 -2
  29. package/dist/components/DropdownMenu.svelte.d.ts +1 -1
  30. package/dist/components/EmptyState.svelte +40 -0
  31. package/dist/components/EmptyState.svelte.d.ts +19 -0
  32. package/dist/components/Fieldset.svelte +60 -0
  33. package/dist/components/Fieldset.svelte.d.ts +19 -0
  34. package/dist/components/FileInput.svelte +47 -0
  35. package/dist/components/FileInput.svelte.d.ts +15 -0
  36. package/dist/components/Filmstrip.svelte +72 -0
  37. package/dist/components/Filmstrip.svelte.d.ts +50 -0
  38. package/dist/components/FloatingBar.svelte +43 -0
  39. package/dist/components/FloatingBar.svelte.d.ts +16 -0
  40. package/dist/components/IconButton.svelte +1 -1
  41. package/dist/components/Input.svelte.d.ts +1 -1
  42. package/dist/components/ListRow.svelte +50 -0
  43. package/dist/components/ListRow.svelte.d.ts +18 -0
  44. package/dist/components/Marker.svelte +38 -0
  45. package/dist/components/Marker.svelte.d.ts +14 -0
  46. package/dist/components/NavMenu.svelte +6 -1
  47. package/dist/components/NavMenu.svelte.d.ts +2 -1
  48. package/dist/components/NumberField.svelte +12 -1
  49. package/dist/components/NumberField.svelte.d.ts +1 -1
  50. package/dist/components/Pagination.svelte +86 -0
  51. package/dist/components/Pagination.svelte.d.ts +22 -0
  52. package/dist/components/Popover.svelte +24 -3
  53. package/dist/components/Popover.svelte.d.ts +4 -1
  54. package/dist/components/RadioGroup.svelte.d.ts +2 -2
  55. package/dist/components/RecordStepper.svelte +58 -0
  56. package/dist/components/RecordStepper.svelte.d.ts +18 -0
  57. package/dist/components/SegmentedNav.svelte +53 -0
  58. package/dist/components/SegmentedNav.svelte.d.ts +22 -0
  59. package/dist/components/Select.svelte.d.ts +2 -2
  60. package/dist/components/Stat.svelte +41 -0
  61. package/dist/components/Stat.svelte.d.ts +17 -0
  62. package/dist/components/StatusDot.svelte +27 -0
  63. package/dist/components/StatusDot.svelte.d.ts +12 -0
  64. package/dist/components/SwatchGroup.svelte +69 -0
  65. package/dist/components/SwatchGroup.svelte.d.ts +44 -0
  66. package/dist/components/Switch.svelte +19 -0
  67. package/dist/components/Table.svelte +30 -3
  68. package/dist/components/Table.svelte.d.ts +13 -0
  69. package/dist/components/TableHeader.svelte +72 -0
  70. package/dist/components/TableHeader.svelte.d.ts +24 -0
  71. package/dist/components/Tabs.svelte +23 -5
  72. package/dist/components/Tabs.svelte.d.ts +4 -3
  73. package/dist/components/Textarea.svelte.d.ts +1 -1
  74. package/dist/components/ThemeToggle.svelte +133 -0
  75. package/dist/components/ThemeToggle.svelte.d.ts +20 -0
  76. package/dist/components/Thumbnail.svelte +51 -0
  77. package/dist/components/Thumbnail.svelte.d.ts +19 -0
  78. package/dist/components/ToggleGroup.svelte +107 -0
  79. package/dist/components/ToggleGroup.svelte.d.ts +46 -0
  80. package/dist/components/TooltipProvider.svelte +1 -0
  81. package/dist/components/Workspace.svelte +27 -6
  82. package/dist/components/Workspace.svelte.d.ts +12 -0
  83. package/dist/index.d.ts +29 -2
  84. package/dist/index.js +26 -1
  85. package/dist/media.svelte.d.ts +48 -0
  86. package/dist/media.svelte.js +69 -0
  87. package/dist/theme.svelte.d.ts +86 -0
  88. package/dist/theme.svelte.js +166 -0
  89. package/dist/toaster.svelte.d.ts +2 -0
  90. package/dist/toaster.svelte.js +51 -9
  91. package/dist/types.d.ts +21 -0
  92. package/package.json +10 -8
@@ -4,22 +4,32 @@
4
4
  import type { ActionVariant, ControlSize } from '../types.js';
5
5
  import { cx } from '../utils.js';
6
6
 
7
- type Props = HTMLButtonAttributes &
8
- HTMLAnchorAttributes & {
9
- variant?: ActionVariant;
10
- size?: ControlSize;
11
- /** Renders an `<a>` styled as a button. */
12
- href?: string;
13
- disabled?: boolean;
14
- loading?: boolean;
15
- /** Visible text swapped in while loading. */
16
- loadingText?: string;
17
- /** Announced while loading when there is no visible `loadingText`. */
18
- loadingLabel?: string;
19
- class?: string;
20
- children: Snippet;
21
- ref?: HTMLButtonElement | HTMLAnchorElement | null;
22
- };
7
+ interface CommonProps {
8
+ variant?: ActionVariant;
9
+ size?: ControlSize;
10
+ disabled?: boolean;
11
+ loading?: boolean;
12
+ /** Visible text swapped in while loading. */
13
+ loadingText?: string;
14
+ /** Announced while loading when there is no visible `loadingText`. */
15
+ loadingLabel?: string;
16
+ class?: string;
17
+ children: Snippet;
18
+ ref?: HTMLButtonElement | HTMLAnchorElement | null;
19
+ }
20
+
21
+ // Discriminated on `href`: with it the component renders an `<a>` and accepts anchor
22
+ // attributes; without it, a `<button>` and button attributes. Button-only attributes
23
+ // (`formaction`, `type`, …) on an anchor are a type error instead of silent junk.
24
+ type Props = CommonProps &
25
+ (
26
+ | (Omit<HTMLAnchorAttributes, 'class' | 'type'> & {
27
+ /** Renders an `<a>` styled as a button. */
28
+ href: string;
29
+ type?: never;
30
+ })
31
+ | (Omit<HTMLButtonAttributes, 'class'> & { href?: never })
32
+ );
23
33
 
24
34
  let {
25
35
  variant = 'default',
@@ -38,7 +48,12 @@
38
48
  }: Props = $props();
39
49
 
40
50
  const classes = $derived(
41
- cx('ldt-button', `ldt-button--${variant}`, size !== 'md' && `ldt-button--${size}`, className)
51
+ cx(
52
+ 'ldt-button',
53
+ variant !== 'default' && `ldt-button--${variant}`,
54
+ size !== 'md' && `ldt-button--${size}`,
55
+ className
56
+ )
42
57
  );
43
58
  const inert = $derived(disabled || loading);
44
59
 
@@ -47,7 +62,8 @@
47
62
  event.preventDefault();
48
63
  return;
49
64
  }
50
- onclick?.(event as MouseEvent & { currentTarget: EventTarget & HTMLButtonElement });
65
+ // The union collapses the handler's currentTarget; the branch that rendered us fixes it.
66
+ (onclick as ((event: MouseEvent) => void) | undefined)?.(event);
51
67
  }
52
68
  </script>
53
69
 
@@ -69,17 +85,23 @@
69
85
  aria-disabled={inert || undefined}
70
86
  aria-busy={loading || undefined}
71
87
  tabindex={inert ? -1 : undefined}
72
- {...rest}
88
+ {...rest as HTMLAnchorAttributes}
73
89
  onclick={handleClick}>{@render content()}</a
74
90
  >
75
91
  {:else}
92
+ <!--
93
+ `loading` uses `aria-disabled` + a click guard instead of native `disabled`: disabling
94
+ the control mid-interaction would drop keyboard focus to <body> and can hide the busy
95
+ announcement from assistive tech. Explicit `disabled` keeps native semantics.
96
+ -->
76
97
  <button
77
98
  bind:this={ref}
78
99
  {type}
79
100
  class={classes}
80
- disabled={inert}
101
+ disabled={disabled || undefined}
102
+ aria-disabled={loading && !disabled ? true : undefined}
81
103
  aria-busy={loading || undefined}
82
- {...rest}
104
+ {...rest as HTMLButtonAttributes}
83
105
  onclick={handleClick}>{@render content()}</button
84
106
  >
85
107
  {/if}
@@ -1,11 +1,9 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
3
3
  import type { ActionVariant, ControlSize } from '../types.js';
4
- type Props = HTMLButtonAttributes & HTMLAnchorAttributes & {
4
+ interface CommonProps {
5
5
  variant?: ActionVariant;
6
6
  size?: ControlSize;
7
- /** Renders an `<a>` styled as a button. */
8
- href?: string;
9
7
  disabled?: boolean;
10
8
  loading?: boolean;
11
9
  /** Visible text swapped in while loading. */
@@ -15,7 +13,14 @@ type Props = HTMLButtonAttributes & HTMLAnchorAttributes & {
15
13
  class?: string;
16
14
  children: Snippet;
17
15
  ref?: HTMLButtonElement | HTMLAnchorElement | null;
18
- };
16
+ }
17
+ type Props = CommonProps & ((Omit<HTMLAnchorAttributes, 'class' | 'type'> & {
18
+ /** Renders an `<a>` styled as a button. */
19
+ href: string;
20
+ type?: never;
21
+ }) | (Omit<HTMLButtonAttributes, 'class'> & {
22
+ href?: never;
23
+ }));
19
24
  declare const Button: import("svelte").Component<Props, {}, "ref">;
20
25
  type Button = ReturnType<typeof Button>;
21
26
  export default Button;
@@ -1,22 +1,36 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
- import type { HTMLAttributes } from 'svelte/elements';
3
+ import type { HTMLAnchorAttributes, HTMLAttributes } from 'svelte/elements';
4
4
  import type { HeadingLevel } from '../types.js';
5
5
  import { cx } from '../utils.js';
6
6
 
7
- interface Props extends HTMLAttributes<HTMLElement> {
7
+ interface CommonProps {
8
8
  title?: string;
9
9
  description?: string;
10
10
  header?: Snippet;
11
11
  children: Snippet;
12
12
  footer?: Snippet;
13
- /** Root element. `div` by default — use `article` only for self-contained content. */
14
- as?: 'div' | 'article' | 'section' | 'li';
15
13
  headingLevel?: HeadingLevel;
16
14
  class?: string;
17
15
  ref?: HTMLElement | null;
18
16
  }
19
17
 
18
+ // Discriminated on `href`, like Button: with it the whole card is an anchor and accepts
19
+ // anchor attributes; without it the root stays a plain container element.
20
+ type Props = CommonProps &
21
+ (
22
+ | (Omit<HTMLAnchorAttributes, 'class'> & {
23
+ /** Renders the card as one block-level link. */
24
+ href: string;
25
+ as?: never;
26
+ })
27
+ | (Omit<HTMLAttributes<HTMLElement>, 'class'> & {
28
+ href?: never;
29
+ /** Root element. `div` by default — use `article` only for self-contained content. */
30
+ as?: 'div' | 'article' | 'section' | 'li';
31
+ })
32
+ );
33
+
20
34
  let {
21
35
  title,
22
36
  description,
@@ -24,14 +38,18 @@
24
38
  children,
25
39
  footer,
26
40
  as = 'div',
41
+ href,
27
42
  headingLevel = 3,
28
43
  class: className,
29
44
  ref = $bindable(null),
30
45
  ...rest
31
46
  }: Props = $props();
47
+
48
+ const element = $derived(href ? 'a' : as);
49
+ const classes = $derived(cx('ldt-card', href && 'ldt-card--link', className));
32
50
  </script>
33
51
 
34
- <svelte:element this={as} bind:this={ref} class={cx('ldt-card', className)} {...rest}>
52
+ <svelte:element this={element} bind:this={ref} class={classes} {href} {...rest}>
35
53
  {#if header || title || description}<header class="ldt-card__header">
36
54
  {#if header}{@render header()}{:else}{#if title}<svelte:element
37
55
  this={`h${headingLevel}`}
@@ -1,18 +1,25 @@
1
1
  import type { Snippet } from 'svelte';
2
- import type { HTMLAttributes } from 'svelte/elements';
2
+ import type { HTMLAnchorAttributes, HTMLAttributes } from 'svelte/elements';
3
3
  import type { HeadingLevel } from '../types.js';
4
- interface Props extends HTMLAttributes<HTMLElement> {
4
+ interface CommonProps {
5
5
  title?: string;
6
6
  description?: string;
7
7
  header?: Snippet;
8
8
  children: Snippet;
9
9
  footer?: Snippet;
10
- /** Root element. `div` by default — use `article` only for self-contained content. */
11
- as?: 'div' | 'article' | 'section' | 'li';
12
10
  headingLevel?: HeadingLevel;
13
11
  class?: string;
14
12
  ref?: HTMLElement | null;
15
13
  }
14
+ type Props = CommonProps & ((Omit<HTMLAnchorAttributes, 'class'> & {
15
+ /** Renders the card as one block-level link. */
16
+ href: string;
17
+ as?: never;
18
+ }) | (Omit<HTMLAttributes<HTMLElement>, 'class'> & {
19
+ href?: never;
20
+ /** Root element. `div` by default — use `article` only for self-contained content. */
21
+ as?: 'div' | 'article' | 'section' | 'li';
22
+ }));
16
23
  declare const Card: import("svelte").Component<Props, {}, "ref">;
17
24
  type Card = ReturnType<typeof Card>;
18
25
  export default Card;
@@ -0,0 +1,55 @@
1
+ <script lang="ts">
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ import { cx } from '../utils.js';
4
+
5
+ interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
6
+ code: string;
7
+ /** One click/tap selects the whole value. On by default — this block exists for copying. */
8
+ selectable?: boolean;
9
+ copyable?: boolean;
10
+ copyLabel?: string;
11
+ copiedLabel?: string;
12
+ class?: string;
13
+ ref?: HTMLDivElement | null;
14
+ }
15
+
16
+ let {
17
+ code,
18
+ selectable = true,
19
+ copyable = false,
20
+ copyLabel = 'Copy',
21
+ copiedLabel = 'Copied',
22
+ class: className,
23
+ ref = $bindable(null),
24
+ ...rest
25
+ }: Props = $props();
26
+
27
+ let copied = $state(false);
28
+ let resetTimer: ReturnType<typeof setTimeout> | undefined;
29
+
30
+ async function copy() {
31
+ try {
32
+ await navigator.clipboard.writeText(code);
33
+ copied = true;
34
+ clearTimeout(resetTimer);
35
+ resetTimer = setTimeout(() => (copied = false), 2000);
36
+ } catch {
37
+ // Clipboard unavailable (permissions, insecure context) — the text stays selectable.
38
+ }
39
+ }
40
+ </script>
41
+
42
+ <div
43
+ bind:this={ref}
44
+ class={cx('ldt-code-block', selectable && 'ldt-code-block--selectable', className)}
45
+ {...rest}
46
+ >
47
+ <code class="ldt-code-block__code">{code}</code>
48
+ {#if copyable}
49
+ <button type="button" class="ldt-button ldt-button--quiet ldt-button--sm" onclick={copy}>
50
+ {copied ? copiedLabel : copyLabel}
51
+ </button>
52
+ <!-- Persistently mounted so the announcement fires when the content changes. -->
53
+ <span class="ldt-sr-only" role="status">{copied ? copiedLabel : ''}</span>
54
+ {/if}
55
+ </div>
@@ -0,0 +1,14 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
3
+ code: string;
4
+ /** One click/tap selects the whole value. On by default — this block exists for copying. */
5
+ selectable?: boolean;
6
+ copyable?: boolean;
7
+ copyLabel?: string;
8
+ copiedLabel?: string;
9
+ class?: string;
10
+ ref?: HTMLDivElement | null;
11
+ }
12
+ declare const CodeBlock: import("svelte").Component<Props, {}, "ref">;
13
+ type CodeBlock = ReturnType<typeof CodeBlock>;
14
+ export default CodeBlock;
@@ -0,0 +1,67 @@
1
+ <script lang="ts" generics="T extends string = string">
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+ import Marker from './Marker.svelte';
5
+ import { cx } from '../utils.js';
6
+
7
+ interface CommentListItem<V extends string = string> {
8
+ id: V;
9
+ author: string;
10
+ /** Preformatted suffix after the author — timestamps, origins. The theme adds nothing. */
11
+ meta?: string;
12
+ body: string;
13
+ /** Renders a numbered Marker when set — ties the comment to a pin on a canvas. */
14
+ marker?: number;
15
+ }
16
+
17
+ interface Props extends Omit<HTMLAttributes<HTMLUListElement>, 'children'> {
18
+ items: CommentListItem<T>[];
19
+ /** Highlighted item; kept in view as it changes. */
20
+ activeId?: T | null;
21
+ /** Fired when a markered item is hovered — for syncing the matching canvas pin. */
22
+ onItemHover?: (id: T) => void;
23
+ /** Full row-content override. */
24
+ item?: Snippet<[CommentListItem<T>]>;
25
+ class?: string;
26
+ itemClass?: string;
27
+ ref?: HTMLUListElement | null;
28
+ }
29
+
30
+ let {
31
+ items,
32
+ activeId = $bindable(null),
33
+ onItemHover,
34
+ item,
35
+ class: className,
36
+ itemClass,
37
+ ref = $bindable(null),
38
+ ...rest
39
+ }: Props = $props();
40
+
41
+ let rows = $state<Record<string, HTMLLIElement | null>>({});
42
+
43
+ $effect(() => {
44
+ if (activeId) rows[activeId]?.scrollIntoView({ block: 'nearest' });
45
+ });
46
+ </script>
47
+
48
+ <ul bind:this={ref} class={cx('ldt-comment-list', className)} {...rest}>
49
+ {#each items as entry (entry.id)}
50
+ <li
51
+ class={cx('ldt-comment', activeId === entry.id && 'ldt-comment--active', itemClass)}
52
+ bind:this={rows[entry.id]}
53
+ onpointerenter={entry.marker !== undefined ? () => onItemHover?.(entry.id) : undefined}
54
+ >
55
+ {#if item}{@render item(entry)}{:else}
56
+ {#if entry.marker !== undefined}<Marker
57
+ number={entry.marker}
58
+ active={activeId === entry.id}
59
+ />{/if}
60
+ <div class="ldt-comment__content">
61
+ <span class="ldt-comment__meta">{entry.author}{entry.meta ? ` ${entry.meta}` : ''}</span>
62
+ <p class="ldt-comment__body">{entry.body}</p>
63
+ </div>
64
+ {/if}
65
+ </li>
66
+ {/each}
67
+ </ul>
@@ -0,0 +1,53 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ declare function $$render<T extends string = string>(): {
4
+ props: Omit<HTMLAttributes<HTMLUListElement>, "children"> & {
5
+ items: {
6
+ id: T;
7
+ author: string;
8
+ /** Preformatted suffix after the author — timestamps, origins. The theme adds nothing. */
9
+ meta?: string;
10
+ body: string;
11
+ /** Renders a numbered Marker when set — ties the comment to a pin on a canvas. */
12
+ marker?: number;
13
+ }[];
14
+ /** Highlighted item; kept in view as it changes. */
15
+ activeId?: T | null;
16
+ /** Fired when a markered item is hovered — for syncing the matching canvas pin. */
17
+ onItemHover?: (id: T) => void;
18
+ /** Full row-content override. */
19
+ item?: Snippet<[{
20
+ id: T;
21
+ author: string;
22
+ /** Preformatted suffix after the author — timestamps, origins. The theme adds nothing. */
23
+ meta?: string;
24
+ body: string;
25
+ /** Renders a numbered Marker when set — ties the comment to a pin on a canvas. */
26
+ marker?: number;
27
+ }]>;
28
+ class?: string;
29
+ itemClass?: string;
30
+ ref?: HTMLUListElement | null;
31
+ };
32
+ exports: {};
33
+ bindings: "ref" | "activeId";
34
+ slots: {};
35
+ events: {};
36
+ };
37
+ declare class __sveltets_Render<T extends string = string> {
38
+ props(): ReturnType<typeof $$render<T>>['props'];
39
+ events(): ReturnType<typeof $$render<T>>['events'];
40
+ slots(): ReturnType<typeof $$render<T>>['slots'];
41
+ bindings(): "ref" | "activeId";
42
+ exports(): {};
43
+ }
44
+ interface $$IsomorphicComponent {
45
+ new <T extends string = string>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
46
+ $$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
47
+ } & ReturnType<__sveltets_Render<T>['exports']>;
48
+ <T extends string = string>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
49
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
50
+ }
51
+ declare const CommentList: $$IsomorphicComponent;
52
+ type CommentList<T extends string = string> = InstanceType<typeof CommentList<T>>;
53
+ export default CommentList;
@@ -2,7 +2,13 @@
2
2
  import { Dialog as DialogPrimitive } from 'bits-ui';
3
3
  import type { Snippet } from 'svelte';
4
4
  import type { DialogContentProps } from 'bits-ui';
5
- import type { DialogSize, HeadingLevel, TriggerChildProps } from '../types.js';
5
+ import type {
6
+ ActionVariant,
7
+ ControlSize,
8
+ DialogSize,
9
+ HeadingLevel,
10
+ TriggerChildProps,
11
+ } from '../types.js';
6
12
  import { cx } from '../utils.js';
7
13
 
8
14
  type Props = Omit<DialogContentProps, 'children' | 'class' | 'child'> & {
@@ -13,6 +19,9 @@
13
19
  trigger?: Snippet;
14
20
  /** Full control of the trigger element — receives the props Bits needs on it. */
15
21
  triggerChild?: Snippet<[TriggerChildProps]>;
22
+ /** Button variant for the default trigger. An explicit `triggerClass` wins wholesale. */
23
+ triggerVariant?: ActionVariant;
24
+ triggerSize?: ControlSize;
16
25
  triggerClass?: string;
17
26
  children: Snippet;
18
27
  footer?: Snippet;
@@ -34,7 +43,9 @@
34
43
  description,
35
44
  trigger,
36
45
  triggerChild,
37
- triggerClass = 'ldt-button',
46
+ triggerVariant,
47
+ triggerSize,
48
+ triggerClass,
38
49
  children,
39
50
  footer,
40
51
  size,
@@ -47,6 +58,17 @@
47
58
  ref = $bindable(null),
48
59
  ...rest
49
60
  }: Props = $props();
61
+
62
+ // An explicit triggerClass keeps today's contract and wins outright; otherwise the class is
63
+ // composed from the button variant vocabulary.
64
+ const triggerClasses = $derived(
65
+ triggerClass ??
66
+ cx(
67
+ 'ldt-button',
68
+ triggerVariant && triggerVariant !== 'default' && `ldt-button--${triggerVariant}`,
69
+ triggerSize && triggerSize !== 'md' && `ldt-button--${triggerSize}`
70
+ )
71
+ );
50
72
  </script>
51
73
 
52
74
  <DialogPrimitive.Root bind:open {onOpenChange}>
@@ -54,7 +76,7 @@
54
76
  >{#snippet child(childProps)}{@render triggerChild(
55
77
  childProps
56
78
  )}{/snippet}</DialogPrimitive.Trigger
57
- >{:else if trigger}<DialogPrimitive.Trigger class={triggerClass}
79
+ >{:else if trigger}<DialogPrimitive.Trigger class={triggerClasses}
58
80
  >{@render trigger()}</DialogPrimitive.Trigger
59
81
  >{/if}
60
82
  <DialogPrimitive.Portal>
@@ -1,6 +1,6 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  import type { DialogContentProps } from 'bits-ui';
3
- import type { DialogSize, HeadingLevel, TriggerChildProps } from '../types.js';
3
+ import type { ActionVariant, ControlSize, DialogSize, HeadingLevel, TriggerChildProps } from '../types.js';
4
4
  type Props = Omit<DialogContentProps, 'children' | 'class' | 'child'> & {
5
5
  open?: boolean;
6
6
  title: string;
@@ -9,6 +9,9 @@ type Props = Omit<DialogContentProps, 'children' | 'class' | 'child'> & {
9
9
  trigger?: Snippet;
10
10
  /** Full control of the trigger element — receives the props Bits needs on it. */
11
11
  triggerChild?: Snippet<[TriggerChildProps]>;
12
+ /** Button variant for the default trigger. An explicit `triggerClass` wins wholesale. */
13
+ triggerVariant?: ActionVariant;
14
+ triggerSize?: ControlSize;
12
15
  triggerClass?: string;
13
16
  children: Snippet;
14
17
  footer?: Snippet;
@@ -0,0 +1,105 @@
1
+ <script lang="ts">
2
+ import { Dialog as DialogPrimitive } from 'bits-ui';
3
+ import type { Snippet } from 'svelte';
4
+ import type { DialogContentProps } from 'bits-ui';
5
+ import type { ActionVariant, ControlSize, HeadingLevel, TriggerChildProps } from '../types.js';
6
+ import { cx } from '../utils.js';
7
+
8
+ type Props = Omit<DialogContentProps, 'children' | 'class' | 'child'> & {
9
+ open?: boolean;
10
+ title: string;
11
+ description?: string;
12
+ /** Edge the panel is anchored to. `left`/`right` size by width, `top`/`bottom` by height. */
13
+ side?: 'left' | 'right' | 'top' | 'bottom';
14
+ /** Trigger contents, rendered inside a button styled with `triggerClass`. */
15
+ trigger?: Snippet;
16
+ /** Full control of the trigger element — receives the props Bits needs on it. */
17
+ triggerChild?: Snippet<[TriggerChildProps]>;
18
+ /** Button variant for the default trigger. An explicit `triggerClass` wins wholesale. */
19
+ triggerVariant?: ActionVariant;
20
+ triggerSize?: ControlSize;
21
+ triggerClass?: string;
22
+ children: Snippet;
23
+ footer?: Snippet;
24
+ headingLevel?: HeadingLevel;
25
+ showClose?: boolean;
26
+ closeLabel?: string;
27
+ /** `class` lands on the portaled Content element. */
28
+ class?: string;
29
+ overlayClass?: string;
30
+ onOpenChange?: (open: boolean) => void;
31
+ ref?: HTMLElement | null;
32
+ };
33
+
34
+ let {
35
+ open = $bindable(false),
36
+ title,
37
+ description,
38
+ side = 'left',
39
+ trigger,
40
+ triggerChild,
41
+ triggerVariant,
42
+ triggerSize,
43
+ triggerClass,
44
+ children,
45
+ footer,
46
+ headingLevel = 2,
47
+ showClose = true,
48
+ closeLabel = 'Close',
49
+ class: className,
50
+ overlayClass,
51
+ onOpenChange,
52
+ ref = $bindable(null),
53
+ ...rest
54
+ }: Props = $props();
55
+
56
+ // An explicit triggerClass keeps today's contract and wins outright.
57
+ const triggerClasses = $derived(
58
+ triggerClass ??
59
+ cx(
60
+ 'ldt-button',
61
+ triggerVariant && triggerVariant !== 'default' && `ldt-button--${triggerVariant}`,
62
+ triggerSize && triggerSize !== 'md' && `ldt-button--${triggerSize}`
63
+ )
64
+ );
65
+ </script>
66
+
67
+ <!--
68
+ A modal panel anchored to one edge — the narrow-viewport form of a sidebar, and the usual home
69
+ for navigation that has no room to sit in the topbar. Same primitive as `Dialog`, so focus is
70
+ trapped, Escape closes, and the page behind is inert; set `--ldt-drawer-size` for the measure
71
+ across the anchored edge.
72
+ -->
73
+ <DialogPrimitive.Root bind:open {onOpenChange}>
74
+ {#if triggerChild}<DialogPrimitive.Trigger
75
+ >{#snippet child(childProps)}{@render triggerChild(
76
+ childProps
77
+ )}{/snippet}</DialogPrimitive.Trigger
78
+ >{:else if trigger}<DialogPrimitive.Trigger class={triggerClasses}
79
+ >{@render trigger()}</DialogPrimitive.Trigger
80
+ >{/if}
81
+ <DialogPrimitive.Portal>
82
+ <DialogPrimitive.Overlay class={cx('ldt-overlay', overlayClass)} />
83
+ <DialogPrimitive.Content
84
+ bind:ref
85
+ class={cx('ldt-drawer', `ldt-drawer--${side}`, className)}
86
+ {...rest}
87
+ >
88
+ <header class="ldt-drawer__header">
89
+ <div>
90
+ <DialogPrimitive.Title level={headingLevel} class="ldt-drawer__title"
91
+ >{title}</DialogPrimitive.Title
92
+ >{#if description}<DialogPrimitive.Description class="ldt-drawer__description"
93
+ >{description}</DialogPrimitive.Description
94
+ >{/if}
95
+ </div>
96
+ {#if showClose}<DialogPrimitive.Close
97
+ class="ldt-button ldt-icon-button ldt-button--ghost"
98
+ aria-label={closeLabel}>×</DialogPrimitive.Close
99
+ >{/if}
100
+ </header>
101
+ <div class="ldt-drawer__body ldt-scrollbar">{@render children()}</div>
102
+ {#if footer}<footer class="ldt-drawer__footer">{@render footer()}</footer>{/if}
103
+ </DialogPrimitive.Content>
104
+ </DialogPrimitive.Portal>
105
+ </DialogPrimitive.Root>
@@ -0,0 +1,31 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { DialogContentProps } from 'bits-ui';
3
+ import type { ActionVariant, ControlSize, HeadingLevel, TriggerChildProps } from '../types.js';
4
+ type Props = Omit<DialogContentProps, 'children' | 'class' | 'child'> & {
5
+ open?: boolean;
6
+ title: string;
7
+ description?: string;
8
+ /** Edge the panel is anchored to. `left`/`right` size by width, `top`/`bottom` by height. */
9
+ side?: 'left' | 'right' | 'top' | 'bottom';
10
+ /** Trigger contents, rendered inside a button styled with `triggerClass`. */
11
+ trigger?: Snippet;
12
+ /** Full control of the trigger element — receives the props Bits needs on it. */
13
+ triggerChild?: Snippet<[TriggerChildProps]>;
14
+ /** Button variant for the default trigger. An explicit `triggerClass` wins wholesale. */
15
+ triggerVariant?: ActionVariant;
16
+ triggerSize?: ControlSize;
17
+ triggerClass?: string;
18
+ children: Snippet;
19
+ footer?: Snippet;
20
+ headingLevel?: HeadingLevel;
21
+ showClose?: boolean;
22
+ closeLabel?: string;
23
+ /** `class` lands on the portaled Content element. */
24
+ class?: string;
25
+ overlayClass?: string;
26
+ onOpenChange?: (open: boolean) => void;
27
+ ref?: HTMLElement | null;
28
+ };
29
+ declare const Drawer: import("svelte").Component<Props, {}, "ref" | "open">;
30
+ type Drawer = ReturnType<typeof Drawer>;
31
+ export default Drawer;
@@ -58,13 +58,13 @@
58
58
  {#if item.href}
59
59
  <a {...props} href={item.href}
60
60
  >{#if item.icon}{@render item.icon()}{/if}<span>{item.label}</span>{#if item.hint}<span
61
- aria-hidden="true">{item.hint}</span
61
+ class="ldt-menu__hint">{item.hint}</span
62
62
  >{/if}</a
63
63
  >
64
64
  {:else}
65
65
  <button {...props} type="button"
66
66
  >{#if item.icon}{@render item.icon()}{/if}<span>{item.label}</span>{#if item.hint}<span
67
- aria-hidden="true">{item.hint}</span
67
+ class="ldt-menu__hint">{item.hint}</span
68
68
  >{/if}</button
69
69
  >
70
70
  {/if}
@@ -2,7 +2,7 @@ import { DropdownMenu as MenuPrimitive } from 'bits-ui';
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { Alignment, MenuItem, Placement, TriggerChildProps } from '../types.js';
4
4
  declare function $$render<T extends string = string>(): {
5
- props: Omit<MenuPrimitive.ContentProps, "class" | "children" | "child"> & {
5
+ props: Omit<MenuPrimitive.ContentProps, "children" | "class" | "child"> & {
6
6
  open?: boolean;
7
7
  /** Heading rendered above `items`, associated with them via a menu group. */
8
8
  groupLabel?: string;