@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
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
+ ```
@@ -0,0 +1,100 @@
1
+ <script lang="ts" generics="T extends string = string">
2
+ import { Accordion as AccordionPrimitive } from 'bits-ui';
3
+ import type { Snippet } from 'svelte';
4
+ import type { HTMLAttributes } from 'svelte/elements';
5
+ import type { Disclosure, HeadingLevel } from '../types.js';
6
+ import { cx } from '../utils.js';
7
+
8
+ interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
9
+ items: Disclosure<T>[];
10
+ /** Narrowed from the DOM attribute: the Bits root this spreads onto rejects `null`. */
11
+ id?: string;
12
+ /** Lets more than one panel stay open. `value` is then an array. */
13
+ multiple?: boolean;
14
+ /** Open panel, or panels when `multiple`. Bindable. */
15
+ value?: T | T[];
16
+ /**
17
+ * Heading level of every trigger's wrapper. Panels sit at one level below whatever heading
18
+ * introduces the accordion, so keep this in step with the surrounding outline.
19
+ */
20
+ headingLevel?: HeadingLevel;
21
+ disabled?: boolean;
22
+ /** Panel body, rendered for each item with the item's own value. */
23
+ children: Snippet<[{ value: T }]>;
24
+ class?: string;
25
+ itemClass?: string;
26
+ contentClass?: string;
27
+ onValueChange?: (value: T | T[]) => void;
28
+ ref?: HTMLDivElement | null;
29
+ }
30
+
31
+ let {
32
+ items,
33
+ multiple = false,
34
+ value = $bindable(),
35
+ headingLevel = 3,
36
+ disabled = false,
37
+ children,
38
+ class: className,
39
+ itemClass,
40
+ contentClass,
41
+ onValueChange,
42
+ ref = $bindable(null),
43
+ ...rest
44
+ }: Props = $props();
45
+
46
+ // Bits discriminates single/multiple at the type level with a different `value` shape for
47
+ // each. The panels are identical either way, so the markup lives in one snippet and only the
48
+ // Root differs.
49
+ const change = (next: string | string[]) => {
50
+ value = next as T | T[];
51
+ onValueChange?.(next as T | T[]);
52
+ };
53
+ </script>
54
+
55
+ {#snippet panels()}
56
+ {#each items as item (item.value)}
57
+ <AccordionPrimitive.Item
58
+ value={item.value}
59
+ disabled={item.disabled}
60
+ class={cx('ldt-accordion__item', itemClass)}
61
+ >
62
+ <AccordionPrimitive.Header level={headingLevel} class="ldt-accordion__heading">
63
+ <AccordionPrimitive.Trigger class="ldt-accordion__trigger">
64
+ <span class="ldt-accordion__label">{item.title}</span>
65
+ <!-- Decorative: the trigger's `aria-expanded` already carries open state. -->
66
+ <span class="ldt-accordion__indicator" aria-hidden="true"></span>
67
+ </AccordionPrimitive.Trigger>
68
+ </AccordionPrimitive.Header>
69
+ <AccordionPrimitive.Content class={cx('ldt-accordion__content', contentClass)}>
70
+ <div class="ldt-accordion__body">{@render children({ value: item.value })}</div>
71
+ </AccordionPrimitive.Content>
72
+ </AccordionPrimitive.Item>
73
+ {/each}
74
+ {/snippet}
75
+
76
+ {#if multiple}
77
+ <AccordionPrimitive.Root
78
+ bind:ref
79
+ type="multiple"
80
+ value={(value ?? []) as string[]}
81
+ onValueChange={change}
82
+ {disabled}
83
+ class={cx('ldt-accordion', className)}
84
+ {...rest}
85
+ >
86
+ {@render panels()}
87
+ </AccordionPrimitive.Root>
88
+ {:else}
89
+ <AccordionPrimitive.Root
90
+ bind:ref
91
+ type="single"
92
+ value={(value ?? '') as string}
93
+ onValueChange={change}
94
+ {disabled}
95
+ class={cx('ldt-accordion', className)}
96
+ {...rest}
97
+ >
98
+ {@render panels()}
99
+ </AccordionPrimitive.Root>
100
+ {/if}
@@ -0,0 +1,50 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ import type { Disclosure, HeadingLevel } from '../types.js';
4
+ declare function $$render<T extends string = string>(): {
5
+ props: Omit<HTMLAttributes<HTMLDivElement>, "children"> & {
6
+ items: Disclosure<T>[];
7
+ /** Narrowed from the DOM attribute: the Bits root this spreads onto rejects `null`. */
8
+ id?: string;
9
+ /** Lets more than one panel stay open. `value` is then an array. */
10
+ multiple?: boolean;
11
+ /** Open panel, or panels when `multiple`. Bindable. */
12
+ value?: T | T[];
13
+ /**
14
+ * Heading level of every trigger's wrapper. Panels sit at one level below whatever heading
15
+ * introduces the accordion, so keep this in step with the surrounding outline.
16
+ */
17
+ headingLevel?: HeadingLevel;
18
+ disabled?: boolean;
19
+ /** Panel body, rendered for each item with the item's own value. */
20
+ children: Snippet<[{
21
+ value: T;
22
+ }]>;
23
+ class?: string;
24
+ itemClass?: string;
25
+ contentClass?: string;
26
+ onValueChange?: (value: T | T[]) => void;
27
+ ref?: HTMLDivElement | null;
28
+ };
29
+ exports: {};
30
+ bindings: "value" | "ref";
31
+ slots: {};
32
+ events: {};
33
+ };
34
+ declare class __sveltets_Render<T extends string = string> {
35
+ props(): ReturnType<typeof $$render<T>>['props'];
36
+ events(): ReturnType<typeof $$render<T>>['events'];
37
+ slots(): ReturnType<typeof $$render<T>>['slots'];
38
+ bindings(): "value" | "ref";
39
+ exports(): {};
40
+ }
41
+ interface $$IsomorphicComponent {
42
+ 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']>> & {
43
+ $$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
44
+ } & ReturnType<__sveltets_Render<T>['exports']>;
45
+ <T extends string = string>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
46
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
47
+ }
48
+ declare const Accordion: $$IsomorphicComponent;
49
+ type Accordion<T extends string = string> = InstanceType<typeof Accordion<T>>;
50
+ export default Accordion;
@@ -4,9 +4,10 @@
4
4
  import type { HeadingLevel, StatusVariant } from '../types.js';
5
5
  import { cx } from '../utils.js';
6
6
 
7
- interface Props extends HTMLAttributes<HTMLDivElement> {
7
+ interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'title'> {
8
8
  variant?: StatusVariant;
9
- title: string;
9
+ /** Optional heading. Accepts a snippet for rich content; omit it for a body-only alert. */
10
+ title?: string | Snippet;
10
11
  /** Heading element used for the title, so the alert fits the page outline. */
11
12
  headingLevel?: HeadingLevel;
12
13
  class?: string;
@@ -31,6 +32,8 @@
31
32
  role={variant === 'error' ? 'alert' : 'status'}
32
33
  {...rest}
33
34
  >
34
- <svelte:element this={`h${headingLevel}`} class="ldt-alert__title">{title}</svelte:element>
35
+ {#if title}<svelte:element this={`h${headingLevel}`} class="ldt-alert__title"
36
+ >{#if typeof title === 'string'}{title}{:else}{@render title()}{/if}</svelte:element
37
+ >{/if}
35
38
  {#if children}<div class="ldt-alert__description">{@render children()}</div>{/if}
36
39
  </div>
@@ -1,9 +1,10 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  import type { HTMLAttributes } from 'svelte/elements';
3
3
  import type { HeadingLevel, StatusVariant } from '../types.js';
4
- interface Props extends HTMLAttributes<HTMLDivElement> {
4
+ interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'title'> {
5
5
  variant?: StatusVariant;
6
- title: string;
6
+ /** Optional heading. Accepts a snippet for rich content; omit it for a body-only alert. */
7
+ title?: string | Snippet;
7
8
  /** Heading element used for the title, so the alert fits the page outline. */
8
9
  headingLevel?: HeadingLevel;
9
10
  class?: string;
@@ -0,0 +1,130 @@
1
+ <script lang="ts">
2
+ import { AlertDialog as AlertDialogPrimitive } from 'bits-ui';
3
+ import type { Snippet } from 'svelte';
4
+ import type { AlertDialogContentProps } from 'bits-ui';
5
+ import type {
6
+ ActionVariant,
7
+ ControlSize,
8
+ DialogSize,
9
+ HeadingLevel,
10
+ TriggerChildProps,
11
+ } from '../types.js';
12
+ import { cx } from '../utils.js';
13
+
14
+ type Props = Omit<AlertDialogContentProps, 'children' | 'class' | 'child'> & {
15
+ open?: boolean;
16
+ title: string;
17
+ /** What confirming will do. Say the consequence here, not in the button. */
18
+ description?: string;
19
+ /** Trigger contents, rendered inside a button styled with `triggerClass`. */
20
+ trigger?: Snippet;
21
+ /** Full control of the trigger element — receives the props Bits needs on it. */
22
+ triggerChild?: Snippet<[TriggerChildProps]>;
23
+ /** Button variant for the default trigger. An explicit `triggerClass` wins wholesale. */
24
+ triggerVariant?: ActionVariant;
25
+ triggerSize?: ControlSize;
26
+ triggerClass?: string;
27
+ /** Extra body content between the description and the buttons. */
28
+ children?: Snippet;
29
+ confirmLabel?: string;
30
+ cancelLabel?: string;
31
+ /** Colour of the confirm button. Use `danger` for anything destructive. */
32
+ confirmVariant?: ActionVariant;
33
+ onConfirm?: () => void;
34
+ onCancel?: () => void;
35
+ size?: DialogSize;
36
+ headingLevel?: HeadingLevel;
37
+ /** `class` lands on the portaled Content element. */
38
+ class?: string;
39
+ overlayClass?: string;
40
+ onOpenChange?: (open: boolean) => void;
41
+ ref?: HTMLElement | null;
42
+ };
43
+
44
+ let {
45
+ open = $bindable(false),
46
+ title,
47
+ description,
48
+ trigger,
49
+ triggerChild,
50
+ triggerVariant,
51
+ triggerSize,
52
+ triggerClass,
53
+ children,
54
+ confirmLabel = 'Confirm',
55
+ cancelLabel = 'Cancel',
56
+ confirmVariant = 'primary',
57
+ onConfirm,
58
+ onCancel,
59
+ size = 'sm',
60
+ headingLevel = 2,
61
+ class: className,
62
+ overlayClass,
63
+ onOpenChange,
64
+ ref = $bindable(null),
65
+ ...rest
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
+ );
77
+ </script>
78
+
79
+ <!--
80
+ A decision the user has to answer before anything else continues. Unlike `Dialog` it has no
81
+ close affordance in the corner and does not dismiss on an outside click — Bits' alertdialog
82
+ role enforces that, and it is the point: a confirmation you can dismiss by missing is not one.
83
+ Focus opens on Cancel, so a stray Enter cannot confirm a destructive action.
84
+ -->
85
+ <AlertDialogPrimitive.Root bind:open {onOpenChange}>
86
+ {#if triggerChild}<AlertDialogPrimitive.Trigger
87
+ >{#snippet child(childProps)}{@render triggerChild(
88
+ childProps
89
+ )}{/snippet}</AlertDialogPrimitive.Trigger
90
+ >{:else if trigger}<AlertDialogPrimitive.Trigger class={triggerClasses}
91
+ >{@render trigger()}</AlertDialogPrimitive.Trigger
92
+ >{/if}
93
+ <AlertDialogPrimitive.Portal>
94
+ <AlertDialogPrimitive.Overlay class={cx('ldt-overlay', overlayClass)} />
95
+ <AlertDialogPrimitive.Content
96
+ bind:ref
97
+ class={cx('ldt-dialog', size && `ldt-dialog--${size}`, className)}
98
+ {...rest}
99
+ >
100
+ <header class="ldt-dialog__header">
101
+ <div>
102
+ <AlertDialogPrimitive.Title level={headingLevel} class="ldt-dialog__title"
103
+ >{title}</AlertDialogPrimitive.Title
104
+ >{#if description}<AlertDialogPrimitive.Description class="ldt-dialog__description"
105
+ >{description}</AlertDialogPrimitive.Description
106
+ >{/if}
107
+ </div>
108
+ </header>
109
+ {#if children}<div class="ldt-dialog__body">{@render children()}</div>{/if}
110
+ <footer class="ldt-dialog__footer">
111
+ <AlertDialogPrimitive.Cancel
112
+ class="ldt-button ldt-button--quiet"
113
+ onclick={() => onCancel?.()}>{cancelLabel}</AlertDialogPrimitive.Cancel
114
+ >
115
+ <!--
116
+ Bits leaves the Action button open on purpose, for callers awaiting a request. This
117
+ wrapper is the declarative form, so it closes: `open` is bindable, and work that has to
118
+ keep the dialog up can set it back to `true` from `onConfirm`.
119
+ -->
120
+ <AlertDialogPrimitive.Action
121
+ class={cx('ldt-button', confirmVariant !== 'default' && `ldt-button--${confirmVariant}`)}
122
+ onclick={() => {
123
+ onConfirm?.();
124
+ open = false;
125
+ }}>{confirmLabel}</AlertDialogPrimitive.Action
126
+ >
127
+ </footer>
128
+ </AlertDialogPrimitive.Content>
129
+ </AlertDialogPrimitive.Portal>
130
+ </AlertDialogPrimitive.Root>
@@ -0,0 +1,35 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { AlertDialogContentProps } from 'bits-ui';
3
+ import type { ActionVariant, ControlSize, DialogSize, HeadingLevel, TriggerChildProps } from '../types.js';
4
+ type Props = Omit<AlertDialogContentProps, 'children' | 'class' | 'child'> & {
5
+ open?: boolean;
6
+ title: string;
7
+ /** What confirming will do. Say the consequence here, not in the button. */
8
+ description?: string;
9
+ /** Trigger contents, rendered inside a button styled with `triggerClass`. */
10
+ trigger?: Snippet;
11
+ /** Full control of the trigger element — receives the props Bits needs on it. */
12
+ triggerChild?: Snippet<[TriggerChildProps]>;
13
+ /** Button variant for the default trigger. An explicit `triggerClass` wins wholesale. */
14
+ triggerVariant?: ActionVariant;
15
+ triggerSize?: ControlSize;
16
+ triggerClass?: string;
17
+ /** Extra body content between the description and the buttons. */
18
+ children?: Snippet;
19
+ confirmLabel?: string;
20
+ cancelLabel?: string;
21
+ /** Colour of the confirm button. Use `danger` for anything destructive. */
22
+ confirmVariant?: ActionVariant;
23
+ onConfirm?: () => void;
24
+ onCancel?: () => void;
25
+ size?: DialogSize;
26
+ headingLevel?: HeadingLevel;
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
+ declare const AlertDialog: import("svelte").Component<Props, {}, "ref" | "open">;
34
+ type AlertDialog = ReturnType<typeof AlertDialog>;
35
+ export default AlertDialog;
@@ -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,11 +1,12 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLAttributes } from 'svelte/elements';
4
- import type { BadgeVariant } from '../types.js';
4
+ import type { BadgeSize, BadgeVariant } from '../types.js';
5
5
  import { cx } from '../utils.js';
6
6
 
7
7
  interface Props extends HTMLAttributes<HTMLSpanElement> {
8
8
  variant?: BadgeVariant;
9
+ size?: BadgeSize;
9
10
  class?: string;
10
11
  children: Snippet;
11
12
  ref?: HTMLSpanElement | null;
@@ -13,6 +14,7 @@
13
14
 
14
15
  let {
15
16
  variant = 'default',
17
+ size = 'inline',
16
18
  class: className,
17
19
  children,
18
20
  ref = $bindable(null),
@@ -22,6 +24,11 @@
22
24
 
23
25
  <span
24
26
  bind:this={ref}
25
- class={cx('ldt-badge', variant !== 'default' && `ldt-badge--${variant}`, className)}
27
+ class={cx(
28
+ 'ldt-badge',
29
+ variant !== 'default' && `ldt-badge--${variant}`,
30
+ size !== 'inline' && `ldt-badge--${size}`,
31
+ className
32
+ )}
26
33
  {...rest}>{@render children()}</span
27
34
  >
@@ -1,8 +1,9 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  import type { HTMLAttributes } from 'svelte/elements';
3
- import type { BadgeVariant } from '../types.js';
3
+ import type { BadgeSize, BadgeVariant } from '../types.js';
4
4
  interface Props extends HTMLAttributes<HTMLSpanElement> {
5
5
  variant?: BadgeVariant;
6
+ size?: BadgeSize;
6
7
  class?: string;
7
8
  children: Snippet;
8
9
  ref?: HTMLSpanElement | null;
@@ -3,17 +3,24 @@
3
3
  import type { HTMLAnchorAttributes, HTMLAttributes } from 'svelte/elements';
4
4
  import { cx } from '../utils.js';
5
5
 
6
- type Props = HTMLAnchorAttributes &
7
- HTMLAttributes<HTMLSpanElement> & {
8
- name: string;
9
- /** Secondary line, e.g. an environment or version marker. */
10
- meta?: string;
11
- /** Renders an `<a>` instead of a `<span>`. */
12
- href?: string;
13
- mark?: Snippet;
14
- class?: string;
15
- ref?: HTMLElement | null;
16
- };
6
+ interface CommonProps {
7
+ name: string;
8
+ /** Secondary line, e.g. an environment or version marker. */
9
+ meta?: string;
10
+ mark?: Snippet;
11
+ class?: string;
12
+ ref?: HTMLElement | null;
13
+ }
14
+
15
+ // Discriminated on `href`: an `<a>` with anchor attributes, or a plain `<span>`.
16
+ type Props = CommonProps &
17
+ (
18
+ | (Omit<HTMLAnchorAttributes, 'class'> & {
19
+ /** Renders an `<a>` instead of a `<span>`. */
20
+ href: string;
21
+ })
22
+ | (Omit<HTMLAttributes<HTMLSpanElement>, 'class'> & { href?: never })
23
+ );
17
24
 
18
25
  let {
19
26
  name,
@@ -28,8 +35,15 @@
28
35
 
29
36
  {#snippet content()}{#if mark}{@render mark()}{/if}<strong class="ldt-brand__name">{name}</strong
30
37
  >{#if meta}<span class="ldt-brand__meta">{meta}</span>{/if}{/snippet}
31
- {#if href}<a bind:this={ref} {href} class={cx('ldt-brand', className)} {...rest}
32
- >{@render content()}</a
33
- >{:else}<span bind:this={ref} class={cx('ldt-brand', className)} {...rest}
34
- >{@render content()}</span
38
+ <!-- TS cannot narrow the destructured `rest` by `href`, so each branch casts to its side
39
+ of the union. The public Props type still rejects mismatched attributes. -->
40
+ {#if href}<a
41
+ bind:this={ref}
42
+ {href}
43
+ class={cx('ldt-brand', className)}
44
+ {...rest as HTMLAnchorAttributes}>{@render content()}</a
45
+ >{:else}<span
46
+ bind:this={ref}
47
+ class={cx('ldt-brand', className)}
48
+ {...rest as HTMLAttributes<HTMLSpanElement>}>{@render content()}</span
35
49
  >{/if}
@@ -1,15 +1,19 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  import type { HTMLAnchorAttributes, HTMLAttributes } from 'svelte/elements';
3
- type Props = HTMLAnchorAttributes & HTMLAttributes<HTMLSpanElement> & {
3
+ interface CommonProps {
4
4
  name: string;
5
5
  /** Secondary line, e.g. an environment or version marker. */
6
6
  meta?: string;
7
- /** Renders an `<a>` instead of a `<span>`. */
8
- href?: string;
9
7
  mark?: Snippet;
10
8
  class?: string;
11
9
  ref?: HTMLElement | null;
12
- };
10
+ }
11
+ type Props = CommonProps & ((Omit<HTMLAnchorAttributes, 'class'> & {
12
+ /** Renders an `<a>` instead of a `<span>`. */
13
+ href: string;
14
+ }) | (Omit<HTMLAttributes<HTMLSpanElement>, 'class'> & {
15
+ href?: never;
16
+ }));
13
17
  declare const Brand: import("svelte").Component<Props, {}, "ref">;
14
18
  type Brand = ReturnType<typeof Brand>;
15
19
  export default Brand;
@@ -0,0 +1,53 @@
1
+ <script lang="ts">
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ import type { Crumb } from '../types.js';
4
+ import { cx } from '../utils.js';
5
+
6
+ interface Props extends HTMLAttributes<HTMLElement> {
7
+ /** Ordered trail, root first. The last entry is the current page and is never a link. */
8
+ items: Crumb[];
9
+ /** Accessible name of the landmark. Several trails on one page need distinct names. */
10
+ label?: string;
11
+ /**
12
+ * Separator drawn between crumbs. Decorative — it lives in a `::before` on the list item, so
13
+ * it is never read out or copied with the text.
14
+ */
15
+ separator?: string;
16
+ class?: string;
17
+ ref?: HTMLElement | null;
18
+ }
19
+
20
+ let {
21
+ items,
22
+ label = 'Breadcrumb',
23
+ separator = '/',
24
+ class: className,
25
+ ref = $bindable(null),
26
+ ...rest
27
+ }: Props = $props();
28
+ </script>
29
+
30
+ <nav
31
+ bind:this={ref}
32
+ class={cx('ldt-breadcrumbs', className)}
33
+ aria-label={label}
34
+ style={`--ldt-breadcrumb-separator: ${JSON.stringify(separator)}`}
35
+ {...rest}
36
+ >
37
+ <ol class="ldt-breadcrumbs__list">
38
+ {#each items as item, index (item.label)}
39
+ {@const current = index === items.length - 1}
40
+ <li class="ldt-breadcrumbs__item">
41
+ {#if item.href && !current}
42
+ <a class="ldt-breadcrumbs__link" href={item.href}>{item.label}</a>
43
+ {:else}
44
+ <!-- The current page is not a link, and says so. Marking it up as one that goes
45
+ nowhere is the most common breadcrumb defect. -->
46
+ <span class="ldt-breadcrumbs__current" aria-current={current ? 'page' : undefined}
47
+ >{item.label}</span
48
+ >
49
+ {/if}
50
+ </li>
51
+ {/each}
52
+ </ol>
53
+ </nav>
@@ -0,0 +1,18 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ import type { Crumb } from '../types.js';
3
+ interface Props extends HTMLAttributes<HTMLElement> {
4
+ /** Ordered trail, root first. The last entry is the current page and is never a link. */
5
+ items: Crumb[];
6
+ /** Accessible name of the landmark. Several trails on one page need distinct names. */
7
+ label?: string;
8
+ /**
9
+ * Separator drawn between crumbs. Decorative — it lives in a `::before` on the list item, so
10
+ * it is never read out or copied with the text.
11
+ */
12
+ separator?: string;
13
+ class?: string;
14
+ ref?: HTMLElement | null;
15
+ }
16
+ declare const Breadcrumbs: import("svelte").Component<Props, {}, "ref">;
17
+ type Breadcrumbs = ReturnType<typeof Breadcrumbs>;
18
+ export default Breadcrumbs;