@loidolt/theme-svelte 0.3.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 (51) hide show
  1. package/README.md +21 -0
  2. package/dist/components/AlertDialog.svelte +24 -3
  3. package/dist/components/AlertDialog.svelte.d.ts +4 -1
  4. package/dist/components/AppShell.svelte +2 -1
  5. package/dist/components/AppShell.svelte.d.ts +1 -0
  6. package/dist/components/Card.svelte +23 -5
  7. package/dist/components/Card.svelte.d.ts +11 -4
  8. package/dist/components/CodeBlock.svelte +55 -0
  9. package/dist/components/CodeBlock.svelte.d.ts +14 -0
  10. package/dist/components/CommentList.svelte +67 -0
  11. package/dist/components/CommentList.svelte.d.ts +53 -0
  12. package/dist/components/Dialog.svelte +25 -3
  13. package/dist/components/Dialog.svelte.d.ts +4 -1
  14. package/dist/components/Drawer.svelte +18 -3
  15. package/dist/components/Drawer.svelte.d.ts +4 -1
  16. package/dist/components/FileInput.svelte +47 -0
  17. package/dist/components/FileInput.svelte.d.ts +15 -0
  18. package/dist/components/Filmstrip.svelte +72 -0
  19. package/dist/components/Filmstrip.svelte.d.ts +50 -0
  20. package/dist/components/FloatingBar.svelte +43 -0
  21. package/dist/components/FloatingBar.svelte.d.ts +16 -0
  22. package/dist/components/ListRow.svelte +50 -0
  23. package/dist/components/ListRow.svelte.d.ts +18 -0
  24. package/dist/components/Marker.svelte +38 -0
  25. package/dist/components/Marker.svelte.d.ts +14 -0
  26. package/dist/components/Popover.svelte +24 -3
  27. package/dist/components/Popover.svelte.d.ts +4 -1
  28. package/dist/components/RecordStepper.svelte +58 -0
  29. package/dist/components/RecordStepper.svelte.d.ts +18 -0
  30. package/dist/components/SegmentedNav.svelte +53 -0
  31. package/dist/components/SegmentedNav.svelte.d.ts +22 -0
  32. package/dist/components/Stat.svelte +41 -0
  33. package/dist/components/Stat.svelte.d.ts +17 -0
  34. package/dist/components/StatusDot.svelte +27 -0
  35. package/dist/components/StatusDot.svelte.d.ts +12 -0
  36. package/dist/components/SwatchGroup.svelte +69 -0
  37. package/dist/components/SwatchGroup.svelte.d.ts +44 -0
  38. package/dist/components/Switch.svelte +19 -0
  39. package/dist/components/Tabs.svelte +10 -0
  40. package/dist/components/ThemeToggle.svelte +102 -31
  41. package/dist/components/ThemeToggle.svelte.d.ts +5 -10
  42. package/dist/components/Thumbnail.svelte +51 -0
  43. package/dist/components/Thumbnail.svelte.d.ts +19 -0
  44. package/dist/components/Workspace.svelte +9 -1
  45. package/dist/components/Workspace.svelte.d.ts +4 -0
  46. package/dist/index.d.ts +13 -0
  47. package/dist/index.js +13 -0
  48. package/dist/theme.svelte.js +17 -7
  49. package/dist/toaster.svelte.d.ts +2 -0
  50. package/dist/toaster.svelte.js +30 -2
  51. package/package.json +8 -7
package/README.md ADDED
@@ -0,0 +1,21 @@
1
+ # @loidolt/theme-svelte
2
+
3
+ Accessible Svelte 5 components and state helpers for the Loidolt design system.
4
+
5
+ ```sh
6
+ npm install @loidolt/theme-svelte
7
+ ```
8
+
9
+ Import the global styles once:
10
+
11
+ ```css
12
+ @import '@loidolt/theme-svelte/styles.css';
13
+ ```
14
+
15
+ Then import components from the package barrel:
16
+
17
+ ```svelte
18
+ <script>
19
+ import { Button, Field, Input } from '@loidolt/theme-svelte';
20
+ </script>
21
+ ```
@@ -2,7 +2,13 @@
2
2
  import { AlertDialog as AlertDialogPrimitive } from 'bits-ui';
3
3
  import type { Snippet } from 'svelte';
4
4
  import type { AlertDialogContentProps } from 'bits-ui';
