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
@@ -3,6 +3,17 @@
3
3
  import MenuItemContent from "./MenuItemContent.svelte";
4
4
  import type { MenuItem as Item } from "./MenuTypes.js";
5
5
 
6
+ /**
7
+ * @description
8
+ * A single menu item within a menu list. Supports item, header, text, and divider types.
9
+ * Items can be links, buttons, or custom rendered content. Keyboard navigation is handled by the parent MenuDropdown.
10
+ * @cssprop --menu-item-font-size - Font size of the menu item. (Default: var(--font-size-sm))
11
+ * @cssprop --menu-item-padding-block - Block padding of the menu item. (Default: var(--space-xs))
12
+ * @cssprop --menu-item-padding-inline - Inline padding of the menu item. (Default: var(--space-md))
13
+ * @cssprop --menu-item-gap - Gap between icon and text within the item. (Default: var(--space-sm))
14
+ * @cssprop --menu-item-margin - Top margin of the first item. (Default: var(--space-xs))
15
+ * @cssprop --menu-shortcut-font-size - Font size of the keyboard shortcut label. (Default: max(0.75em, 9px))
16
+ */
6
17
  let {
7
18
  item,
8
19
  index,
@@ -11,11 +22,17 @@
11
22
  keyboardHasFocus,
12
23
  shape = 'default',
13
24
  }: {
25
+ /** The menu item data object. */
14
26
  item: Item;
27
+ /** The index of this item in the menu list. */
15
28
  index: number;
29
+ /** Callback when the mouse enters the item. */
16
30
  onmouseover?: (e: MouseEvent, item: Item, index: number) => void;
31
+ /** Callback when the item is selected. */
17
32
  onselect?: (item: Item, index: number) => void;
33
+ /** Whether keyboard navigation is active, suppressing mouse hover styles. */
18
34
  keyboardHasFocus?: boolean;
35
+ /** The border-radius shape of the item. */
19
36
  shape?: 'default' | 'rounded' | 'pill';
20
37
  } = $props();
21
38
 
@@ -104,20 +121,19 @@
104
121
  li .Item,
105
122
  li .Header,
106
123
  li .Text {
107
- font-size: var(--font-size, 0.9em);
124
+ font-size: var(--menu-item-font-size, var(--font-size-sm));
108
125
  text-align: left;
109
- padding-block: 0.5rem;
110
- padding-inline: 1rem;
126
+ padding-block: var(--menu-item-padding-block, var(--space-xs));
127
+ padding-inline: var(--menu-item-padding-inline, var(--space-md));
111
128
  display: inline-flex;
112
129
  align-items: center;
113
130
  justify-content: space-between;
114
131
  width: 100%;
115
- color: inherit;
116
132
  text-decoration: none;
117
133
  color: var(--color);
118
- --inset-block: 0.5rem;
119
- --inset-inline: 1rem;
120
- border-radius: none;
134
+ --inset-block: var(--menu-item-padding-block, var(--space-xs));
135
+ --inset-inline: var(--menu-item-padding-inline, var(--space-md));
136
+ border-radius: 0;
121
137
  white-space: nowrap;
122
138
  }
123
139
 
@@ -128,7 +144,7 @@
128
144
  }
129
145
 
130
146
  li .Header {
131
- font-weight: 600;
147
+ font-weight: var(--font-weight-medium);
132
148
  }
133
149
 
134
150
  li:not(.keyboardHasFocus) .Item:not(.Custom):hover,
@@ -146,7 +162,7 @@
146
162
  }
147
163
 
148
164
  li .Item span.Shortcut {
149
- font-size: max(0.75em, 9px);
165
+ font-size: var(--menu-shortcut-font-size, max(0.75em, 9px));
150
166
  text-align: right;
151
167
  color: var(--menu-text-color-subtle);
152
168
  white-space: nowrap;
@@ -157,23 +173,23 @@
157
173
  }
158
174
 
159
175
  li.divider {
160
- padding-block: 0.5rem;
176
+ padding-block: var(--menu-item-padding-block, var(--space-xs));
161
177
  }
162
178
 
163
179
  hr {
164
180
  display: block;
165
181
  border: none;
166
182
  margin: 0;
167
- border-top: 1px solid var(--menu-border-color);
183
+ border-top: var(--menu-border-size) var(--menu-border-style) var(--menu-border-color);
168
184
  }
169
185
 
