@lyeve-labs/ui-kit 0.10.1 → 0.11.2

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.
@@ -21,35 +21,65 @@
21
21
  >('accordion');
22
22
 
23
23
  let open = $derived(ctx?.isOpen(id) ?? false);
24
+ const panelId = $derived(`accordion-panel-${id}`);
25
+ const headerId = $derived(`accordion-header-${id}`);
24
26
  </script>
25
27
 
26
28
  <div class="bg-surface {cls}">
27
- <button
28
- type="button"
29
- onclick={() => ctx?.toggle(id)}
30
- aria-expanded={open}
31
- class="flex w-full items-center justify-between px-5 py-4 text-left text-sm font-medium
32
- text-fg transition-colors duration-150 hover:bg-surface-2/50"
33
- >
34
- <span>{title}</span>
35
- <svg
36
- width="16"
37
- height="16"
38
- viewBox="0 0 24 24"
39
- fill="none"
40
- stroke="currentColor"
41
- stroke-width="2"
42
- stroke-linecap="round"
43
- stroke-linejoin="round"
44
- class="shrink-0 text-faint transition-transform {open ? 'rotate-180' : ''}"
45
- aria-hidden="true"
29
+ <h3 class="m-0">
30
+ <button
31
+ type="button"
32
+ id={headerId}
33
+ onclick={() => ctx?.toggle(id)}
34
+ aria-expanded={open}
35
+ aria-controls={panelId}
36
+ class="flex w-full items-center justify-between px-5 py-4 text-left text-sm font-medium
37
+ text-fg transition-colors duration-150 hover:bg-surface-2/50
38
+ outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand/50
39
+ {open ? 'bg-surface-2/40' : ''}"
46
40
  >
47
- <path d="M6 9l6 6 6-6" />
48
- </svg>
49
- </button>
50
- {#if open}
51
- <div class="px-5 pb-4 text-sm text-muted">
52
- {@render children()}
41
+ <span>{title}</span>
42
+ <svg
43
+ width="16"
44
+ height="16"
45
+ viewBox="0 0 24 24"
46
+ fill="none"
47
+ stroke="currentColor"
48
+ stroke-width="2"
49
+ stroke-linecap="round"
50
+ stroke-linejoin="round"
51
+ class="shrink-0 text-faint transition-transform duration-200 ease-out {open
52
+ ? 'rotate-180'
53
+ : ''}"
54
+ aria-hidden="true"
55
+ >
56
+ <path d="M6 9l6 6 6-6" />
57
+ </svg>
58
+ </button>
59
+ </h3>
60
+
61
+ <!--
62
+ A panel opens to the height of its own content, which no fixed value knows.
63
+ Animating grid-template-rows from 0fr to 1fr resolves to that height in CSS
64
+ alone - no measuring, no reflow on every frame, and nothing to get wrong when
65
+ the content changes. The inner element carries the overflow so the text is
66
+ clipped rather than spilling while the row grows.
67
+ -->
68
+ <div
69
+ class="grid transition-[grid-template-rows] duration-200 ease-out {open
70
+ ? 'grid-rows-[1fr]'
71
+ : 'grid-rows-[0fr]'}"
72
+ >
73
+ <div class="overflow-hidden">
74
+ <div
75
+ id={panelId}
76
+ role="region"
77
+ aria-labelledby={headerId}
78
+ inert={!open}
79
+ class="px-5 pb-4 pt-1 text-sm text-muted"
80
+ >
81
+ {@render children()}
82
+ </div>
53
83
  </div>
54
- {/if}
84
+ </div>
55
85
  </div>
@@ -1,11 +1,22 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
+ import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
3
4
  import Spinner from './Spinner.svelte';
4
5
 
5
6
  type Variant = 'primary' | 'secondary' | 'danger' | 'ghost' | 'outline' | 'violet';
6
7
  type Size = 'sm' | 'md' | 'lg';
7
8
 