5
- import type { ActionVariant, 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<AlertDialogContentProps, 'children' | 'class' | 'child'> & {
@@ -14,6 +20,9 @@
14
20
  trigger?: Snippet;
15
21
  /** Full control of the trigger element — receives the props Bits needs on it. */
16
22
  triggerChild?: Snippet<[TriggerChildProps]>;
23
+ /** Button variant for the default trigger. An explicit `triggerClass` wins wholesale. */
24
+ triggerVariant?: ActionVariant;
25
+ triggerSize?: ControlSize;
17
26
  triggerClass?: string;
18
27
  /** Extra body content between the description and the buttons. */
19
28
  children?: Snippet;
@@ -38,7 +47,9 @@
38
47
  description,
39
48
  trigger,
40
49
  triggerChild,
41
- triggerClass = 'ldt-button',
50
+ triggerVariant,
51
+ triggerSize,
52
+ triggerClass,
42
53
  children,
43
54
  confirmLabel = 'Confirm',
44
55
  cancelLabel = 'Cancel',
@@ -53,6 +64,16 @@
53
64
  ref = $bindable(null),
54
65
  ...rest
55
66
  }: Props = $props();
67
+
68
+ // An explicit triggerClass keeps today's contract and wins outright.
69
+ const triggerClasses = $derived(
70
+ triggerClass ??
71
+ cx(
72
+ 'ldt-button',
73
+ triggerVariant && triggerVariant !== 'default' && `ldt-button--${triggerVariant}`,
74
+ triggerSize && triggerSize !== 'md' && `ldt-button--${triggerSize}`
75
+ )
76
+ );
56
77
  </script>
57
78
 
58
79
  <!--
@@ -66,7 +87,7 @@
66
87
  >{#snippet child(childProps)}{@render triggerChild(
67
88
  childProps
68
89
  )}{/snippet}</AlertDialogPrimitive.Trigger
69
- >{:else if trigger}<AlertDialogPrimitive.Trigger class={triggerClass}
90
+ >{:else if trigger}<AlertDialogPrimitive.Trigger class={triggerClasses}
70
91
  >{@render trigger()}</AlertDialogPrimitive.Trigger
71
92
  >{/if}
72
93
  <AlertDialogPrimitive.Portal>