170
186
  li:first-child[data-type="item"] {
171
- margin-block-start: var(--menu-item-margin, 0.5rem);
187
+ margin-block-start: var(--menu-item-margin, var(--space-xs));
172
188
  }
173
189
 
174
- @media (pointer: none) {
190
+ @media (pointer: coarse) {
175
191
  li .Item span.Shortcut {
176
192
  display: none;
177
- }
193
+ }
178
194
  }
179
195
  </style>
@@ -1,10 +1,16 @@
1
1
  import type { MenuItem as Item } from "./MenuTypes.js";
2
2
  type $$ComponentProps = {
3
+ /** The menu item data object. */
3
4
  item: Item;
5
+ /** The index of this item in the menu list. */
4
6
  index: number;
7
+ /** Callback when the mouse enters the item. */
5
8
  onmouseover?: (e: MouseEvent, item: Item, index: number) => void;
9
+ /** Callback when the item is selected. */
6
10
  onselect?: (item: Item, index: number) => void;
11
+ /** Whether keyboard navigation is active, suppressing mouse hover styles. */
7
12
  keyboardHasFocus?: boolean;
13
+ /** The border-radius shape of the item. */
8
14
  shape?: 'default' | 'rounded' | 'pill';
9
15
  };
10
16
  declare const MenuItem: import("svelte").Component<$$ComponentProps, {}, "">;
@@ -5,30 +5,20 @@
5
5
  import type { ModalButton } from "./ModalTypes.js";
6
6
 
7
7
  /**
8
- * A modal component using the native `<dialog>` element.
9
- *
10
- * **Pattern A: Trigger-based**
11
- * ```svelte
12
- * <Modal {trigger} {content} />
13
- * ```
14
- *
15
- * **Pattern B: Controlled state**
16
- * ```svelte
8
+ * @description
9
+ * A modal component using the native `<dialog>` element. Supports trigger-based, controlled, and auto-show patterns.
10
+ * Uses modal tokens for styling. The `contained` prop can be set via context.
11
+ * @cssprop --modal-width - Width of the modal. (Default: fit-content)
12
+ * @cssprop --modal-min-width - Minimum width. (Default: auto)
13
+ * @cssprop --modal-max-width - Maximum width. (Default: 40rem)
14
+ * @cssprop --modal-max-height - Maximum height. (Default: 80svh)
15
+ * @example
17
16
  * <Modal bind:open={showModal}>
18
17
  * {#snippet content(close)}
19
18
  * <p>Modal content</p>
19
+ * <button onclick={close}>Close</button>
20
20
  * {/snippet}
21
21
  * </Modal>
22
- * ```
23
- *
24
- * **Pattern C: Auto-show (no trigger)**
25
- * ```svelte
26
- * {#if shouldShow}
27
- * <Modal open onclose={() => shouldShow = false}>
28
- * {#snippet content(close)}...{/snippet}
29
- * </Modal>
30
- * {/if}
31
- * ```
32
22
  */
