@lyeve-labs/ui-kit 0.17.1 → 0.19.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 (65) hide show
  1. package/README.md +27 -17
  2. package/dist/components/AccordionItem.svelte +2 -2
  3. package/dist/components/AccountMenu.svelte +1 -1
  4. package/dist/components/AppShell.svelte +22 -8
  5. package/dist/components/Autocomplete.svelte +13 -4
  6. package/dist/components/AvatarGroup.svelte +2 -2
  7. package/dist/components/Breadcrumb.svelte +1 -1
  8. package/dist/components/Button.svelte +34 -9
  9. package/dist/components/ButtonGroup.svelte +3 -3
  10. package/dist/components/Card.svelte +2 -1
  11. package/dist/components/Checkbox.svelte +1 -1
  12. package/dist/components/CheckboxGroup.svelte +1 -1
  13. package/dist/components/Collapsible.svelte +4 -4
  14. package/dist/components/CopyButton.svelte +1 -1
  15. package/dist/components/DatePicker.svelte +3 -3
  16. package/dist/components/DateTimePicker.svelte +1 -1
  17. package/dist/components/Drawer.svelte +7 -27
  18. package/dist/components/Dropdown.svelte +197 -11
  19. package/dist/components/Dropdown.svelte.d.ts +12 -0
  20. package/dist/components/Field.svelte +1 -1
  21. package/dist/components/Input.svelte +1 -1
  22. package/dist/components/Label.svelte +1 -1
  23. package/dist/components/Logo.svelte +77 -0
  24. package/dist/components/Logo.svelte.d.ts +35 -0
  25. package/dist/components/Modal.svelte +2 -15
  26. package/dist/components/MultiSelect.svelte +16 -7
  27. package/dist/components/NumberInput.svelte +3 -3
  28. package/dist/components/PageHeader.svelte +23 -1
  29. package/dist/components/Pagination.svelte +164 -65
  30. package/dist/components/Pagination.svelte.d.ts +39 -2
  31. package/dist/components/Panel.svelte +1 -1
  32. package/dist/components/PasswordInput.svelte +4 -4
  33. package/dist/components/Radio.svelte +1 -1
  34. package/dist/components/RadioGroup.svelte +1 -1
  35. package/dist/components/SearchInput.svelte +13 -4
  36. package/dist/components/SegmentedControl.svelte +3 -1
  37. package/dist/components/Select.svelte +6 -6
  38. package/dist/components/SidebarNav.svelte +20 -5
  39. package/dist/components/Table.svelte +256 -10
  40. package/dist/components/Table.svelte.d.ts +44 -0
  41. package/dist/components/Tabs.svelte +1 -1
  42. package/dist/components/Tag.svelte +1 -1
  43. package/dist/components/Textarea.svelte +1 -1
  44. package/dist/components/ThemeToggle.svelte +76 -13
  45. package/dist/components/ThemeToggle.svelte.d.ts +9 -0
  46. package/dist/components/TimePicker.svelte +2 -2
  47. package/dist/components/Toaster.svelte +3 -15
  48. package/dist/components/Toggle.svelte +10 -4
  49. package/dist/components/Tooltip.svelte +1 -1
  50. package/dist/components/TreeView.svelte +4 -4
  51. package/dist/components/dialog/Dialog.svelte +10 -5
  52. package/dist/components/dialog/dialog-manager.svelte.d.ts +20 -1
  53. package/dist/components/dialog/dialog-manager.svelte.js +13 -2
  54. package/dist/index.d.ts +5 -2
  55. package/dist/index.js +6 -2
  56. package/dist/internal/href.d.ts +13 -0
  57. package/dist/internal/href.js +27 -0
  58. package/dist/internal/layout.js +11 -2
  59. package/dist/internal/panel.d.ts +1 -1
  60. package/dist/internal/panel.js +2 -2
  61. package/dist/styles/theme.css +288 -0
  62. package/dist/utils/theme.d.ts +68 -3
  63. package/dist/utils/theme.js +118 -3
  64. package/package.json +20 -22
  65. package/src/lib/styles/theme.css +288 -0
