lutra 0.1.68 → 0.1.70

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 (84) hide show
  1. package/dist/components/AspectRatio.svelte +19 -9
  2. package/dist/components/AspectRatio.svelte.d.ts +2 -1
  3. package/dist/components/Avatar.svelte +5 -8
  4. package/dist/components/Close.svelte +24 -27
  5. package/dist/components/Close.svelte.d.ts +2 -0
  6. package/dist/components/ContextTip.svelte +3 -2
  7. package/dist/components/DataList.svelte +111 -0
  8. package/dist/components/DataList.svelte.d.ts +10 -0
  9. package/dist/components/DataListTypes.d.ts +14 -0
  10. package/dist/components/DataListTypes.js +1 -0
  11. package/dist/components/Dialog.svelte +38 -0
  12. package/dist/components/Icon.svelte +2 -2
  13. package/dist/components/IconButton.svelte +10 -22
  14. package/dist/components/Image.svelte +2 -2
  15. package/dist/components/Indicator.svelte +2 -1
  16. package/dist/components/Inset.svelte +13 -0
  17. package/dist/components/Layout.svelte +7 -3
  18. package/dist/components/Layout.svelte.d.ts +3 -2
  19. package/dist/components/MenuDropdown.svelte +12 -2
  20. package/dist/components/MenuItem.svelte +30 -14
  21. package/dist/components/MenuItem.svelte.d.ts +6 -0
  22. package/dist/components/Modal.svelte +36 -20
  23. package/dist/components/Popover.svelte +43 -13
  24. package/dist/components/TabbedContent.svelte +1 -1
  25. package/dist/components/TabbedContentItem.svelte +14 -0
  26. package/dist/components/TabbedContentItem.svelte.d.ts +4 -0
  27. package/dist/components/Table.svelte +69 -0
  28. package/dist/components/Table.svelte.d.ts +7 -0
  29. package/dist/components/Tabs.svelte +44 -36
  30. package/dist/components/Tag.svelte +53 -13
  31. package/dist/components/Tag.svelte.d.ts +4 -0
  32. package/dist/components/Theme.svelte +121 -94
  33. package/dist/components/Theme.svelte.d.ts +7 -6
  34. package/dist/components/Toast.svelte +11 -8
  35. package/dist/components/Tooltip.svelte +17 -10
  36. package/dist/components/index.d.ts +2 -0
  37. package/dist/components/index.js +2 -0
  38. package/dist/css/1-props.css +197 -163
  39. package/dist/css/2-init.css +519 -0
  40. package/dist/css/{2-base.css → 3-base.css} +42 -131
  41. package/dist/css/{3-typo.css → 4-typo.css} +3 -1
  42. package/dist/css/lutra.css +7 -6
  43. package/dist/css/themes/DefaultTheme.css +26 -4
  44. package/dist/form/Button.svelte +20 -0
  45. package/dist/form/Button.svelte.d.ts +9 -0
  46. package/dist/form/Datepicker.svelte +13 -0
  47. package/dist/form/Datepicker.svelte.d.ts +3 -0
  48. package/dist/form/FieldContent.svelte +20 -11
  49. package/dist/form/FieldError.svelte +1 -1
  50. package/dist/form/FieldGroup.svelte +84 -0
  51. package/dist/form/FieldGroup.svelte.d.ts +20 -0
  52. package/dist/form/Fieldset.svelte +19 -11
  53. package/dist/form/Form.svelte +137 -63
  54. package/dist/form/Form.svelte.d.ts +21 -0
  55. package/dist/form/FormActions.svelte +21 -3
  56. package/dist/form/FormActions.svelte.d.ts +3 -0
  57. package/dist/form/FormSection.svelte +22 -20
  58. package/dist/form/ImageUpload.svelte +50 -30
  59. package/dist/form/ImageUpload.svelte.d.ts +14 -0
  60. package/dist/form/Input.svelte +62 -30
  61. package/dist/form/Input.svelte.d.ts +0 -1
  62. package/dist/form/InputLength.svelte +5 -5
  63. package/dist/form/Label.svelte +6 -6
  64. package/dist/form/LogoUpload.svelte +24 -10
  65. package/dist/form/Select.svelte +23 -10
  66. package/dist/form/Select.svelte.d.ts +6 -6
  67. package/dist/form/Textarea.svelte +11 -1
  68. package/dist/form/Toggle.svelte +162 -0
  69. package/dist/form/Toggle.svelte.d.ts +31 -17
  70. package/dist/form/client.svelte.js +0 -2
  71. package/dist/form/index.d.ts +1 -0
  72. package/dist/form/index.js +1 -0
  73. package/dist/state/Persisted.svelte.d.ts +6 -0
  74. package/dist/state/Persisted.svelte.js +29 -0
  75. package/dist/state/theme.svelte.d.ts +7 -0
  76. package/dist/state/theme.svelte.js +14 -0
  77. package/dist/types.d.ts +6 -23
  78. package/dist/types.js +0 -17
  79. package/dist/util/color.js +2 -2
  80. package/package.json +5 -4
  81. package/dist/config.d.ts +0 -30
  82. package/dist/config.js +0 -18
  83. /package/dist/css/{4-layout.css → 5-layout.css} +0 -0
  84. /package/dist/css/{5-media.css → 6-media.css} +0 -0