@@ -1,6 +1,6 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  import type { AlertDialogContentProps } from 'bits-ui';
3
- import type { ActionVariant, DialogSize, HeadingLevel, TriggerChildProps } from '../types.js';
3
+ import type { ActionVariant, ControlSize, DialogSize, HeadingLevel, TriggerChildProps } from '../types.js';
4
4
  type Props = Omit<AlertDialogContentProps, 'children' | 'class' | 'child'> & {
5
5
  open?: boolean;
6
6
  title: string;
@@ -10,6 +10,9 @@ type Props = Omit<AlertDialogContentProps, 'children' | 'class' | 'child'> & {
10
10
  trigger?: Snippet;
11
11
  /** Full control of the trigger element — receives the props Bits needs on it. */
12
12
  triggerChild?: Snippet<[TriggerChildProps]>;
13
+ /** Button variant for the default trigger. An explicit `triggerClass` wins wholesale. */
14
+ triggerVariant?: ActionVariant;
15
+ triggerSize?: ControlSize;
13
16
  triggerClass?: string;
14
17
  /** Extra body content between the description and the buttons. */
15
18
  children?: Snippet;
@@ -4,6 +4,7 @@
4
4
  import { cx } from '../utils.js';
5
5
 
6
6
  interface Props extends HTMLAttributes<HTMLDivElement> {
7
+ /** Complete banner/header content, such as `Topbar`; rendered without another landmark wrapper. */
7
8
  header?: Snippet;
8
9
  children: Snippet;
9
10
  footer?: Snippet;
@@ -25,7 +26,7 @@
25
26
  </script>
26
27
 
27
28
  <div bind:this={ref} class={cx('ldt-app-shell', className)} {...rest}>
28
- {#if header}<header>{@render header()}</header>{/if}
29
+ {#if header}{@render header()}{/if}
29
30
  <main id={mainId}>{@render children()}</main>
30
31
  {#if footer}<footer>{@render footer()}</footer>{/if}
31
32
  </div>
@@ -1,6 +1,7 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  import type { HTMLAttributes } from 'svelte/elements';
3
3
  interface Props extends HTMLAttributes<HTMLDivElement> {
4
+ /** Complete banner/header content, such as `Topbar`; rendered without another landmark wrapper. */
4
5
  header?: Snippet;
5
6
  children: Snippet;
6
7
  footer?: Snippet;
@@ -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;
@@ -2,7 +2,7 @@
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 { HeadingLevel, TriggerChildProps } from '../types.js';
5
+ import type { ActionVariant, ControlSize, HeadingLevel, TriggerChildProps } from '../types.js';
6
6
  import { cx } from '../utils.js';
7
7
 
8
8
  type Props = Omit<DialogContentProps, 'children' | 'class' | 'child'> & {
@@ -15,6 +15,9 @@
15
15
  trigger?: Snippet;
16
16
  /** Full control of the trigger element — receives the props Bits needs on it. */
17
17
  triggerChild?: Snippet<[TriggerChildProps]>;
18
+ /** Button variant for the default trigger. An explicit `triggerClass` wins wholesale. */
19
+ triggerVariant?: ActionVariant;
20
+ triggerSize?: ControlSize;
18
21
  triggerClass?: string;
19
22
  children: Snippet;
20
23
  footer?: Snippet;
@@ -35,7 +38,9 @@
35
38
  side = 'left',
36
39
  trigger,
37
40
  triggerChild,
38
- triggerClass = 'ldt-button',
41
+ triggerVariant,
42
+ triggerSize,
43
+ triggerClass,
39
44
  children,
40
45
  footer,
41
46
  headingLevel = 2,
@@ -47,6 +52,16 @@
47
52
  ref = $bindable(null),
48
53
  ...rest
49
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
+ );
50
65
  </script>
51
66
 
52
67
  <!--
@@ -60,7 +75,7 @@
60
75
  >{#snippet child(childProps)}{@render triggerChild(
61
76
  childProps
62
77
  )}{/snippet}</DialogPrimitive.Trigger
63
- >{:else if trigger}<DialogPrimitive.Trigger class={triggerClass}
78
+ >{:else if trigger}<DialogPrimitive.Trigger class={triggerClasses}
64
79
  >{@render trigger()}</DialogPrimitive.Trigger
65
80
  >{/if}
66
81
  <DialogPrimitive.Portal>
@@ -1,6 +1,6 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  import type { DialogContentProps } from 'bits-ui';
3
- import type { HeadingLevel, TriggerChildProps } from '../types.js';
3
+ import type { ActionVariant, ControlSize, HeadingLevel, TriggerChildProps } from '../types.js';
4
4
  type Props = Omit<DialogContentProps, 'children' | 'class' | 'child'> & {
5
5
  open?: boolean;
6
6
  title: string;
@@ -11,6 +11,9 @@ type Props = Omit<DialogContentProps, 'children' | 'class' | 'child'> & {
11
11
  trigger?: Snippet;
12
12
  /** Full control of the trigger element — receives the props Bits needs on it. */
13
13
  triggerChild?: Snippet<[TriggerChildProps]>;
14
+ /** Button variant for the default trigger. An explicit `triggerClass` wins wholesale. */
15
+ triggerVariant?: ActionVariant;
16
+ triggerSize?: ControlSize;
14
17
  triggerClass?: string;
15
18
  children: Snippet;
16
19
  footer?: Snippet;
@@ -0,0 +1,47 @@
1
+ <script lang="ts">
2
+ import type { HTMLInputAttributes } from 'svelte/elements';
3
+ import { cx } from '../utils.js';
4
+
5
+ // Input cannot render type="file" — its string `value` binding does not apply — so the
6
+ // file picker gets its own component in the same clothes.
7
+ interface Props extends Omit<HTMLInputAttributes, 'type' | 'value' | 'files' | 'class'> {
8
+ accept?: string;
9
+ multiple?: boolean;
10
+ /** Fired when the user picks at least one file. */
11
+ onFiles?: (files: FileList) => void;
12
+ disabled?: boolean;
13
+ /** Draws the full box instead of the default underline, like Input. */
14
+ boxed?: boolean;
15
+ class?: string;
16
+ ref?: HTMLInputElement | null;
17
+ }
18
+
19
+ let {
20
+ accept,
21
+ multiple = false,
22
+ onFiles,
23
+ disabled = false,
24
+ boxed = false,
25
+ class: className,
26
+ onchange,
27
+ ref = $bindable(null),
28
+ ...rest
29
+ }: Props = $props();
30
+
31
+ function handleChange(event: Event & { currentTarget: HTMLInputElement }) {
32
+ onchange?.(event);
33
+ const files = event.currentTarget.files;
34
+ if (files && files.length > 0) onFiles?.(files);
35
+ }
36
+ </script>
37
+
38
+ <input
39
+ bind:this={ref}
40
+ type="file"
41
+ class={cx('ldt-input', 'ldt-file-input', boxed && 'ldt-input--boxed', className)}
42
+ {accept}
43
+ {multiple}
44
+ {disabled}
45
+ {...rest}
46
+ onchange={handleChange}
47
+ />
@@ -0,0 +1,15 @@
1
+ import type { HTMLInputAttributes } from 'svelte/elements';
2
+ interface Props extends Omit<HTMLInputAttributes, 'type' | 'value' | 'files' | 'class'> {
3
+ accept?: string;
4
+ multiple?: boolean;
5
+ /** Fired when the user picks at least one file. */
6
+ onFiles?: (files: FileList) => void;
7
+ disabled?: boolean;
8
+ /** Draws the full box instead of the default underline, like Input. */
9
+ boxed?: boolean;
10
+ class?: string;
11
+ ref?: HTMLInputElement | null;
12
+ }
13
+ declare const FileInput: import("svelte").Component<Props, {}, "ref">;
14
+ type FileInput = ReturnType<typeof FileInput>;
15
+ export default FileInput;