package/README.md CHANGED
@@ -7,7 +7,7 @@ A clean, accessible Svelte 5 component library. The design system behind [LyEve]
7
7
  [![Tailwind CSS 4](https://img.shields.io/badge/tailwind-v4-38bdf8.svg)](https://tailwindcss.com)
8
8
 
9
9
  ```bash
10
- pnpm add @lyeve-labs/ui-kit
10
+ pnpm add @lyeve-labs/ui-kit @lucide/svelte
11
11
  ```
12
12
 
13
13
  ```svelte
@@ -28,7 +28,7 @@ No config file, no theme provider, no setup ceremony.
28
28
 
29
29
  ## What's in the box
30
30
 
31
- - **48 components:** buttons, inputs, modals, drawers, tabs, tables, toasts, the works.
31
+ - **67 components:** buttons, inputs, modals, drawers, tabs, tables, toasts, the works.
32
32
  - **Two themes:** Soft Dark (default) and Soft Light, switched by a single `data-theme` attribute on `<html>`.
33
33
  - **One CSS file:** `@lyeve-labs/ui-kit/styles.css` declares every token; the rest is just Tailwind.
34
34
  - **Svelte 5 native:** built on runes and snippets, fully typed end-to-end.
@@ -37,28 +37,31 @@ No config file, no theme provider, no setup ceremony.
37
37
  ## Component list
38
38
 
39
39
  <details>
40
- <summary>48 components, organized by purpose</summary>
40
+ <summary>67 components, organized by purpose</summary>
41
41
 
42
- **Layout & display**
43
- Card · PageHeader · Divider · Accordion · AccordionItem · Table
42
+ **Layout and structure**
43
+ Card, Panel, AppShell, PageShell, PageHeader, SectionHeading, Divider, Accordion, AccordionItem, Collapsible, Table, DescriptionList, Toolbar, TreeView
44
44
 
45
- **Forms**
46
- Button · ButtonGroup · Input · Textarea · NumberInput · SearchInput · FileInput · Label · Select · MultiSelect · Autocomplete · DatePicker · Checkbox · Radio · RadioGroup · Toggle
45
+ **Forms and inputs**
46
+ Button, ButtonGroup, Input, PasswordInput, Textarea, NumberInput, SearchInput, FileInput, Label, Field, FormMessage, SegmentedControl, Select, MultiSelect, Autocomplete, DatePicker, TimePicker, DateTimePicker, Checkbox, CheckboxGroup, Radio, RadioGroup, Toggle
47
47
 
48
48
  **Navigation**
49
- Breadcrumb · Tabs · Pagination · StepIndicator · Dropdown
49
+ Breadcrumb, Tabs, Pagination, StepIndicator, Dropdown, SidebarNav, AccountMenu
50
50
 
51
51
  **Overlays**
52
- Modal · Drawer · Tooltip · Dialog · DialogContainer · ConfirmDialog
52
+ Modal, Drawer, Tooltip
53
53
 
54
- **Feedback**
55
- Alert · Banner · Badge · Tag · Indicator · Progress · Spinner · Skeleton · EmptyState · Stat · Kbd
54
+ **Dialogs**
55
+ Dialog, DialogContainer, ConfirmDialog (plus the `openDialog` and `confirm` services)
56
+
57
+ **Feedback and status**
58
+ Alert, Banner, Badge, Tag, Indicator, Progress, Spinner, Skeleton, EmptyState, Stat, Kbd, CopyButton
56
59
 
57
60
  **Media**
58
- Avatar · AvatarGroup
61
+ Avatar, AvatarGroup, Logo
59
62
 
60
63
  **Theming**
61
- ThemeToggle · Toaster (+ `toast` service)
64
+ ThemeToggle, Toaster (plus the `toast` service)
62
65
 
63
66
  </details>
64
67
 
@@ -66,16 +69,23 @@ ThemeToggle · Toaster (+ `toast` service)
66
69
 
67
70
  - **Svelte 5.0** or newer
68
71
  - **Tailwind CSS v4** with the `@tailwindcss/vite` plugin
72
+ - **`@lucide/svelte`** 0.511 or newer, below 2.0
69
73
  - **Node 20** or newer
70
74
 
71
75
  ## Install
72
76
 
73
77
  ```bash
74
- pnpm add @lyeve-labs/ui-kit
75
- # or npm install @lyeve-labs/ui-kit
76
- # or yarn add @lyeve-labs/ui-kit
78
+ pnpm add @lyeve-labs/ui-kit @lucide/svelte
79
+ # or npm install @lyeve-labs/ui-kit @lucide/svelte
80
+ # or yarn add @lyeve-labs/ui-kit @lucide/svelte
77
81
  ```
78
82
 
83
+ The icon set is a peer dependency, so it installs once and the application
84
+ picks the version. Most applications already depend on it directly, and while
85
+ the kit owned a copy of its own they resolved two: the same icons shipped twice
86
+ in the bundle and an upgrade on the application's side moved only one of them.
87
+ A missing peer is resolved for you by pnpm and by npm, and not by yarn.
88
+
79
89
  If you don't have Tailwind v4 yet:
80
90
 
81
91
  ```bash
@@ -175,7 +185,7 @@ This repo is a single-purpose component library. Nothing but `src/lib/`.
175
185
  ```
176
186
  src/
177
187
  └── lib/ # → published as @lyeve-labs/ui-kit
178
- ├── components/ # 48 .svelte files
188
+ ├── components/ # 67 .svelte files
179
189
  ├── stores/ # toast.svelte.ts
180
190
  ├── styles/ # theme.css (the one stylesheet)
181
191
  ├── utils/ # cn.ts, theme.ts
@@ -33,7 +33,7 @@
33
33
  onclick={() => ctx?.toggle(id)}
34
34
  aria-expanded={open}
35
35
  aria-controls={panelId}
36
- class="flex w-full items-center justify-between px-5 py-4 text-left text-sm font-medium
36
+ class="flex w-full items-center justify-between px-5 py-4 text-start text-sm font-medium
37
37
  text-fg transition-colors duration-150 hover:bg-surface-2/50
38
38
  outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand
39
39
  {open ? 'bg-surface-2/40' : ''}"
@@ -66,7 +66,7 @@
66
66
  clipped rather than spilling while the row grows.
67
67
  -->
68
68
  <div
69
- class="grid transition-[grid-template-rows] duration-200 ease-out {open
69
+ class="grid transition-[grid-template-rows] duration-[var(--duration-collapse)] ease-out {open
70
70
  ? 'grid-rows-[1fr]'
71
71
  : 'grid-rows-[0fr]'}"
72
72
  >
@@ -120,7 +120,7 @@
120
120
  rule are the card treatment, because lifted off the header it is a
121
121
  surface of its own. -->
122
122
  <div
123
- class="absolute end-0 top-full z-50 mt-2 flex w-64 flex-col gap-1 rounded-xl border border-line bg-surface p-2 shadow-2xl"
123
+ class="absolute end-0 top-full z-dropdown mt-2 flex w-64 flex-col gap-1 rounded-xl border border-line bg-surface p-2 shadow-2xl"
124
124
  >
125
125
  <div class="border-b border-line px-2 pb-2">
126
126
  <p class="truncate text-sm font-medium text-fg">{name}</p>
@@ -110,12 +110,13 @@
110
110
 
111
111
  <svelte:window onkeydown={onWindowKeydown} />
112
112
 
113
- <div class="flex h-screen {klass}">
113
+ <div data-print="unclip" class="flex h-screen {klass}">
114
114
  <!-- First focusable thing in the document. Without it a keyboard reader tabs
115
115
  the whole sidebar again on every page. -->
116
116
  <a
117
117
  href="#content"
118
- class="sr-only focus:not-sr-only focus:fixed focus:left-3 focus:top-3 focus:z-50 focus:rounded-lg focus:bg-surface focus:px-3 focus:py-2 focus:text-sm focus:text-fg focus:outline-none focus:ring-2 focus:ring-brand"
118
+ data-print="hide"
119
+ class="sr-only focus:not-sr-only focus:fixed focus:start-3 focus:top-3 focus:z-skip-link focus:rounded-lg focus:bg-surface focus:px-3 focus:py-2 focus:text-sm focus:text-fg focus:outline-none focus:ring-2 focus:ring-brand"
119
120
  >
120
121
  Skip to content
121
122
  </a>
@@ -125,7 +126,8 @@
125
126
  themes. Closing on it is the gesture people expect, and it stops a tap
126
127
  reaching the page under the open drawer. -->
127
128
  <div
128
- class="fixed inset-0 z-40 bg-black/60 backdrop-blur-sm"
129
+ data-print="hide"
130
+ class="fixed inset-0 z-overlay bg-black/60 backdrop-blur-sm"
129
131
  role="presentation"
130
132
  onclick={() => (navOpen = false)}
131
133
  ></div>
@@ -145,7 +147,8 @@
145
147
  -->
146
148
  {#if drawerOpen}
147
149
  <div
148
- class="fixed inset-y-0 start-0 z-50 flex shadow-2xl"
150
+ data-print="hide"
151
+ class="fixed inset-y-0 start-0 z-drawer flex shadow-2xl"
149
152
  role="dialog"
150
153
  aria-modal="true"
151
154
  aria-label={drawerLabel}
@@ -160,7 +163,7 @@
160
163
  {/if}
161
164
 
162
165
  <div class="flex min-w-0 flex-1 flex-col">
163
- <header class={APP_HEADER}>
166
+ <header data-print="hide" class={APP_HEADER}>
164
167
  <div class="flex min-w-0 flex-1 items-center gap-2">
165
168
  {#if isMobile}
166
169
  <!-- 44px square, the smallest tap target SC 2.5.5 accepts. The -ms-2
@@ -168,7 +171,7 @@
168
171
  still lines up with the page content below. -->
169
172
  <button
170
173
  type="button"
171
- class="-ms-2 flex h-11 w-11 shrink-0 items-center justify-center rounded-lg text-muted outline-none transition-colors duration-150 hover:bg-surface-2 hover:text-fg focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand"
174
+ class="-ms-2 flex h-11 w-11 shrink-0 items-center justify-center rounded-lg text-muted outline-none transition-colors duration-150 hover:bg-surface-2 hover:text-fg active:bg-line active:text-fg focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand"
172
175
  aria-label={navOpen ? 'Close navigation' : 'Open navigation'}
173
176
  aria-expanded={navOpen}
174
177
  onclick={() => (navOpen = !navOpen)}
@@ -181,7 +184,7 @@
181
184
  <button
182
185
  type="button"
183
186
  data-testid="app-sidebar-toggle"
184
- class="-ms-2 flex h-11 w-11 shrink-0 items-center justify-center rounded-lg text-muted outline-none transition-colors duration-150 hover:bg-surface-2 hover:text-fg focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand"
187
+ class="-ms-2 flex h-11 w-11 shrink-0 items-center justify-center rounded-lg text-muted outline-none transition-colors duration-150 hover:bg-surface-2 hover:text-fg active:bg-line active:text-fg focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand"
185
188
  aria-label={collapsed ? 'Show sidebar' : 'Hide sidebar'}
186
189
  aria-expanded={!collapsed}
187
190
  onclick={() => (collapsed = !collapsed)}
@@ -201,7 +204,17 @@
201
204
  {/if}
202
205
  </header>
203
206
 
204
- <main id="content" class="min-w-0 flex-1 overflow-auto bg-ink">
207
+ <!-- tabindex -1 is what makes the skip link work. A fragment link scrolls
208
+ to its target and moves focus to it only if the target can hold focus,
209
+ so without this the link moved the page and left focus on the body, and
210
+ the next Tab started again at the top of the document: the sidebar the
211
+ reader had just skipped. -->
212
+ <main
213
+ id="content"
214
+ tabindex="-1"
215
+ data-print="unclip"
216
+ class="min-w-0 flex-1 overflow-auto bg-ink"
217
+ >
205
218
  {@render children()}
206
219
  </main>
207
220
  </div>
@@ -210,6 +223,7 @@
210
223
  {#snippet sidebar(hidden: boolean)}
211
224
  <aside
212
225
  aria-label={sidebarLabel}
226
+ data-print="hide"
213
227
  class="{APP_SIDEBAR} flex"
214
228
  inert={hidden}
215
229
  aria-hidden={hidden ? 'true' : undefined}
@@ -169,7 +169,7 @@
169
169
  <div class="{FIELD_WRAP} {cls}">
170
170
  {#if label}
171
171
  <label for={fieldId} class={FIELD_LABEL}>
172
- {label}{#if required}<span class="ml-0.5 text-danger" aria-hidden="true">*</span>{/if}
172
+ {label}{#if required}<span class="ms-0.5 text-danger" aria-hidden="true">*</span>{/if}
173
173
  </label>
174
174
  {/if}
175
175
 
@@ -193,15 +193,24 @@
193
193
  aria-invalid={error ? 'true' : undefined}
194
194
  aria-describedby={describedBy(fieldId, error, hint)}
195
195
  {...box.triggerAttrs}
196
- class="{CONTROL_BASE} {controlBorder(!!error)} {allowClear && value ? 'pr-8' : 'pr-3'}"
196
+ class="{CONTROL_BASE} {controlBorder(!!error)} {allowClear && value ? 'pe-8' : 'pe-3'}"
197
197
  />
198
198
 
199
199
  {#if allowClear && value && !disabled}
200
+ <!--
201
+ 12px inside 6px of padding is a 24px target, the floor SC 2.5.8 puts on
202
+ a control with no spacing exception to claim, and this one sits inside
203
+ the field. The padding is taken back off the horizontal axis only: the
204
+ box is positioned from its right edge, so a negative right margin holds
205
+ the glyph exactly where it was, while a negative top margin would fight
206
+ the translate that centres it and lift it 6px off the middle of the
207
+ field.
208
+ -->
200
209
  <button
201
210
  type="button"
202
211
  aria-label="Clear"
203
212
  onclick={clear}
204
- class="absolute right-2.5 top-1/2 -translate-y-1/2 text-faint transition-colors duration-150 hover:text-fg"
213
+ class="absolute end-2.5 top-1/2 -mx-1.5 -translate-y-1/2 p-1.5 text-faint transition-colors duration-150 hover:text-fg"
205
214
  >
206
215
  <svg
207
216
  width="12"
@@ -255,7 +264,7 @@
255
264
  height="10"
256
265
  viewBox="0 0 12 10"
257
266
  fill="none"
258
- class="ml-auto shrink-0"
267
+ class="ms-auto shrink-0"
259
268
  aria-hidden="true"
260
269
  >
261
270
  <path
@@ -30,14 +30,14 @@
30
30
 
31
31
  <div class="flex items-center {cls}">
32
32
  {#each visible as user}
33
- <span class="-ml-1.5 inline-block rounded-full ring-2 ring-ink first:ml-0">
33
+ <span class="-ms-1.5 inline-block rounded-full ring-2 ring-ink first:ms-0">
34
34
  <Avatar name={user.name} src={user.src} {size} />
35
35
  </span>
36
36
  {/each}
37
37
 
38
38
  {#if overflow > 0}
39
39
  <span
40
- class="-ml-1.5 inline-flex shrink-0 items-center justify-center rounded-full
40
+ class="-ms-1.5 inline-flex shrink-0 items-center justify-center rounded-full
41
41
  bg-surface-2 font-semibold text-faint ring-2 ring-ink
42
42
  {overflowSizes[size]}">+{overflow}</span
43
43
  >
@@ -24,7 +24,7 @@
24
24
  stroke-width="2"
25
25
  stroke-linecap="round"
26
26
  stroke-linejoin="round"
27
- class="text-faint shrink-0"
27
+ class="text-faint shrink-0 rtl:rotate-180"
28
28
  aria-hidden="true"
29
29
  >
30
30
  <path d="M9 18l6-6-6-6" />
@@ -2,6 +2,7 @@
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
4
4
  import Spinner from './Spinner.svelte';
5
+ import { safeHref } from '../internal/href.js';
5
6
 
6
7
  type Variant = 'primary' | 'secondary' | 'danger' | 'ghost' | 'outline' | 'violet';
7
8
  type Size = 'sm' | 'md' | 'lg';
@@ -43,14 +44,36 @@
43
44
  ...rest
44
45
  }: Props = $props();
45
46
 
47
+ /**
48
+ * Colour per variant, in three states: at rest, under a pointer, and held.
49
+ *
50
+ * The pressed step is the one a touch screen depends on. `hover:` compiles
51
+ * inside `@media (hover: hover)`, which is false on a finger, so a tap ran
52
+ * rest to rest with the action already fired and the control never
53
+ * acknowledged the press. `active:` matches under a finger as well as under a
54
+ * mouse, and Tailwind emits it after `hover:` at equal specificity, so the
55
+ * held colour wins on a device that has both.
56
+ *
57
+ * primary and violet had a pressed state already and neither one showed:
58
+ * `active:bg-brand` on a `bg-brand` button and `active:brightness-100` on an
59
+ * unfiltered one are both the resting appearance spelled twice.
60
+ *
61
+ * Every pressed step moves the same way in both palettes. brand-deep sits
62
+ * below brand-light on the dark ramp and below it again on the light one, and
63
+ * the filtered variants darken rather than brighten, so a press does not read
64
+ * as one gesture in one theme and its opposite in the other.
65
+ */
46
66
  const variants: Record<Variant, string> = {
47
- primary: 'bg-brand text-ink hover:bg-brand-light active:bg-brand shadow-sm shadow-brand/20',
67
+ primary:
68
+ 'bg-brand text-ink hover:bg-brand-light active:bg-brand-deep shadow-sm shadow-brand/20',
48
69
  violet:
49
- 'bg-violet text-ink hover:brightness-110 active:brightness-100 shadow-sm shadow-violet/20',
50
- secondary: 'bg-surface-2 text-fg border border-line-strong hover:bg-line',
51
- danger: 'bg-danger text-ink hover:brightness-110 active:brightness-100',
52
- ghost: 'text-muted hover:bg-surface-2 hover:text-fg',
53
- outline: 'border border-line-strong text-fg hover:border-brand hover:text-brand',
70
+ 'bg-violet text-ink hover:brightness-110 active:brightness-90 shadow-sm shadow-violet/20',
71
+ secondary:
72
+ 'bg-surface-2 text-fg border border-line-strong hover:bg-line active:bg-line-strong/30',
73
+ danger: 'bg-danger text-ink hover:brightness-110 active:brightness-90',
74
+ ghost: 'text-muted hover:bg-surface-2 hover:text-fg active:bg-line active:text-fg',
75
+ outline:
76
+ 'border border-line-strong text-fg hover:border-brand hover:text-brand active:bg-brand/10',
54
77
  };
55
78
 
56
79
  const sizes: Record<Size, string> = {
@@ -66,8 +89,10 @@
66
89
  `${full ? 'w-full' : ''} ${variants[variant]} ${sizes[size]} ${klass}`,
67
90
  );
68
91
 
69
- // Reject javascript: and data: URIs - only allow standard schemes and relative URLs.
70
- let safeHref = $derived(href && !/^(javascript|data):/i.test(href) ? href : undefined);
92
+ // Only standard schemes and relative URLs reach the DOM. The rule itself
93
+ // lives in internal/href.ts, because Pagination applies the same one to the
94
+ // hrefs its caller builds and two copies of it would drift.
95
+ let resolvedHref = $derived(safeHref(href));
71
96
 
72
97
  /**
73
98
  * An anchor has no `disabled`. The prop was accepted and then dropped on this
@@ -81,7 +106,7 @@
81
106
 
82
107
  {#if href}
83
108
  <a
84
- href={inert ? undefined : safeHref}
109
+ href={inert ? undefined : resolvedHref}
85
110
  class="{cls} {inert ? 'pointer-events-none opacity-50' : ''}"
86
111
  aria-disabled={inert ? 'true' : undefined}
87
112
  tabindex={inert ? -1 : undefined}
@@ -13,9 +13,9 @@
13
13
  {#if orientation === 'horizontal'}
14
14
  <div
15
15
  class="inline-flex
16
- [&>*:not(:first-child)]:rounded-l-none
17
- [&>*:not(:last-child)]:rounded-r-none
18
- [&>*:not(:first-child)]:-ml-px
16
+ [&>*:not(:first-child)]:rounded-s-none
17
+ [&>*:not(:last-child)]:rounded-e-none
18
+ [&>*:not(:first-child)]:-ms-px
19
19
  {cls}"
20
20
  >
21
21
  {@render children()}
@@ -82,6 +82,7 @@
82
82
  so the element is interactive; the compiler can't narrow the dynamic role. -->
83
83
  <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
84
84
  <div
85
+ data-print="keep"
85
86
  class="bg-surface border border-line rounded-xl overflow-hidden transition-colors duration-150 {hover
86
87
  ? 'hover:border-line/0 hover:ring-1 hover:ring-brand/30'
87
88
  : ''} {onclick ? 'cursor-pointer' : ''} {klass}"
@@ -108,7 +109,7 @@
108
109
  {:else}
109
110
  <h3 class={sectionHeading(3)}>{heading}</h3>
110
111
  {/if}
111
- {#if meta}<span class="ml-auto shrink-0 text-xs text-faint">{meta}</span>{/if}
112
+ {#if meta}<span class="ms-auto shrink-0 text-xs text-faint">{meta}</span>{/if}
112
113
  </div>
113
114
  {#if description}<p class="text-sm text-muted mt-0.5">{description}</p>{/if}
114
115
  {:else}
@@ -155,7 +155,7 @@
155
155
  own text it became part of the name, and the reader heard the
156
156
  whole paragraph before it reached the checked state. -->
157
157
  <span id="{fieldId}-label" class="{choiceLabel(size)} {labelHidden ? 'sr-only' : ''}">
158
- {label}{#if required}<span class="ml-0.5 text-danger" aria-hidden="true">*</span>{/if}
158
+ {label}{#if required}<span class="ms-0.5 text-danger" aria-hidden="true">*</span>{/if}
159
159
  </span>
160
160
  {/if}
161
161
  </span>
@@ -124,7 +124,7 @@
124
124
  ahead of them saying what the set is for.
125
125
  -->
126
126
  <legend class="{FIELD_LABEL} {labelHidden ? 'sr-only' : ''}">
127
- {label}{#if required}<span class="text-danger ml-0.5" aria-hidden="true">*</span><span
127
+ {label}{#if required}<span class="text-danger ms-0.5" aria-hidden="true">*</span><span
128
128
  class="sr-only"
129
129
  >
130
130
  (required)</span
@@ -70,7 +70,7 @@
70
70
  aria-expanded={open}
71
71
  aria-controls={panelId}
72
72
  onclick={toggle}
73
- class="flex w-full items-center gap-2 rounded-lg px-2 py-1.5 text-left text-sm font-medium
73
+ class="flex w-full items-center gap-2 rounded-lg px-2 py-1.5 text-start text-sm font-medium
74
74
  text-fg transition-colors duration-150 outline-none focus-visible:ring-2
75
75
  focus-visible:ring-inset focus-visible:ring-brand disabled:cursor-not-allowed
76
76
  disabled:opacity-50 {disabled ? '' : 'hover:bg-surface-2'}"
@@ -88,7 +88,7 @@
88
88
  stroke-linejoin="round"
89
89
  class="shrink-0 text-faint transition-transform duration-200 ease-out {open
90
90
  ? ''
91
- : '-rotate-90'}"
91
+ : '-rotate-90 rtl:rotate-90'}"
92
92
  aria-hidden="true"
93
93
  >
94
94
  <path d="M6 9l6 6 6-6" />
@@ -102,7 +102,7 @@
102
102
  <span class="truncate">{label}</span>
103
103
 
104
104
  {#if badge !== undefined}
105
- <span class="ml-auto shrink-0 rounded-full bg-surface-2 px-2 py-0.5 text-xs text-muted">
105
+ <span class="ms-auto shrink-0 rounded-full bg-surface-2 px-2 py-0.5 text-xs text-muted">
106
106
  {badge}
107
107
  </span>
108
108
  {/if}
@@ -116,7 +116,7 @@
116
116
  clipped rather than spilling while the row grows.
117
117
  -->
118
118
  <div
119
- class="grid transition-[grid-template-rows] duration-200 ease-out {open
119
+ class="grid transition-[grid-template-rows] duration-[var(--duration-collapse)] ease-out {open
120
120
  ? 'grid-rows-[1fr]'
121
121
  : 'grid-rows-[0fr]'}"
122
122
  >
@@ -105,7 +105,7 @@
105
105
  type="button"
106
106
  aria-label={label}
107
107
  onclick={copy}
108
- class="inline-flex items-center justify-center rounded-md p-1 outline-none transition-colors duration-150 focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand {status ===
108
+ class="inline-flex items-center justify-center rounded-md p-1 outline-none transition-colors duration-150 active:bg-surface-2 focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand {status ===
109
109
  'copied'
110
110
  ? 'text-success'
111
111
  : 'text-faint hover:text-fg'}"
@@ -176,7 +176,7 @@
176
176
  <div class="{FIELD_WRAP} {cls}" bind:this={containerEl}>
177
177
  {#if label}
178
178
  <label for={fieldId} class={FIELD_LABEL}>
179
- {label}{#if required}<span class="text-danger ml-0.5" aria-hidden="true">*</span>{/if}
179
+ {label}{#if required}<span class="text-danger ms-0.5" aria-hidden="true">*</span>{/if}
180
180
  </label>
181
181
  {/if}
182
182
 
@@ -206,7 +206,7 @@
206
206
  aria-invalid={error ? 'true' : undefined}
207
207
  aria-required={required ? 'true' : undefined}
208
208
  aria-describedby={describedBy(fieldId, error, hint)}
209
- class="{CONTROL_BASE} {controlBorder(!!error)} flex items-center justify-between text-left"
209
+ class="{CONTROL_BASE} {controlBorder(!!error)} flex items-center justify-between text-start"
210
210
  >
211
211
  <span class={display ? 'text-fg' : 'text-faint'}>{display || placeholder}</span>
212
212
  <span class="text-faint shrink-0" aria-hidden="true">
@@ -242,7 +242,7 @@
242
242
  id={calendarId}
243
243
  role="dialog"
244
244
  aria-label="Choose date"
245
- class="absolute z-50 mt-1 w-[17rem] rounded-xl border border-line bg-surface shadow-2xl p-3"
245
+ class="absolute z-dropdown mt-1 w-[17rem] rounded-xl border border-line bg-surface shadow-2xl p-3"
246
246
  >
247
247
  <!-- Header -->
248
248
  <div class="flex items-center justify-between mb-2">
@@ -281,7 +281,7 @@
281
281
  names it and clicking that label opens the calendar. The group below
282
282
  takes its name from this same element. -->
283
283
  <label id="{fieldId}-label" for="{fieldId}-date" class={FIELD_LABEL}>
284
- {label}{#if required}<span class="ml-0.5 text-danger" aria-hidden="true">*</span>{/if}
284
+ {label}{#if required}<span class="ms-0.5 text-danger" aria-hidden="true">*</span>{/if}
285
285
  </label>
286
286
  {/if}
287
287
 
@@ -53,7 +53,10 @@
53
53
  <svelte:window onkeydown={open ? onkeydown : undefined} />
54
54
 
55
55
  {#if open}
56
- <div class="fixed inset-0 z-50 flex {side === 'right' ? 'justify-end' : 'justify-start'}">
56
+ <div
57
+ data-print="hide"
58
+ class="fixed inset-0 z-drawer flex {side === 'right' ? 'justify-end' : 'justify-start'}"
59
+ >
57
60
  <button
58
61
  type="button"
59
62
  tabindex="-1"
@@ -65,10 +68,10 @@
65
68
  <div
66
69
  use:overlay
67
70
  class="relative flex h-full max-w-full flex-col {widths[size]} bg-surface shadow-2xl
68
- {side === 'right' ? 'border-l' : 'border-r'} border-line
71
+ {side === 'right' ? 'border-s' : 'border-e'} border-line
69
72
  {side === 'right'
70
- ? 'animate-[drawer-in-right_150ms_ease-out]'
71
- : 'animate-[drawer-in-left_150ms_ease-out]'}"
73
+ ? 'animate-[drawer-in-right_var(--duration-drawer-in)_ease-out]'
74
+ : 'animate-[drawer-in-left_var(--duration-drawer-in)_ease-out]'}"
72
75
  role="dialog"
73
76
  aria-modal="true"
74
77
  aria-labelledby={title ? headingId : undefined}
@@ -117,26 +120,3 @@
117
120
  </div>
118
121
  </div>
119
122
  {/if}
120
-
121
- <style>
122
- @keyframes drawer-in-right {
123
- from {
124
- opacity: 0;
125
- transform: translateX(16px);
126
- }
127
- to {
128
- opacity: 1;
129
- transform: translateX(0);
130
- }
131
- }
132
- @keyframes drawer-in-left {
133
- from {
134
- opacity: 0;
135
- transform: translateX(-16px);
136
- }
137
- to {
138
- opacity: 1;
139
- transform: translateX(0);
140
- }
141
- }
142
- </style>