@@ -1,26 +1,36 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from "svelte";
3
+
4
+ /**
5
+ * @description
6
+ * A wrapper that enforces an aspect ratio on its children. Accepts numeric, ratio string, or colon-separated formats.
7
+ * @cssprop --aspect-ratio - The aspect ratio of the container. (Default: 16 / 9)
8
+ * @example
9
+ * <AspectRatio ratio="4:3">
10
+ * <img src="photo.jpg" alt="Photo" style="width: 100%; height: 100%; object-fit: cover;" />
11
+ * </AspectRatio>
12
+ */
3
13
  let {
4
- ratio = 16 / 9,
14
+ ratio = "16 / 9",
5
15
  children
6
16
  }: {
7
- ratio?: number;
17
+ /** Aspect ratio as a number (e.g. 1.778), a ratio string (e.g. "16 / 9"), or colon-separated (e.g. "16:9"). */
18
+ ratio?: number | string;
8
19
  children: Snippet;
9
20
  } = $props();
21
+
22
+ const cssRatio = $derived(
23
+ typeof ratio === "string" ? ratio.replace(":", " / ") : ratio
24
+ );
10
25
  </script>
11
26
 
12
- <div class="AspectRatio" style="--aspect-ratio: {ratio}">
27
+ <div class="AspectRatio" style="--aspect-ratio: {cssRatio}">
13
28
  {@render children()}
14
29
  </div>
15
30
 
16
31
  <style>
17
- @property --aspect-ratio {
18
- syntax: "<number-percentage> / <number-percentage>";
19
- inherits: false;
20
- initial-value: 16 / 9;
21
- }
22
32
  .AspectRatio {
23
- aspect-ratio: var(--aspect-ratio);
33
+ aspect-ratio: var(--aspect-ratio, 16 / 9);
24
34
  }
25
35
  </style>
26
36
 
@@ -1,6 +1,7 @@
1
1
  import type { Snippet } from "svelte";
2
2
  type $$ComponentProps = {
3
- ratio?: number;
3
+ /** Aspect ratio as a number (e.g. 1.778), a ratio string (e.g. "16 / 9"), or colon-separated (e.g. "16:9"). */
4
+ ratio?: number | string;
4
5
  children: Snippet;
5
6
  };
6
7
  declare const AspectRatio: import("svelte").Component<$$ComponentProps, {}, "">;
@@ -9,6 +9,9 @@
9
9
  * You can pick from three crop styles: circle, square, or rounded. The color of the placeholder is based on the name, making each visually distinct from each other.
10
10
  * @cssprop --border-radius - The border radius of the avatar when cropped as rounded.
11
11
  * @cssprop --mask-image - Custom mask image to use for the avatar.