33
23
  let {
34
24
  open = $bindable(false),
@@ -241,6 +231,31 @@
241
231
  dialog.Modal[open] {
242
232
  display: grid;
243
233
  grid-template-rows: 1fr auto;
234
+ opacity: 1;
235
+ translate: 0 0;
236
+ transition:
237
+ opacity var(--transition-duration-fast) ease-out,
238
+ translate var(--transition-duration-fast) ease-out,
239
+ display var(--transition-duration-fast) allow-discrete,
240
+ overlay var(--transition-duration-fast) allow-discrete;
241
+ }
242
+
243
+ dialog.Modal[open]::backdrop {
244
+ opacity: 1;
245
+ transition:
246
+ opacity var(--transition-duration-fast) ease-out,
247
+ display var(--transition-duration-fast) allow-discrete,
248
+ overlay var(--transition-duration-fast) allow-discrete;
249
+ }
250
+
251
+ @starting-style {
252
+ dialog.Modal[open] {
253
+ opacity: 0;
254
+ translate: 0 var(--space-xs);
255
+ }
256
+ dialog.Modal[open]::backdrop {
257
+ opacity: 0;
258
+ }
244
259
  }
245
260
 
246
261
  dialog.Modal.contained {
@@ -306,7 +321,8 @@
306
321
  }
307
322
 
308
323
  @media (prefers-reduced-motion: reduce) {
309
- dialog.Modal {
324
+ dialog.Modal,
325
+ dialog.Modal::backdrop {
310
326
  transition: none;
311
327
  }
312
328
  }
@@ -1,10 +1,27 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from "svelte";
3
3
  import { BROWSER } from "esm-env";
4
+ import UIContent from "./UIContent.svelte";
4
5
 
5
6
  /**
6
- * A base popover component using the Popover API and CSS Anchor Positioning.
7
- * Use this as a foundation for dropdowns, tooltips, contextual help, etc.
7
+ * @description
8
+ * A base popover component using the native Popover API and CSS Anchor Positioning.
9
+ * Use as a foundation for dropdowns, tooltips, contextual help, etc.
10
+ * Supports trigger snippets, external anchors, placement with fallbacks, and light dismiss.
11
+ * @cssprop --popover-background - Background color. (Default: var(--surface-background))
12
+ * @cssprop --popover-border - Border shorthand. (Default: var(--surface-border))
13
+ * @cssprop --popover-border-radius - Border radius. (Default: var(--surface-border-radius))
14
+ * @cssprop --popover-shadow - Box shadow. (Default: var(--surface-shadow))
15
+ * @cssprop --popover-width - Width of the popover. (Default: max-content)
16
+ * @cssprop --popover-max-width - Maximum width. (Default: calc(100vw - 2rem))
17
+ * @cssprop --popover-max-height - Maximum height. (Default: calc(100vh - 2rem))
18
+ * @example
19
+ * <Popover placement="bottom-start">
20
+ * {#snippet trigger({ toggle, triggerAttrs })}
21
+ * <button {...triggerAttrs}>Open</button>
22
+ * {/snippet}
23
+ * <p>Popover content</p>
24
+ * </Popover>
8
25
  */
9
26
  let {
10
27
  open = $bindable(false),
@@ -191,7 +208,9 @@
191
208
  class:unstyled
192
209
  style={popoverStyle}
193
210
  >
194
- {@render children()}
211
+ <UIContent>
212
+ {@render children()}
213
+ </UIContent>
195
214
  </div>
196
215
 
197
216
  <style>
@@ -218,23 +237,34 @@
218
237
  }
219
238
 
220
239
  .Popover:popover-open {
221
- animation: popover-show 0.15s ease-out;
240
+ opacity: 1;
241
+ translate: 0 0;
242
+ transition:
243
+ opacity var(--transition-duration-fast) ease-out,
244
+ translate var(--transition-duration-fast) ease-out,
245
+ display var(--transition-duration-fast) allow-discrete;
222
246
  }
223
247
 
224
- @keyframes popover-show {
225
- from {
248
+ .Popover:not(:popover-open) {
249
+ opacity: 0;
250
+ translate: 0 calc(-1 * var(--space-xxs));
251
+ transition:
252
+ opacity var(--transition-duration-fast) ease-in,
253
+ translate var(--transition-duration-fast) ease-in,
254
+ display var(--transition-duration-fast) allow-discrete;
255
+ }
256
+
257
+ @starting-style {
258
+ .Popover:popover-open {
226
259
  opacity: 0;
227
- transform: translateY(-4px);
228
- }
229
- to {
230
- opacity: 1;
231
- transform: translateY(0);
260
+ translate: 0 calc(-1 * var(--space-xxs));
232
261
  }
233
262
  }
234
263
 
235
264
  @media (prefers-reduced-motion: reduce) {
236
- .Popover:popover-open {
237
- animation: none;
265
+ .Popover:popover-open,
266
+ .Popover:not(:popover-open) {
267
+ transition: none;
238
268
  }
239
269
  }
240
270
  </style>
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import { page } from "$app/stores";
2
+ import { page } from "$app/state";
3
3
  import { setContext, type Snippet } from "svelte";
4
4
  import type { TabbedContentItem } from "./MenuTypes.js";
5
5
  import Tabs from "./Tabs.svelte";
@@ -1,15 +1,29 @@
1
1
  <script lang="ts">
2
2
  import { getContext, type Snippet } from "svelte";
3
3
 
4
+ /**
5
+ * @description
6
+ * A single tab panel within a TabbedContent. Registers itself with the parent TabbedContent via context.
7
+ * @example
8
+ * <TabbedContent>
9
+ * <TabbedContentItem label="First">
10
+ * <p>First tab content</p>
11
+ * </TabbedContentItem>
12
+ * </TabbedContent>
13
+ */
4
14
  let {
5
15
  id,
6
16
  label,
7
17
  href,
8
18
  children,
9
19
  }: {
20
+ /** Unique identifier for this tab. Defaults to a slug of `label`. */
10
21
  id?: string;
22
+ /** The label displayed in the tab bar. */
11
23
  label: string;
24
+ /** Optional URL hash for this tab. */
12
25
  href?: string;
26
+ /** The content of this tab panel. */
13
27
  children: Snippet;
14
28
  } = $props();
15
29
 
@@ -1,8 +1,12 @@
1
1
  import { type Snippet } from "svelte";
2
2
  type $$ComponentProps = {
3
+ /** Unique identifier for this tab. Defaults to a slug of `label`. */
3
4
  id?: string;
5
+ /** The label displayed in the tab bar. */
4
6
  label: string;
7
+ /** Optional URL hash for this tab. */
5
8
  href?: string;
9
+ /** The content of this tab panel. */
6
10
  children: Snippet;
7
11
  };
8
12
  declare const TabbedContentItem: import("svelte").Component<$$ComponentProps, {}, "">;
@@ -1,6 +1,16 @@
1
1
  <script lang="ts">
2
2
  import { getContext, type Snippet } from "svelte";
3
3
 
4
+ /**
5
+ * @description
6
+ * A table wrapper that provides containment, rounding, hover highlights, and zebra striping.
7
+ * Styling is provided by the table tokens in the CSS layer. The `contained` prop can be set via context.
8
+ * @example
9
+ * <Table contained rounded hoverable>
10
+ * <thead><tr><th>Name</th><th>Value</th></tr></thead>
11
+ * <tbody><tr><td>Alpha</td><td>1</td></tr><tr><td>Beta</td><td>2</td></tr></tbody>
12
+ * </Table>
13
+ */
4
14
  let {
5
15
  colored,
6
16
  contained,
@@ -10,12 +20,19 @@
10
20
  fullWidth,
11
21
  children,
12
22
  }: {
23
+ /** Enable alternating row background colors. */
13
24
  colored?: boolean;
25
+ /** Wrap the table in a border. Inherits from `lutra.table.contained` or `lutra.contained` context. */
14
26
  contained?: boolean;
27
+ /** Round the corners of the container when contained. */
15
28
  rounded?: boolean;
29
+ /** Apply a negative inline margin to align table borders with surrounding content. */
16
30
  hang?: boolean;
31
+ /** Highlight rows on hover. */
17
32
  hoverable?: boolean;
33
+ /** Force the table to full width. */
18
34
  fullWidth?: boolean;
35
+ /** Table content (thead, tbody, etc.). */
19
36
  children: Snippet;
20
37
  } = $props();
21
38
 
@@ -39,3 +56,55 @@
39
56
  {@render children()}
40
57
  </table>
41
58
  </div>
59
+
60
+ <style>
61
+ .table-container {
62
+ overflow: clip;
63
+ }
64
+ .table-container.contained {
65
+ border: var(--table-border-size) var(--table-border-style) var(--table-border-color);
66
+ }
67
+ .table-container.rounded {
68
+ border-radius: var(--table-border-radius);
69
+ }
70
+ .table-container.hang {
71
+ margin-inline: calc(-1 * var(--table-cell-padding-inline));
72
+ }
73
+ table {
74
+ border-collapse: collapse;
75
+ width: 100%;
76
+ }
77
+ :global(th),
78
+ :global(td) {
79
+ padding-block: var(--table-cell-padding-block);
80
+ padding-inline: var(--table-cell-padding-inline);
81
+ text-align: start;
82
+ vertical-align: top;
83
+ color: var(--table-cell-color);
84
+ }
85
+ .contained :global(th),
86
+ .contained :global(td) {
87
+ border: none;
88
+ }
89
+ table:not(.contained) :global(th),
90
+ table:not(.contained) :global(td) {
91
+ border: var(--table-border-size) var(--table-border-style) var(--table-border-color);
92
+ }
93
+ :global(th) {
94
+ background-color: var(--table-header-background);
95
+ color: var(--table-header-color);
96
+ font-weight: var(--table-header-font-weight);
97
+ }
98
+ :global(tbody tr) {
99
+ background-color: var(--table-row-background);
100
+ }
101
+ .colored :global(tbody tr:nth-child(even)) {
102
+ background-color: var(--table-row-background-even);
103
+ }
104
+ .hoverable :global(tbody tr:hover) {
105
+ background-color: var(--table-row-background-hover);
106
+ }
107
+ .fullWidth {
108
+ width: 100%;
109
+ }
110
+ </style>
@@ -1,11 +1,18 @@
1
1
  import { type Snippet } from "svelte";
2
2
  type $$ComponentProps = {
3
+ /** Enable alternating row background colors. */
3
4
  colored?: boolean;
5
+ /** Wrap the table in a border. Inherits from `lutra.table.contained` or `lutra.contained` context. */
4
6
  contained?: boolean;
7
+ /** Round the corners of the container when contained. */
5
8
  rounded?: boolean;
9
+ /** Apply a negative inline margin to align table borders with surrounding content. */
6
10
  hang?: boolean;
11
+ /** Highlight rows on hover. */
7
12
  hoverable?: boolean;
13
+ /** Force the table to full width. */
8
14
  fullWidth?: boolean;
15
+ /** Table content (thead, tbody, etc.). */
9
16
  children: Snippet;
10
17
  };
11
18
  declare const Table: import("svelte").Component<$$ComponentProps, {}, "">;
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
- import { browser } from "$app/environment";
3
- import { page } from "$app/stores";
2
+ import { BROWSER } from 'esm-env';
3
+ import { page } from "$app/state";
4
4
  import type { TabItem } from "./MenuTypes.js";
5
5
  import { onMount } from "svelte";
6
6
 
@@ -8,6 +8,19 @@
8
8
  * @description
9
9
  * A tabbed navigation menu used to navigate between pages or sections of content.
10
10
  * Clicking a tab will change the URL or trigger a JavaScript event.
11
+ * @cssprop --tab-background-color - Background color of the tab bar. (Default: var(--menu-background-color))
12
+ * @cssprop --tab-background-color-hover - Background color of a tab on hover. (Default: var(--menu-background-color-hover))
13
+ * @cssprop --tab-background-color-active - Background color of the active tab. (Default: var(--background-selected))
14
+ * @cssprop --tab-text-color - Text color of tabs. (Default: var(--menu-text-color))
15
+ * @cssprop --tab-text-color-active - Text color of the active tab. (Default: var(--text-color-heading))
16
+ * @cssprop --tab-border-size - Border width. (Default: var(--menu-border-size))
17
+ * @cssprop --tab-border-style - Border style. (Default: var(--menu-border-style))
18
+ * @cssprop --tab-border-color - Border color. (Default: var(--menu-border-color))
19
+ * @cssprop --tab-border-radius - Border radius when rounded. (Default: var(--menu-border-radius))
20
+ * @cssprop --tab-gap - Gap between tabs when not contained. (Default: var(--space-md))
21
+ * @cssprop --tab-padding - Padding of each tab. (Default: var(--space-sm) var(--space-xs))
22
+ * @cssprop --tab-font-size - Font size of each tab label. (Default: var(--font-size-sm))
23
+ * @cssprop --tab-letter-spacing - Letter spacing of each tab label. (Default: -0.05ch)
11
24
  * @example
12
25
  * <Tabs contained rounded items={[
13
26
  * {
@@ -58,21 +71,15 @@
58
71
 
59
72
  let activeIndex = $derived.by(() => {
60
73
  return items.findIndex((item, index) => {
61
- console.log(item.href);
62
74
  if(!item.href) return false;
63
- // check if href ends in *
64
75
  if(item.href.endsWith('*')) {
65
- return $page.url.pathname.startsWith(item.href.slice(0, -1));
76
+ return page.url.pathname.startsWith(item.href.slice(0, -1));
66
77
  } else {
67
- return $page.url.pathname.endsWith(item.href);
78
+ return page.url.pathname.endsWith(item.href);
68
79
  }
69
80
  })
70
81
  });
71
82
 
72
- $effect(() => {
73
- console.log(activeIndex);
74
- });
75
-
76
83
  let loaded = $state(false);
77
84
 
78
85
  onMount(async () => {
@@ -81,7 +88,7 @@
81
88
  });
82
89
 
83
90
  let underlineStyle = $derived.by(() => {
84
- if(browser && underline && loaded) {
91
+ if(BROWSER && underline && loaded) {
85
92
  let active = items[activeIndex];
86
93
  let activeElement: HTMLElement | null = document.querySelector(`#${id} li[data-index="${activeIndex}"]`);
87
94
  if(activeElement) {
@@ -126,15 +133,14 @@
126
133
  <style>
127
134
  .Tabs {
128
135
  display: flex;
129
- z-index: 2;
130
- background: var(--menu-bg);
136
+ background: var(--tab-background-color, var(--menu-background-color));
131
137
  overflow: clip;
132
138
  }
133
139
  .Tabs.contained {
134
- border: var(--menu-border);
140
+ border: var(--tab-border-size, var(--menu-border-size)) var(--tab-border-style, var(--menu-border-style)) var(--tab-border-color, var(--menu-border-color));
135
141
  }
136
142
  .Tabs.rounded {
137
- border-radius: var(--border-radius);
143
+ border-radius: var(--tab-border-radius, var(--menu-border-radius));
138
144
  }
139
145
  menu, li {
140
146
  list-style: none;
@@ -148,9 +154,9 @@
148
154
  align-items: center;
149
155
  justify-content: flex-start;
150
156
  padding: 0;
151
- gap: var(--gap, 1rem);
157
+ gap: var(--tab-gap, var(--space-md));
152
158
  inline-size: 100%;
153
- border-block-end: var(--menu-border);
159
+ border-block-end: var(--tab-border-size, var(--menu-border-size)) var(--tab-border-style, var(--menu-border-style)) var(--tab-border-color, var(--menu-border-color));
154
160
  }
155
161
  .Tabs.contained menu {
156
162
  gap: 0;
@@ -161,15 +167,15 @@
161
167
  a,
162
168
  button {
163
169
  display: block;
164
- padding: var(--padding, 0.75rem 0.5rem);
165
- color: var(--menu-text);
166
- transition: all var(--menu-trans);
167
- font-weight: 500;
168
- font-size: var(--font-size, 0.9em);
169
- letter-spacing: -0.05ch;
170
+ padding: var(--tab-padding, var(--space-sm) var(--space-xs));
171
+ color: var(--tab-text-color, var(--menu-text-color));
172
+ transition: all var(--transition-duration-fast);
173
+ font-weight: var(--font-weight-normal);
174
+ font-size: var(--tab-font-size, var(--font-size-sm));
175
+ letter-spacing: var(--tab-letter-spacing, -0.05ch);
170
176
  background: transparent;
171
177
  border: none;
172
- border-block-end: 2px solid transparent;
178
+ border-block-end: var(--border-size-thick) solid transparent;
173
179
  cursor: pointer;
174
180
  text-decoration: none;
175
181
  }
@@ -178,7 +184,7 @@
178
184
  }
179
185
  .Tabs.contained li {
180
186
  flex-grow: 1;
181
- border-inline-end: var(--menu-border);
187
+ border-inline-end: var(--tab-border-size, var(--menu-border-size)) var(--tab-border-style, var(--menu-border-style)) var(--tab-border-color, var(--menu-border-color));
182
188
  flex-basis: auto;
183
189
  }
184
190
  .Tabs.contained menu li:last-of-type {
@@ -189,28 +195,30 @@
189
195
  flex-grow: 1;
190
196
  inline-size: 100%;
191
197
  text-align: center;
192
- padding-block-start: calc(0.75rem + 3px);
193
- color: var(--menu-text);
194
- font-weight: 600;
198
+ padding-block-start: calc(var(--space-sm) + var(--border-size-thick));
199
+ color: var(--tab-text-color, var(--menu-text-color));
200
+ font-weight: var(--font-weight-medium);
195
201
  }
196
202
  a:hover,
197
- button:hover {
198
- color: color-mix(in hsl shorter hue, var(--menu-text) var(--mix-amount), var(--mix-target));
199
- background-color: color-mix(in hsl shorter hue, var(--menu-bg) var(--mix-amount), var(--mix-target));
203
+ button:hover,
204
+ a:focus-visible,
205
+ button:focus-visible {
206
+ background-color: var(--tab-background-color-hover, var(--menu-background-color-hover));
207
+ outline: none;
200
208
  }
201
209
  li[aria-current="page"] a,
202
210
  li[aria-current="page"] button {
203
- background: var(--menu-bg-active);
204
- color: var(--menu-text-active);
211
+ background: var(--tab-background-color-active, var(--background-selected));
212
+ color: var(--tab-text-color-active, var(--text-color-heading));
205
213
  opacity: 1;
206
214
  }
207
215
  .Underline {
208
- height: 2px;
209
- background-color: var(--menu-text-active);
216
+ height: var(--border-size-thick);
217
+ background-color: var(--tab-text-color-active, var(--text-color-heading));
210
218
  position: absolute;
211
219
  bottom: 0;
212
220
  left: 0;
213
- transition: all 0.2s ease-out;
221
+ transition: all var(--transition-duration-fast) ease-out;
214
222
  opacity: 0;
215
223
  }
216
224
  </style>