8
- interface Props {
9
+ /**
10
+ * Attributes forwarded to whichever element renders. Naming them, rather than
11
+ * accepting an open index signature, means a prop the component does not
12
+ * define fails the build instead of reaching the DOM as a stray attribute.
13
+ */
14
+ type Forwarded = Omit<
15
+ HTMLButtonAttributes & HTMLAnchorAttributes,
16
+ 'type' | 'href' | 'disabled' | 'class' | 'onclick' | 'children'
17
+ >;
18
+
19
+ interface Props extends Forwarded {
9
20
  variant?: Variant;
10
21
  size?: Size;
11
22
  disabled?: boolean;
@@ -16,7 +27,6 @@
16
27
  class?: string;
17
28
  onclick?: (e: MouseEvent) => void;
18
29
  children: Snippet;
19
- [key: string]: unknown;
20
30
  }
21
31
 
22
32
  let {
@@ -1,7 +1,14 @@
1
1
  import type { Snippet } from 'svelte';
2
+ import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
2
3
  type Variant = 'primary' | 'secondary' | 'danger' | 'ghost' | 'outline' | 'violet';
3
4
  type Size = 'sm' | 'md' | 'lg';
4
- interface Props {
5
+ /**
6
+ * Attributes forwarded to whichever element renders. Naming them, rather than
7
+ * accepting an open index signature, means a prop the component does not
8
+ * define fails the build instead of reaching the DOM as a stray attribute.
9
+ */
10
+ type Forwarded = Omit<HTMLButtonAttributes & HTMLAnchorAttributes, 'type' | 'href' | 'disabled' | 'class' | 'onclick' | 'children'>;
11
+ interface Props extends Forwarded {
5
12
  variant?: Variant;
6
13
  size?: Size;
7
14
  disabled?: boolean;
@@ -12,7 +19,6 @@ interface Props {
12
19
  class?: string;
13
20
  onclick?: (e: MouseEvent) => void;
14
21
  children: Snippet;
15
- [key: string]: unknown;
16
22
  }
17
23
  declare const Button: import("svelte").Component<Props, {}, "">;
18
24
  type Button = ReturnType<typeof Button>;
@@ -45,10 +45,10 @@
45
45
  {disabled}
46
46
  {checked}
47
47
  onchange={handleChange}
48
- class="peer sr-only"
48
+ class="peer absolute inset-0 m-0 h-full w-full cursor-pointer opacity-0 disabled:cursor-not-allowed"
49
49
  />
50
50
  <span
51
- class="w-4 h-4 rounded border transition-colors duration-150 flex items-center justify-center
51
+ class="pointer-events-none w-4 h-4 rounded border transition-colors duration-150 flex items-center justify-center
52
52
  {checked
53
53
  ? 'bg-brand border-brand'
54
54
  : 'bg-surface-2 border-line peer-focus-visible:border-brand'}"
@@ -8,8 +8,19 @@
8
8
  controlBorder,
9
9
  describedBy,
10
10
  } from '../internal/field.js';
11
+ import type { HTMLInputAttributes } from 'svelte/elements';
11
12
 
12
- interface Props {
13
+ /**
14
+ * Attributes forwarded to the input. Naming them, rather than accepting an
15
+ * open index signature, means a prop the component does not define fails the
16
+ * build instead of reaching the DOM as a stray attribute.
17
+ */
18
+ type Forwarded = Omit<
19
+ HTMLInputAttributes,
20
+ 'value' | 'type' | 'placeholder' | 'disabled' | 'required' | 'id' | 'name' | 'class'
21
+ >;
22
+
23
+ interface Props extends Forwarded {
13
24
  value?: string;
14
25
  label?: string;
15
26
  type?: string;
@@ -21,7 +32,6 @@
21
32
  id?: string;
22
33
  name?: string;
23
34
  class?: string;
24
- [key: string]: unknown;
25
35
  }
26
36
 
27
37
  let {
@@ -1,4 +1,11 @@
1
- interface Props {
1
+ import type { HTMLInputAttributes } from 'svelte/elements';
2
+ /**
3
+ * Attributes forwarded to the input. Naming them, rather than accepting an
4
+ * open index signature, means a prop the component does not define fails the
5
+ * build instead of reaching the DOM as a stray attribute.
6
+ */
7
+ type Forwarded = Omit<HTMLInputAttributes, 'value' | 'type' | 'placeholder' | 'disabled' | 'required' | 'id' | 'name' | 'class'>;
8
+ interface Props extends Forwarded {
2
9
  value?: string;
3
10
  label?: string;
4
11
  type?: string;
@@ -10,7 +17,6 @@ interface Props {
10
17
  id?: string;
11
18
  name?: string;
12
19
  class?: string;
13
- [key: string]: unknown;
14
20
  }
15
21
  declare const Input: import("svelte").Component<Props, {}, "value">;
16
22
  type Input = ReturnType<typeof Input>;
@@ -196,7 +196,8 @@
196
196
  disabled={opt.disabled}
197
197
  onclick={() => toggle(opt)}
198
198
  class="w-full flex items-center gap-2.5 px-3 py-2 text-sm text-fg transition-colors duration-150
199
- hover:bg-surface-2 disabled:opacity-40 disabled:cursor-not-allowed"
199
+ hover:bg-surface-2 disabled:opacity-40 disabled:cursor-not-allowed
200
+ outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand/50"
200
201
  >
201
202
  <span
202
203
  class="flex h-4 w-4 shrink-0 items-center justify-center rounded border transition-colors duration-150
@@ -46,10 +46,10 @@
46
46
  {disabled}
47
47
  checked={group === value}
48
48
  onchange={handleChange}
49
- class="peer sr-only"
49
+ class="peer absolute inset-0 m-0 h-full w-full cursor-pointer opacity-0 disabled:cursor-not-allowed"
50
50
  />
51
51
  <span
52
- class="flex h-4 w-4 items-center justify-center rounded-full border-2 transition-colors duration-150
52
+ class="pointer-events-none flex h-4 w-4 items-center justify-center rounded-full border-2 transition-colors duration-150
53
53
  {group === value
54
54
  ? 'border-brand bg-surface-2'
55
55
  : 'border-line bg-surface-2 peer-focus-visible:border-brand'}"
@@ -68,10 +68,10 @@
68
68
  disabled={disabled || opt.disabled}
69
69
  checked={value === opt.value}
70
70
  onchange={() => select(opt)}
71
- class="peer sr-only"
71
+ class="peer absolute inset-0 m-0 h-full w-full cursor-pointer opacity-0 disabled:cursor-not-allowed"
72
72
  />
73
73
  <span
74
- class="flex h-4 w-4 items-center justify-center rounded-full border-2 transition-colors duration-150
74
+ class="pointer-events-none flex h-4 w-4 items-center justify-center rounded-full border-2 transition-colors duration-150
75
75
  {value === opt.value
76
76
  ? 'border-brand bg-surface-2'
77
77
  : 'border-line bg-surface-2 peer-focus-visible:border-brand'}"
@@ -22,7 +22,7 @@
22
22
  <table
23
23
  class="w-full text-sm text-left
24
24
  {fixed ? 'table-fixed' : ''}
25
- [&_thead_tr]:border-b [&_thead_tr]:border-line
25
+ [&_thead_tr]:border-b [&_thead_tr]:border-line [&_thead]:bg-surface-2/40
26
26
  [&_thead_th]:px-4 [&_thead_th]:py-3 [&_thead_th]:text-xs
27
27
  [&_thead_th]:font-medium [&_thead_th]:text-faint
28
28
  [&_thead_th]:uppercase [&_thead_th]:tracking-wider [&_thead_th]:whitespace-nowrap
@@ -30,7 +30,7 @@
30
30
  [&_tbody_tr]:border-b [&_tbody_tr]:border-line [&_tbody_tr:last-child]:border-0
31
31
  {striped ? '[&_tbody_tr:nth-child(even)]:bg-surface-2/40' : ''}
32
32
  {hoverable
33
- ? '[&_tbody_tr:hover]:bg-surface-2/60 [&_tbody_tr]:transition-colors duration-150'
33
+ ? '[&_tbody_tr:hover]:bg-surface-2/60 [&_tbody_tr]:transition-colors [&_tbody_tr]:duration-150'
34
34
  : ''}"
35
35
  >
36
36
  {@render children()}
@@ -49,11 +49,6 @@
49
49
  } = $props();
50
50
 
51
51
  const fieldId = $derived(id ?? (label ? label.toLowerCase().replace(/\s+/g, '-') : undefined));
52
-
53
- function handleInput(e: TAE) {
54
- value = e.currentTarget.value;
55
- oninput?.(e);
56
- }
57
52
  </script>
58
53
 
59
54
  <div class="{FIELD_WRAP} {cls}">
@@ -75,9 +70,10 @@
75
70
  aria-invalid={error ? 'true' : undefined}
76
71
  aria-describedby={describedBy(fieldId, error, hint)}
77
72
  class="{CONTROL_MULTILINE} {controlBorder(!!error)} {resize ? 'resize-y' : 'resize-none'}"
78
- oninput={handleInput}
79
- {onblur}>{value}</textarea
80
- >
73
+ bind:value
74
+ {oninput}
75
+ {onblur}
76
+ ></textarea>
81
77
 
82
78
  {#if error}
83
79
  <p id={fieldId ? `${fieldId}-error` : undefined} class={FIELD_ERROR}>{error}</p>
@@ -43,7 +43,7 @@
43
43
  <p class="flex-1 text-sm text-fg leading-snug">{t.message}</p>
44
44
  <button
45
45
  type="button"
46
- class="shrink-0 text-faint transition-colors duration-150 hover:text-fg"
46
+ class="shrink-0 rounded text-faint transition-colors duration-150 hover:text-fg outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand/50"
47
47
  aria-label="Dismiss"
48
48
  onclick={() => toast.dismiss(t.id)}
49
49
  >
package/dist/index.d.ts CHANGED
@@ -60,4 +60,4 @@ export { openDialog, closeDialog, dismissDialog, dismissAllDialogs, confirm, set
60
60
  export type { DialogOptions, DialogEntry, DialogSize } from './components/dialog/types.js';
61
61
  export { cn, type ClassValue } from './utils/cn.js';
62
62
  export { getTheme, setTheme, toggleTheme, themeBootScript, type Theme } from './utils/theme.js';
63
- export declare const VERSION = "0.10.1";
63
+ export declare const VERSION = "0.11.2";
package/dist/index.js CHANGED
@@ -71,4 +71,4 @@ export { getTheme, setTheme, toggleTheme, themeBootScript } from './utils/theme.
71
71
  // ── Version ────────────────────────────────────────────────────────────────
72
72
  // Generated from package.json by `pnpm version:sync`. Bump package.json, never
73
73
  // this line; the build and the test suite fail when the two disagree.
74
- export const VERSION = '0.10.1';
74
+ export const VERSION = '0.11.2';
@@ -171,4 +171,24 @@ html[data-theme='light'] {
171
171
  :focus:not(:focus-visible) {
172
172
  outline: none;
173
173
  }
174
+
175
+ /*
176
+ * Every animation in the kit ran regardless of what the reader asked for:
177
+ * the drawer slid, the toast flew in, the pulse and ping looped forever.
178
+ * Honouring the preference once here covers every component, including any
179
+ * added later, which is the only way it stays honoured.
180
+ *
181
+ * Durations are not zeroed. An animation that finishes in 1ms still fires its
182
+ * animationend, so components that wait on one keep working.
183
+ */
184
+ @media (prefers-reduced-motion: reduce) {
185
+ *,
186
+ *::before,
187
+ *::after {
188
+ animation-duration: 1ms !important;
189
+ animation-iteration-count: 1 !important;
190
+ transition-duration: 1ms !important;
191
+ scroll-behavior: auto !important;
192
+ }
193
+ }
174
194
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lyeve-labs/ui-kit",
3
- "version": "0.10.1",
3
+ "version": "0.11.2",
4
4
  "description": "A clean, accessible, palette-aware Svelte 5 component library. The design system behind LyEve.",
5
5
  "license": "MIT",
6
6
  "author": "LyEve Labs <hello@lyeve.com>",
@@ -171,4 +171,24 @@ html[data-theme='light'] {
171
171
  :focus:not(:focus-visible) {
172
172
  outline: none;
173
173
  }
174
+
175
+ /*
176
+ * Every animation in the kit ran regardless of what the reader asked for:
177
+ * the drawer slid, the toast flew in, the pulse and ping looped forever.
178
+ * Honouring the preference once here covers every component, including any
179
+ * added later, which is the only way it stays honoured.
180
+ *
181
+ * Durations are not zeroed. An animation that finishes in 1ms still fires its
182
+ * animationend, so components that wait on one keep working.
183
+ */
184
+ @media (prefers-reduced-motion: reduce) {
185
+ *,
186
+ *::before,
187
+ *::after {
188
+ animation-duration: 1ms !important;
189
+ animation-iteration-count: 1 !important;
190
+ transition-duration: 1ms !important;
191
+ scroll-behavior: auto !important;
192
+ }
193
+ }
174
194
  }