12
+ * @cssprop --size - The size of the avatar. (Default: 3rem)
13
+ * @cssprop --user-color - The background color of the avatar. (Default: #666666)
14
+ * @cssprop --text-color - The text color of the avatar. (Default: rgba(0,0,0,0.85))
12
15
  * @example
13
16
  * <p>With picture:</p>
14
17
  * <Avatar name="Auth70" shape="rounded" src="https://avatars.githubusercontent.com/u/122825113?s=200&v=4" --size="4rem" />
@@ -84,11 +87,6 @@
84
87
  .Avatar.circle { border-radius: 50%; }
85
88
  .Avatar.square { border-radius: 0; }
86
89
  .Avatar.rounded { border-radius: var(--border-radius); }
87
- .Avatar img {
88
- block-size: 100%;
89
- inline-size: 100%;
90
- object-fit: cover;
91
- }
92
90
  .Avatar .Placeholder {
93
91
  display: flex;
94
92
  align-items: center;
@@ -97,9 +95,8 @@
97
95
  inline-size: 100%;
98
96
  background-color: var(--user-color);
99
97
  color: var(--text-color);
100
- user-select: none;
101
- font-size: 1rem;
102
- font-weight: 600;
98
+ font-size: calc(var(--size, 3rem) * 0.4);
99
+ font-weight: var(--font-weight-medium);
103
100
  user-select: none;
104
101
  }
105
102
  </style>
@@ -1,9 +1,22 @@
1
1
  <script lang="ts">
2
+ /**
3
+ * @description
4
+ * A close button rendered as an X icon. Can be absolutely positioned in a corner of its containing element.
5
+ * @cssprop --close-padding - Padding around the icon. (Default: var(--space-xs))
6
+ * @cssprop --close-icon-size - Size of the close icon. (Default: max(1.5rem, 1rem))
7
+ * @example
8
+ * <div style="position: relative; padding: 2rem; border: 1px solid gray;">
9
+ * <Close position="top right" onclick={() => alert('closed')} />
10
+ * <p>Content with a close button</p>
11
+ * </div>
12
+ */
2
13
  let {
3
14
  onclick,
4
15
  position
5
16
  }: {
17
+ /** Callback when the close button is clicked. */
6
18
  onclick?: (e: MouseEvent) => void;
19
+ /** Absolute position within the parent container. */
7
20
  position?: "top left" | "top right" | "bottom left" | "bottom right";
8
21
  } = $props();
9
22
  </script>
@@ -18,10 +31,9 @@
18
31
  <style>
19
32
  .Close {
20
33
  cursor: pointer;
21
- padding: var(--close-padding, 0.5rem);
34
+ padding: var(--close-padding, var(--space-xs));
22
35
  border-radius: 50%;
23
- color: var(--text-color, light-dark(black, white));
24
- cursor: pointer;
36
+ color: var(--text-color-p);
25
37
  pointer-events: auto;
26
38
  border: none;
27
39
  }
@@ -40,37 +52,22 @@
40
52
  svg {
41
53
  display: block;
42
54
  margin: 0;
43
- width: max(1.5rem, 16px);
44
- height: max(1.5rem, 16px);
55
+ width: var(--close-icon-size, max(1.5rem, 1rem));
56
+ height: var(--close-icon-size, max(1.5rem, 1rem));
45
57
  }
46
58
 
47
59
  .Close:hover {
48
- color: var(--text-color-subtle, light-dark(rgba(0, 0, 0, 0.5), rgba(255, 255, 255, 0.5)));
49
- }
50
-
51
- .Close.top {
52
- position: absolute;
53
- top: 0;
54
- right: 0;
55
- z-index: 100;
60
+ color: var(--text-color-p-subtle);
56
61
  }
57
62
 
63
+ .Close.top,
58
64
  .Close.bottom {
59
65
  position: absolute;
60
- bottom: 0;
61
- right: 0;
62
- }
63
-
64
- .Close.left {
65
- position: absolute;
66
- top: 0;
67
- left: 0;
68
- right: auto;
66
+ z-index: 100;
69
67
  }
70
68
 
71
- .Close.right {
72
- position: absolute;
73
- top: 0;
74
- right: 0;
75
- }
69
+ .Close.top.right { inset: 0 0 auto auto; }
70
+ .Close.top.left { inset: 0 auto auto 0; }
71
+ .Close.bottom.right { inset: auto 0 0 auto; }
72
+ .Close.bottom.left { inset: auto auto 0 0; }
76
73
  </style>
@@ -1,5 +1,7 @@
1
1
  type $$ComponentProps = {
2
+ /** Callback when the close button is clicked. */
2
3
  onclick?: (e: MouseEvent) => void;
4
+ /** Absolute position within the parent container. */
3
5
  position?: "top left" | "top right" | "bottom left" | "bottom right";
4
6
  };
5
7
  declare const Close: import("svelte").Component<$$ComponentProps, {}, "">;
@@ -18,7 +18,7 @@
18
18
  </script>
19
19
 
20
20
  <Tooltip {tip}>
21
- <a href="#contexttip" onclick={(e) => { e.preventDefault(); e.stopPropagation(); }}>
21
+ <a class="ContextTip" href="#contexttip" onclick={(e) => { e.preventDefault(); e.stopPropagation(); }}>
22
22
  <Icon icon={Help} --icon-width="16px" --icon-height="16px" --cursor="help" --vertical-align="baseline" />
23
23
  </a>
24
24
  </Tooltip>
@@ -35,7 +35,8 @@
35
35
  justify-content: center;
36
36
  }
