@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.
- package/README.md +27 -17
- package/dist/components/AccordionItem.svelte +2 -2
- package/dist/components/AccountMenu.svelte +1 -1
- package/dist/components/AppShell.svelte +22 -8
- package/dist/components/Autocomplete.svelte +13 -4
- package/dist/components/AvatarGroup.svelte +2 -2
- package/dist/components/Breadcrumb.svelte +1 -1
- package/dist/components/Button.svelte +34 -9
- package/dist/components/ButtonGroup.svelte +3 -3
- package/dist/components/Card.svelte +2 -1
- package/dist/components/Checkbox.svelte +1 -1
- package/dist/components/CheckboxGroup.svelte +1 -1
- package/dist/components/Collapsible.svelte +4 -4
- package/dist/components/CopyButton.svelte +1 -1
- package/dist/components/DatePicker.svelte +3 -3
- package/dist/components/DateTimePicker.svelte +1 -1
- package/dist/components/Drawer.svelte +7 -27
- package/dist/components/Dropdown.svelte +197 -11
- package/dist/components/Dropdown.svelte.d.ts +12 -0
- package/dist/components/Field.svelte +1 -1
- package/dist/components/Input.svelte +1 -1
- package/dist/components/Label.svelte +1 -1
- package/dist/components/Logo.svelte +77 -0
- package/dist/components/Logo.svelte.d.ts +35 -0
- package/dist/components/Modal.svelte +2 -15
- package/dist/components/MultiSelect.svelte +16 -7
- package/dist/components/NumberInput.svelte +3 -3
- package/dist/components/PageHeader.svelte +23 -1
- package/dist/components/Pagination.svelte +164 -65
- package/dist/components/Pagination.svelte.d.ts +39 -2
- package/dist/components/Panel.svelte +1 -1
- package/dist/components/PasswordInput.svelte +4 -4
- package/dist/components/Radio.svelte +1 -1
- package/dist/components/RadioGroup.svelte +1 -1
- package/dist/components/SearchInput.svelte +13 -4
- package/dist/components/SegmentedControl.svelte +3 -1
- package/dist/components/Select.svelte +6 -6
- package/dist/components/SidebarNav.svelte +20 -5
- package/dist/components/Table.svelte +256 -10
- package/dist/components/Table.svelte.d.ts +44 -0
- package/dist/components/Tabs.svelte +1 -1
- package/dist/components/Tag.svelte +1 -1
- package/dist/components/Textarea.svelte +1 -1
- package/dist/components/ThemeToggle.svelte +76 -13
- package/dist/components/ThemeToggle.svelte.d.ts +9 -0
- package/dist/components/TimePicker.svelte +2 -2
- package/dist/components/Toaster.svelte +3 -15
- package/dist/components/Toggle.svelte +10 -4
- package/dist/components/Tooltip.svelte +1 -1
- package/dist/components/TreeView.svelte +4 -4
- package/dist/components/dialog/Dialog.svelte +10 -5
- package/dist/components/dialog/dialog-manager.svelte.d.ts +20 -1
- package/dist/components/dialog/dialog-manager.svelte.js +13 -2
- package/dist/index.d.ts +5 -2
- package/dist/index.js +6 -2
- package/dist/internal/href.d.ts +13 -0
- package/dist/internal/href.js +27 -0
- package/dist/internal/layout.js +11 -2
- package/dist/internal/panel.d.ts +1 -1
- package/dist/internal/panel.js +2 -2
- package/dist/styles/theme.css +288 -0
- package/dist/utils/theme.d.ts +68 -3
- package/dist/utils/theme.js +118 -3
- package/package.json +20 -22
- 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
|
[](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
|
-
- **
|
|
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>
|
|
40
|
+
<summary>67 components, organized by purpose</summary>
|
|
41
41
|
|
|
42
|
-
**Layout
|
|
43
|
-
Card
|
|
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
|
|
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
|
|
49
|
+
Breadcrumb, Tabs, Pagination, StepIndicator, Dropdown, SidebarNav, AccountMenu
|
|
50
50
|
|
|
51
51
|
**Overlays**
|
|
52
|
-
Modal
|
|
52
|
+
Modal, Drawer, Tooltip
|
|
53
53
|
|
|
54
|
-
**
|
|
55
|
-
|
|
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
|
|
61
|
+
Avatar, AvatarGroup, Logo
|
|
59
62
|
|
|
60
63
|
**Theming**
|
|
61
|
-
ThemeToggle
|
|
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/ #
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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="
|
|
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 ? '
|
|
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
|
|
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="
|
|
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="-
|
|
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="-
|
|
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
|
>
|
|
@@ -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:
|
|
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-
|
|
50
|
-
secondary:
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
//
|
|
70
|
-
|
|
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 :
|
|
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-
|
|
17
|
-
[&>*:not(:last-child)]:rounded-
|
|
18
|
-
[&>*:not(:first-child)]:-
|
|
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="
|
|
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="
|
|
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
|
|
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-
|
|
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="
|
|
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-
|
|
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
|
|
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-
|
|
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-
|
|
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="
|
|
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
|
|
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-
|
|
71
|
+
{side === 'right' ? 'border-s' : 'border-e'} border-line
|
|
69
72
|
{side === 'right'
|
|
70
|
-
? 'animate-[drawer-in-
|
|
71
|
-
: 'animate-[drawer-in-
|
|
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>
|