37
37
  a:focus-visible {
38
- color: var(--focus-ring-color);
38
+ outline: var(--focus-ring);
39
39
  outline-offset: 0px;
40
+ color: var(--focus-ring-color);
40
41
  }
41
42
  </style>
@@ -0,0 +1,111 @@
1
+ <script lang="ts">
2
+ import type { DataListItem } from "./DataListTypes.js";
3
+
4
+ /**
5
+ * @description
6
+ * Renders an array of label/value pairs as a semantic description list (`<dl>`).
7
+ * Supports horizontal (side-by-side), vertical (stacked), or auto (container-query
8
+ * responsive) layouts. Values can be plain strings, snippets, render functions,
9
+ * or components.
10
+ *
11
+ * @cssprop --data-list-gap -- Gap between items in the list.
12
+ *
13
+ * @example
14
+ * <DataList items={[
15
+ * { label: 'Name', value: 'Alice' },
16
+ * { label: 'Email', value: 'alice@example.com' },
17
+ * ]} />
18
+ */
19
+ let {
20
+ items,
21
+ direction = 'auto',
22
+ }: {
23
+ /** The data items to display. */
24
+ items: DataListItem[];
25
+ /** Layout direction. 'auto' switches from horizontal to vertical based on container width. */
26
+ direction?: 'horizontal' | 'vertical' | 'auto';
27
+ } = $props();
28
+ </script>
29
+
30
+ <div class="DataList">
31
+ <dl class={direction}>
32
+ {#each items as item}
33
+ <div>
34
+ <dt>{item.label}</dt>
35
+ <dd>
36
+ {#if item.snippet}
37
+ {@render item.snippet()}
38
+ {:else if item.render}
39
+ {@render item.render()}
40
+ {:else if item.component}
41
+ <item.component />
42
+ {:else if item.value}
43
+ {item.value}
44
+ {/if}
45
+ </dd>
46
+ </div>
47
+ {/each}
48
+ </dl>
49
+ </div>
50
+
51
+ <style>
52
+ .DataList {
53
+ container-type: inline-size;
54
+ }
55
+
56
+ dl {
57
+ margin: 0;
58
+ padding: 0;
59
+ }
60
+
61
+ dt {
62
+ color: var(--text-color-p-subtle);
63
+ }
64
+
65
+ dd {
66
+ margin: 0;
67
+ color: var(--text-color-p);
68
+ }
69
+
70
+ /**
71
+ * Horizontal layout: two-column grid with subgrid for aligned labels
72
+ */
73
+ dl.horizontal,
74
+ dl.auto {
75
+ display: grid;
76
+ grid-template-columns: auto 1fr;
77
+ gap: var(--data-list-gap);
78
+ }
79
+
80
+ dl.horizontal > div,
81
+ dl.auto > div {
82
+ display: grid;
83
+ grid-column: 1 / -1;
84
+ grid-template-columns: subgrid;
85
+ align-items: baseline;
86
+ }
87
+
88
+ /**
89
+ * Vertical layout: stacked label over value
90
+ */
91
+ dl.vertical {
92
+ display: flex;
93
+ flex-direction: column;
94
+ gap: var(--data-list-gap);
95
+ }
96
+
97
+ /**
98
+ * Auto: switch to vertical when the container is narrow
99
+ */
100
+ @container (max-width: 300px) {
101
+ dl.auto {
102
+ display: flex;
103
+ flex-direction: column;
104
+ }
105
+
106
+ dl.auto > div {
107
+ display: flex;
108
+ flex-direction: column;
109
+ }
110
+ }
111
+ </style>
@@ -0,0 +1,10 @@
1
+ import type { DataListItem } from "./DataListTypes.js";
2
+ type $$ComponentProps = {
3
+ /** The data items to display. */
4
+ items: DataListItem[];
5
+ /** Layout direction. 'auto' switches from horizontal to vertical based on container width. */
6
+ direction?: 'horizontal' | 'vertical' | 'auto';
7
+ };
8
+ declare const DataList: import("svelte").Component<$$ComponentProps, {}, "">;
9
+ type DataList = ReturnType<typeof DataList>;
10
+ export default DataList;
@@ -0,0 +1,14 @@
1
+ import type { Component, Snippet } from "svelte";
2
+ import type { RenderFn } from "../types.js";
3
+ export type DataListItem = {
4
+ /** Text label for the item (e.g. "Status", "Email") */
5
+ label: string;
6
+ /** Text value to display */
7
+ value?: string;
8
+ /** Snippet to render as the value */
9
+ snippet?: Snippet;
10
+ /** Render function for the value (works in object literals) */
11
+ render?: RenderFn;
12
+ /** Component to render as the value */
13
+ component?: Component;
14
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -2,8 +2,14 @@
2
2
  import type { Snippet } from "svelte";
3
3
 
4
4
  /**
5
+ * @description
5
6
  * A simple dialog component using the native `<dialog>` element.
6
7
  * For more features (buttons, scrim control, etc.), use `<Modal>` instead.
8
+ * Uses modal tokens for styling (background, border, padding, shadow, etc.).
9
+ * @example
10
+ * <Dialog bind:open={showDialog} title="Confirm">
11
+ * <p>Are you sure?</p>
12
+ * </Dialog>
7
13
  */
8
14
  let {
9
15
  open = $bindable(false),
@@ -81,6 +87,38 @@
81
87
  .Dialog[open] {
82
88
  display: grid;
83
89
  grid-template-rows: auto 1fr;
90
+ opacity: 1;
91
+ translate: 0 0;
92
+ transition:
93
+ opacity var(--transition-duration-fast) ease-out,
94
+ translate var(--transition-duration-fast) ease-out,
95
+ display var(--transition-duration-fast) allow-discrete,
96
+ overlay var(--transition-duration-fast) allow-discrete;
97
+ }
98
+
99
+ .Dialog[open]::backdrop {
100
+ opacity: 1;
101
+ transition:
102
+ opacity var(--transition-duration-fast) ease-out,
103
+ display var(--transition-duration-fast) allow-discrete,
104
+ overlay var(--transition-duration-fast) allow-discrete;
105
+ }
106
+
107
+ @starting-style {
108
+ .Dialog[open] {
109
+ opacity: 0;
110
+ translate: 0 var(--space-xs);
111
+ }
112
+ .Dialog[open]::backdrop {
113
+ opacity: 0;
114
+ }
115
+ }
116
+
117
+ @media (prefers-reduced-motion: reduce) {
118
+ .Dialog,
119
+ .Dialog::backdrop {
120
+ transition: none;
121
+ }
84
122
  }
85
123
 
86
124
  .DialogHeader {
@@ -4,8 +4,8 @@
4
4
  /**
5
5
  * @description
6
6
  * A component that displays an icon. It can be an image url or a component. The icon will be centered in the container.
7
- * @cssprop --icon-width - The width of the icon. (Default: var(--font-size, 1em))
8
- * @cssprop --icon-height - The height of the icon. (Default: var(--font-size, 1em))
7
+ * @cssprop --icon-width - The width of the icon. (Default: font-size or 1rem)
8
+ * @cssprop --icon-height - The height of the icon. (Default: font-size or 1rem)
9
9
  * @cssprop --icon-color - The color of the icon. (Default: var(--text-color, currentColor))
10
10
  * @example
11
11
  * <script>
@@ -9,7 +9,7 @@
9
9
  * A component that displays an icon with a possible label. It can also have a click event.
10
10
  * The button has a padding of 0.75em by default to make it easier to tap on mobile devices. The padding can be changed using the `--padding` CSS variable.
11
11
  * Icon and text color will be inherited from the parent element.
12
- * @cssprop --padding - The padding of the icon button. (Default: 0.75em)
12
+ * @cssprop --padding - The padding of the icon button. (Default: var(--space-sm))
13
13
  * @example
14
14
  * <script>
15
15
  * import Copy from 'lutra/Icons/Copy.svelte';
@@ -71,10 +71,11 @@
71
71
  color: inherit;
72
72
  opacity: 1;
73
73
  background-color: var(--field-background-color, transparent);
74
- transition: background-color 0.04s;
74
+ transition: background-color var(--transition-duration-fast);
75
75
  border-radius: var(--field-border-radius);
76
76
  }
77
- .IconButton:hover {
77
+ .IconButton:hover,
78
+ .IconButton:focus-visible {
78
79
  background-color: var(--menu-background-color-hover);
79
80
  }
80
81
  .IconButton:active {
@@ -84,37 +85,24 @@
84
85
  border: none;
85
86
  background: none;
86
87
  cursor: pointer;
87
- color: var(--text-color-p, light-dark(black, white));
88
88
  }
89
89
  .IconMask {
90
90
  height: 100%;
91
- padding-inline: calc(var(--padding, 0.75em) * 0.8);
92
- padding-block: calc(var(--padding, 0.75em) * 0.8);
91
+ padding-inline: calc(var(--padding, var(--space-sm)) * 0.8);
92
+ padding-block: calc(var(--padding, var(--space-sm)) * 0.8);
93
93
  display: inline-grid;
94
- gap: 0.5rem;
94
+ gap: var(--space-xs);
95
95
  grid-template: "icon";
96
96
  align-items: center;
97
97
  }
98
98
  .IconMask.mask {
99
- -webkit-mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 0), rgba(0, 0, 0, 1) 35%, rgba(0, 0, 0, 1) 65%, rgba(0, 0, 0, 0));
100
- mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 0), rgba(0, 0, 0, 1) 35%, rgba(0, 0, 0, 1) 65%, rgba(0, 0, 0, 0));
99
+ -webkit-mask-image: linear-gradient(to bottom, transparent, black 35%, black 65%, transparent);
100
+ mask-image: linear-gradient(to bottom, transparent, black 35%, black 65%, transparent);
101
101
  }
102
102
  .IconContent {
103
103
  grid-area: icon;
104
104
  display: inline-flex;
105
- gap: 0.5rem;
105
+ gap: var(--space-xs);
106
106
  align-items: center;
107
107
  }
108
- @media(max-width: 960px) {
109
- .IconMask {
110
- padding-inline: calc(var(--padding, 0.75em) * 0.75);
111
- padding-block: calc(var(--padding, 0.75em) * 0.75);
112
- }
113
- }
114
- @media(max-width: 320px) {
115
- .IconMask {
116
- padding-inline: calc(var(--padding, 0.75em) * 0.6);
117
- padding-block: calc(var(--padding, 0.75em) * 0.6);
118
- }
119
- }
120
108
  </style>
@@ -9,7 +9,7 @@
9
9
  * <Image aspectRatio="16:9" fit="cover" src="https://images.unsplash.com/photo-1712337646541-d0c6f85447f8" alt="An example image" />
10
10
  */
11
11
 
12
- import { browser } from "$app/environment";
12
+ import { BROWSER } from 'esm-env';
13
13
  import { decode } from "blurhash";
14
14
  import { fade } from "svelte/transition";
15
15
 
@@ -94,7 +94,7 @@
94
94
  }
95
95
 
96
96
  let decoded = $state(false);
97
- let loaded = $state(browser ? false : true);
97
+ let loaded = $state(BROWSER ? false : true);
98
98
 
99
99
  const onload = () => {
100
100
  loaded = true;
@@ -43,7 +43,7 @@
43
43
  let _label = $derived(isSet ? StatusColors[color as StatusColor] : label ? label : 'status');
44
44
  </script>
45
45
 
46
- <span role="status" aria-label="{_label}" class="Indicator {color} {motion}" style="--bgColor: {isSet ? 'var(--status-'+color+')' : color};"></span>
46
+ <span role="status" aria-label="{_label}" class="Indicator {color} {motion}" style="--bgColor: {isSet ? 'var(--status-'+color+'-color)' : color};"></span>
47
47
 
48
48
  <style>
49
49
  .Indicator {
@@ -242,6 +242,7 @@
242
242
  --mask: radial-gradient(circle, rgba(0, 0, 0, 0) 20%, rgba(0, 0, 0, 0) 45%, black 50%, black 100%);
243
243
  -webkit-mask-image: var(--mask);
244
244
  mask-image: var(--mask);
245
+ mask-size: 100% 100%;
245
246
  filter: drop-shadow(0 0 calc(var(--isize) * 0.05) var(--bgColor));
246
247
  }
247
248
  .Indicator.spin::after,
@@ -1,6 +1,19 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from "svelte";
3
3
 
4
+ /**
5
+ * @description
6
+ * Negates the padding of a parent container by applying negative margins, allowing child content (e.g. images) to span edge-to-edge.
7
+ * Reads `--inset-block` and `--inset-inline` from the parent context (typically set by MenuItem or similar).
8
+ * @cssprop --inset-block - The block inset to negate. (Default: 0)
9
+ * @cssprop --inset-inline - The inline inset to negate. (Default: 0)
10
+ * @example
11
+ * <div style="padding: 1rem; --inset-block: 1rem; --inset-inline: 1rem;">
12
+ * <Inset>
13
+ * <img src="banner.jpg" alt="Full-bleed banner" />
14
+ * </Inset>
15
+ * </div>
16
+ */
4
17
  let {
5
18
  children,
6
19
  }: {
@@ -1,22 +1,26 @@
1
1
  <script lang="ts">
2
- import type { Snippet } from "svelte";
2
+ import { getContext, type Snippet } from "svelte";
3
3
  import "../css/lutra.css";
4
4
  import Theme from "./Theme.svelte";
5
5
  import ToastContainer from "./ToastContainer.svelte";
6
+ import { getContextItem, LutraContext, type LutraContextTypeMap, type LutraTheme } from "../types.js";
7
+
8
+ const lutra = getContext<() => LutraContextTypeMap>('lutra');
6
9
 
7
10
  /**
8
11
  * Default layout component that imports styles and provides theming.
9
12
  * Includes ToastContainer for toast notifications.
10
13
  */
11
14
  let {
12
- theme,
15
+ theme = lutra()?.[LutraContext.Theme]?.() ?? "system",
13
16
  children,
14
17
  }: {
15
18
  /** The theme to use. Leave empty for auto-detection. */
16
- theme?: 'light' | 'dark' | undefined;
19
+ theme?: LutraTheme;
17
20
  /** The content to display. */
18
21
  children: Snippet;
19
22
  } = $props();
23
+
20
24
  </script>
21
25
 
22
26
  <Theme theme={theme}>
@@ -1,8 +1,9 @@
1
- import type { Snippet } from "svelte";
1
+ import { type Snippet } from "svelte";
2
2
  import "../css/lutra.css";
3
+ import { type LutraTheme } from "../types.js";
3
4
  type $$ComponentProps = {
4
5
  /** The theme to use. Leave empty for auto-detection. */
5
- theme?: 'light' | 'dark' | undefined;
6
+ theme?: LutraTheme;
6
7
  /** The content to display. */
7
8
  children: Snippet;
8
9
  };
@@ -7,8 +7,18 @@
7
7
  import { arrowNavigation, matchOnType } from "../util/keyboard.svelte.js";
8
8
 
9
9
  /**
10
- * A dropdown menu using the base Popover component.
11
- * Handles menu-specific keyboard navigation and item rendering.
10
+ * @description
11
+ * A dropdown menu built on the Popover component. Handles keyboard navigation (arrow keys, type-ahead)
12
+ * and renders MenuItem entries. Uses menu tokens for styling via the Popover's CSS variable overrides.
13
+ * @example
14
+ * <MenuDropdown
15
+ * trigger="Options"
16
+ * items={[
17
+ * { type: 'item', text: 'Edit', onclick: () => {} },
18
+ * { type: 'divider' },
19
+ * { type: 'item', text: 'Delete', color: 'alert', onclick: () => {} },
20
+ * ]}
21
+ * />
12
22
  */
13
23
  let {
14
24
  open = $bindable(false),