@lyeve-labs/ui-kit 0.18.0 → 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 (50) hide show
  1. package/README.md +11 -4
  2. package/dist/components/AccordionItem.svelte +1 -1
  3. package/dist/components/AppShell.svelte +3 -3
  4. package/dist/components/Autocomplete.svelte +4 -4
  5. package/dist/components/AvatarGroup.svelte +2 -2
  6. package/dist/components/Breadcrumb.svelte +1 -1
  7. package/dist/components/Button.svelte +34 -9
  8. package/dist/components/ButtonGroup.svelte +3 -3
  9. package/dist/components/Card.svelte +1 -1
  10. package/dist/components/Checkbox.svelte +1 -1
  11. package/dist/components/CheckboxGroup.svelte +1 -1
  12. package/dist/components/Collapsible.svelte +3 -3
  13. package/dist/components/CopyButton.svelte +1 -1
  14. package/dist/components/DatePicker.svelte +2 -2
  15. package/dist/components/DateTimePicker.svelte +1 -1
  16. package/dist/components/Drawer.svelte +1 -1
  17. package/dist/components/Dropdown.svelte +1 -1
  18. package/dist/components/Field.svelte +1 -1
  19. package/dist/components/Input.svelte +1 -1
  20. package/dist/components/Label.svelte +1 -1
  21. package/dist/components/MultiSelect.svelte +4 -4
  22. package/dist/components/NumberInput.svelte +3 -3
  23. package/dist/components/Pagination.svelte +164 -65
  24. package/dist/components/Pagination.svelte.d.ts +39 -2
  25. package/dist/components/PasswordInput.svelte +4 -4
  26. package/dist/components/Radio.svelte +1 -1
  27. package/dist/components/RadioGroup.svelte +1 -1
  28. package/dist/components/SearchInput.svelte +4 -4
  29. package/dist/components/SegmentedControl.svelte +3 -1
  30. package/dist/components/Select.svelte +6 -6
  31. package/dist/components/SidebarNav.svelte +20 -5
  32. package/dist/components/Table.svelte +114 -1
  33. package/dist/components/Table.svelte.d.ts +37 -0
  34. package/dist/components/Tabs.svelte +1 -1
  35. package/dist/components/Tag.svelte +1 -1
  36. package/dist/components/Textarea.svelte +1 -1
  37. package/dist/components/ThemeToggle.svelte +1 -1
  38. package/dist/components/TimePicker.svelte +2 -2
  39. package/dist/components/Toaster.svelte +2 -2
  40. package/dist/components/Toggle.svelte +10 -4
  41. package/dist/components/TreeView.svelte +4 -4
  42. package/dist/components/dialog/Dialog.svelte +1 -1
  43. package/dist/index.d.ts +2 -1
  44. package/dist/index.js +1 -1
  45. package/dist/internal/href.d.ts +13 -0
  46. package/dist/internal/href.js +27 -0
  47. package/dist/internal/panel.js +1 -1
  48. package/dist/styles/theme.css +53 -0
  49. package/package.json +3 -4
  50. package/src/lib/styles/theme.css +53 -0
@@ -1,3 +1,11 @@
1
+ /**
2
+ * How a body cell treats content wider than its column.
3
+ *
4
+ * `wrap` breaks anywhere it has to. `truncate` keeps the cell to one line and
5
+ * ends it with an ellipsis. `auto` is the browser's own table algorithm, with
6
+ * no cap of any kind.
7
+ */
8
+ export type CellFit = 'auto' | 'wrap' | 'truncate';
1
9
  import type { Snippet } from 'svelte';
2
10
  interface Props {
3
11
  striped?: boolean;
@@ -10,6 +18,35 @@ interface Props {
10
18
  * else, which is a landmark the reader has to enter to identify.
11
19
  */
12
20
  label?: string;
21
+ /**
22
+ * What a body cell does with content too wide for its column.
23
+ *
24
+ * `wrap` by default, and the default is not `auto`. A table constrained
25
+ * nothing: one cell holding an unbroken token - an API key, a signed URL,
26
+ * a base64 payload - has no break opportunity in it, so the auto table
27
+ * algorithm sizes the column to the whole string and the table grows to
28
+ * whatever that comes to. One measured page took its container from no
29
+ * overflow at all to 3,086px of it, and the audit log overran its own
30
+ * container by 419px at 390px wide.
31
+ *
32
+ * `wrap` fixes exactly that and nothing else. It is `overflow-wrap:
33
+ * anywhere`, which is the one value that shrinks the column's min-content
34
+ * width, so a paragraph still breaks at its spaces and only a token with
35
+ * no spaces in it is broken mid-string. Prose in a cell renders as it
36
+ * always did.
37
+ *
38
+ * The default wraps rather than truncates because wrapping is the choice
39
+ * that cannot lose anything. Truncation hides, and a table that hides is
40
+ * worse than one that scrolls, so it is opted into.
41
+ */
42
+ cell?: CellFit;
43
+ /**
44
+ * The width a truncating cell stops at. Any CSS length.
45
+ *
46
+ * Read through a custom property, so a single column can disagree with the
47
+ * table by setting `--cell-truncate` in its own style attribute.
48
+ */
49
+ truncateAt?: string;
13
50
  class?: string;
14
51
  children: Snippet;
15
52
  }
@@ -27,7 +27,7 @@
27
27
  {tab.label}
28
28
  {#if tab.count !== undefined}
29
29
  <span
30
- class="ml-1.5 inline-flex items-center justify-center min-w-4.5 h-4.5 px-1
30
+ class="ms-1.5 inline-flex items-center justify-center min-w-4.5 h-4.5 px-1
31
31
  rounded-full text-[10px] font-semibold
32
32
  {active === tab.id ? 'bg-brand/40 text-brand' : 'bg-surface-2 text-faint'}"
33
33
  >
@@ -37,7 +37,7 @@
37
37
  type="button"
38
38
  onclick={onremove}
39
39
  aria-label="Remove {label}"
40
- class="ml-0.5 rounded transition-opacity hover:opacity-60 leading-none"
40
+ class="ms-0.5 rounded transition-opacity hover:opacity-60 leading-none"
41
41
  >
42
42
  <svg
43
43
  width="10"
@@ -54,7 +54,7 @@
54
54
  <div class="{FIELD_WRAP} {cls}">
55
55
  {#if label}
56
56
  <label for={fieldId} class={FIELD_LABEL}>
57
- {label}{#if required}<span class="text-danger ml-0.5" aria-hidden="true">*</span>{/if}
57
+ {label}{#if required}<span class="text-danger ms-0.5" aria-hidden="true">*</span>{/if}
58
58
  </label>
59
59
  {/if}
60
60
 
@@ -81,7 +81,7 @@
81
81
  data-print="hide"
82
82
  data-theme-preference={preference}
83
83
  aria-label="Theme: {LABEL[preference]}. Switch to {LABEL[upcoming]}."
84
- class="rounded-lg p-2 text-faint transition-colors duration-150 hover:bg-surface-2 hover:text-fg {klass}"
84
+ class="rounded-lg p-2 text-faint transition-colors duration-150 hover:bg-surface-2 hover:text-fg active:bg-line active:text-fg {klass}"
85
85
  >
86
86
  <!-- The icon shows the state the control is in, not the one it moves to. It
87
87
  showed the destination while the label named the same destination, which
@@ -401,7 +401,7 @@
401
401
  takes its name from this same element. The hour keeps its own aria-label,
402
402
  which is what a screen reader reads for it. -->
403
403
  <label id="{fieldId}-label" for="{fieldId}-hour" class={FIELD_LABEL}>
404
- {label}{#if required}<span class="ml-0.5 text-danger" aria-hidden="true">*</span>{/if}
404
+ {label}{#if required}<span class="ms-0.5 text-danger" aria-hidden="true">*</span>{/if}
405
405
  </label>
406
406
  {/if}
407
407
 
@@ -459,7 +459,7 @@
459
459
  aria-required={required ? 'true' : undefined}
460
460
  onchange={onMeridiemChange}
461
461
  onkeydown={onMeridiemKeydown}
462
- class="{CONTROL_SEGMENT} ml-1"
462
+ class="{CONTROL_SEGMENT} ms-1"
463
463
  >
464
464
  <option value="AM">AM</option>
465
465
  <option value="PM">PM</option>
@@ -18,7 +18,7 @@
18
18
  change. -->
19
19
  <div
20
20
  data-print="hide"
21
- class="pointer-events-none fixed bottom-4 right-4 z-toast flex w-80 max-w-[calc(100vw-2rem)] flex-col gap-2"
21
+ class="pointer-events-none fixed bottom-4 end-4 z-toast flex w-80 max-w-[calc(100vw-2rem)] flex-col gap-2"
22
22
  role="status"
23
23
  aria-live="polite"
24
24
  aria-atomic="false"
@@ -26,7 +26,7 @@
26
26
  {#each toast.items as t (t.id)}
27
27
  <div
28
28
  class="pointer-events-auto flex items-start gap-3 overflow-hidden rounded-lg border border-line
29
- bg-surface pl-0 pr-3 py-3 shadow-xl animate-[toast-in_var(--duration-toast-in)_ease-out]"
29
+ bg-surface ps-0 pe-3 py-3 shadow-xl animate-[toast-in_var(--duration-toast-in)_ease-out]"
30
30
  >
31
31
  <span class="w-1 self-stretch shrink-0 {styles[statusTone(t.tone)].bar}"></span>
32
32
  <span
@@ -27,13 +27,19 @@
27
27
  };
28
28
 
29
29
  const thumb: Record<'sm' | 'md', string> = {
30
- sm: 'w-3 h-3 top-0.5 left-0.5',
31
- md: 'w-4 h-4 top-0.5 left-0.5',
30
+ sm: 'w-3 h-3 top-0.5 start-0.5',
31
+ md: 'w-4 h-4 top-0.5 start-0.5',
32
32
  };
33
33
 
34
+ /*
35
+ * The travel is mirrored by hand. `start-0.5` moves the resting knob to the
36
+ * right-hand end of the track in a right-to-left page, and a positive
37
+ * translate from there would carry it straight off the track; there is no
38
+ * logical translate utility, so the sign is flipped instead.
39
+ */
34
40
  const thumbOn: Record<'sm' | 'md', string> = {
35
- sm: 'translate-x-4',
36
- md: 'translate-x-5',
41
+ sm: 'translate-x-4 rtl:-translate-x-4',
42
+ md: 'translate-x-5 rtl:-translate-x-5',
37
43
  };
38
44
 
39
45
  function handleClick() {
@@ -270,11 +270,11 @@
270
270
  scan, so no rule is generated and every row draws flush left.
271
271
  -->
272
272
  <div
273
- class="flex w-full items-center gap-2 rounded-md py-1.5 pr-2 text-sm transition-colors duration-150 {rowPaint(
273
+ class="flex w-full items-center gap-2 rounded-md py-1.5 pe-2 text-sm transition-colors duration-150 {rowPaint(
274
274
  selected === node.id,
275
275
  !!node.disabled,
276
276
  )}"
277
- style="padding-left: calc(var(--spacing-inline) + {depth} * var(--spacing-stack))"
277
+ style="padding-inline-start: calc(var(--spacing-inline) + {depth} * var(--spacing-stack))"
278
278
  >
279
279
  {#if isBranch(node)}
280
280
  <span
@@ -283,7 +283,7 @@
283
283
  >
284
284
  <ChevronRight
285
285
  size={14}
286
- class="transition-transform duration-150 {open ? 'rotate-90' : ''}"
286
+ class="transition-transform duration-150 {open ? 'rotate-90' : 'rtl:rotate-180'}"
287
287
  />
288
288
  </span>
289
289
  {:else}
@@ -321,7 +321,7 @@
321
321
  <span class="truncate">{node.label}</span>
322
322
 
323
323
  {#if node.badge !== undefined}
324
- <span class="ml-auto shrink-0 pl-2 text-xs text-faint">{node.badge}</span>
324
+ <span class="ms-auto shrink-0 ps-2 text-xs text-faint">{node.badge}</span>
325
325
  {/if}
326
326
  </div>
327
327
 
@@ -153,7 +153,7 @@
153
153
 
154
154
  {#if !entry.options.persistent}
155
155
  <button
156
- class="inline-flex items-center justify-center w-8 h-8 -mr-2 rounded-lg
156
+ class="inline-flex items-center justify-center w-8 h-8 -me-2 rounded-lg
157
157
  text-muted hover:text-fg hover:bg-surface-2 transition-colors duration-150 shrink-0"
158
158
  onclick={() => handleClose()}
159
159
  aria-label="Close"
package/dist/index.d.ts CHANGED
@@ -20,6 +20,7 @@ export { default as Table } from './components/Table.svelte';
20
20
  export { default as DescriptionList } from './components/DescriptionList.svelte';
21
21
  export { default as Toolbar } from './components/Toolbar.svelte';
22
22
  export { default as TreeView } from './components/TreeView.svelte';
23
+ export type { CellFit } from './components/Table.svelte';
23
24
  export type { PageWidth, SectionVariant } from './internal/layout.js';
24
25
  export type { TreeNode, TriState } from './internal/tree.js';
25
26
  export { default as Button } from './components/Button.svelte';
@@ -88,4 +89,4 @@ export type { ConfirmOptions } from './components/dialog/dialog-manager.svelte.j
88
89
  export type { DialogOptions, DialogEntry, DialogSize } from './components/dialog/types.js';
89
90
  export { cn, type ClassValue } from './utils/cn.js';
90
91
  export { getTheme, getThemePreference, nextThemePreference, resolveTheme, setTheme, setThemePreference, systemTheme, themeBootScript, toggleTheme, watchSystemTheme, THEME_PREFERENCES, type Theme, type ThemePreference, } from './utils/theme.js';
91
- export declare const VERSION = "0.18.0";
92
+ export declare const VERSION = "0.19.0";
package/dist/index.js CHANGED
@@ -96,4 +96,4 @@ export { getTheme, getThemePreference, nextThemePreference, resolveTheme, setThe
96
96
  // ── Version ────────────────────────────────────────────────────────────────
97
97
  // Generated from package.json by `pnpm version:sync`. Bump package.json, never
98
98
  // this line; the build and the test suite fail when the two disagree.
99
- export const VERSION = '0.18.0';
99
+ export const VERSION = '0.19.0';
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The one rule the kit applies to an href it did not write itself.
3
+ *
4
+ * A component takes a URL from its caller, and a caller takes it from data, so
5
+ * `javascript:` and `data:` arrive as markup and leave as a script the page
6
+ * never wrote. Button carried this rule inline and Pagination now builds hrefs
7
+ * from a caller supplied function, and a rule kept in two places is a rule that
8
+ * gets fixed in one of them.
9
+ *
10
+ * Not exported from the package entry point - this is an implementation detail.
11
+ */
12
+ /** The href as given, or nothing when its scheme is one no component will emit. */
13
+ export declare function safeHref(href: string | undefined | null): string | undefined;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The one rule the kit applies to an href it did not write itself.
3
+ *
4
+ * A component takes a URL from its caller, and a caller takes it from data, so
5
+ * `javascript:` and `data:` arrive as markup and leave as a script the page
6
+ * never wrote. Button carried this rule inline and Pagination now builds hrefs
7
+ * from a caller supplied function, and a rule kept in two places is a rule that
8
+ * gets fixed in one of them.
9
+ *
10
+ * Not exported from the package entry point - this is an implementation detail.
11
+ */
12
+ /**
13
+ * The characters a browser drops before it reads the scheme.
14
+ *
15
+ * The URL parser trims leading and trailing ASCII whitespace and strips tab,
16
+ * line feed and carriage return from anywhere in the value, so a tab written
17
+ * inside the word `javascript` still navigates while an anchored scheme test on
18
+ * the raw string sees an unknown scheme and allows it through.
19
+ */
20
+ const IGNORED = /[\u0000-\u0020]/g;
21
+ const DENIED = /^(?:javascript|data|vbscript):/i;
22
+ /** The href as given, or nothing when its scheme is one no component will emit. */
23
+ export function safeHref(href) {
24
+ if (!href)
25
+ return undefined;
26
+ return DENIED.test(href.replace(IGNORED, '')) ? undefined : href;
27
+ }
@@ -55,7 +55,7 @@ export const PANEL_LIST = 'max-h-panel-max overflow-y-auto overscroll-contain py
55
55
  * No background and no text colour of its own: it inherits both from the
56
56
  * surface, which leaves each state below a single utility to override.
57
57
  */
58
- export const PANEL_OPTION = 'flex w-full items-center gap-2.5 px-3 py-2 text-left text-sm ' +
58
+ export const PANEL_OPTION = 'flex w-full items-center gap-2.5 px-3 py-2 text-start text-sm ' +
59
59
  'transition-colors duration-150 outline-none hover:bg-surface-2 ' +
60
60
  'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand';
61
61
  /**
@@ -151,6 +151,8 @@
151
151
  * Body 16px 1.55 0 400 Default body copy *
152
152
  * Caption 13px 1.50 0 400 Labels, metadata *
153
153
  * Mono 18px 1.50 0 500 Code, eyebrows *
154
+ * The ramp the guideline states, for marketing surfaces. *
155
+ * The scale the console renders is declared below it. *
154
156
  * */
155
157
  --text-display: 4rem; /* 64px */
156
158
  --text-h1: 2.75rem; /* 44px */
@@ -173,6 +175,57 @@
173
175
  --tracking-h2: -0.02em;
174
176
  --tracking-h3: -0.015em;
175
177
 
178
+ /* -- Typography - console type scale --------------------------- *
179
+ * Token Size Line Where it renders *
180
+ * text-xs 12px 16px labels, table heads, metadata, hints*
181
+ * text-sm 14px 20px body copy and every control *
182
+ * text-base 16px 24px a card title, the large button *
183
+ * text-lg 18px 28px a dialog title, a small stat value *
184
+ * text-xl 20px 28px the large wordmark *
185
+ * text-2xl 24px 32px a stat value, an empty-state glyph *
186
+ *
187
+ * Two scales, on purpose, and this is the one the components
188
+ * are drawn in.
189
+ *
190
+ * The ramp above is the brand guideline's, and it is a marketing
191
+ * ramp: it runs 64 / 44 / 32 / 22 / 16 / 13, holds nothing
192
+ * between 16px and 22px, and has no step at 14px at all. A
193
+ * console's body copy is 14px and its labels are 12px, both
194
+ * below the ramp's floor, so adopting the ramp wholesale would
195
+ * have meant restyling every screen to fit a scale drawn for a
196
+ * landing page. The ramp keeps its steps for the surfaces it was
197
+ * drawn for, and the two overlap at exactly the two steps a
198
+ * console does reach: PageHeader takes H2 for the page title and
199
+ * SectionHeading takes H3 for a section head.
200
+ *
201
+ * These are Tailwind's own steps at Tailwind's own values,
202
+ * stated here rather than inherited silently. Nothing renders
203
+ * one pixel differently for it. What changes is that the scale
204
+ * the components are built from is now declared beside the one
205
+ * they are not, so a designer reading this file sees both, and
206
+ * moving console body copy to 15px is one line here instead of
207
+ * 49 class attributes. Restated rather than renamed: an app
208
+ * writing `text-sm` in its own markup and a component writing it
209
+ * in the kit's must mean the same size, and a private spelling
210
+ * of the same 14px would be a second name for one thing.
211
+ *
212
+ * A step is declared with its leading, because a --text-* token
213
+ * sets font-size and nothing else. Only the six steps the kit
214
+ * actually renders are declared; the larger ones a marketing
215
+ * page reaches for stay as Tailwind ships them. */
216
+ --text-xs: 0.75rem; /* 12px */
217
+ --text-xs--line-height: calc(1 / 0.75); /* 16px */
218
+ --text-sm: 0.875rem; /* 14px */
219
+ --text-sm--line-height: calc(1.25 / 0.875); /* 20px */
220
+ --text-base: 1rem; /* 16px */
221
+ --text-base--line-height: calc(1.5 / 1); /* 24px */
222
+ --text-lg: 1.125rem; /* 18px */
223
+ --text-lg--line-height: calc(1.75 / 1.125); /* 28px */
224
+ --text-xl: 1.25rem; /* 20px */
225
+ --text-xl--line-height: calc(1.75 / 1.25); /* 28px */
226
+ --text-2xl: 1.5rem; /* 24px */
227
+ --text-2xl--line-height: calc(2 / 1.5); /* 32px */
228
+
176
229
  /* ── Spacing grid · 4px base (semantic shortcuts) ────────────── *
177
230
  * Tailwind's default 0.25rem (4px) grid is unchanged. *
178
231
  * These tokens add named slots for common component spacing. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lyeve-labs/ui-kit",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
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>",
@@ -44,16 +44,15 @@
44
44
  "./package.json": "./package.json"
45
45
  },
46
46
  "peerDependencies": {
47
+ "@lucide/svelte": ">=0.511.0 <2",
47
48
  "svelte": "^5.0.0",
48
49
  "tailwindcss": "^4"
49
50
  },
50
51
  "publishConfig": {
51
52
  "access": "public"
52
53
  },
53
- "dependencies": {
54
- "@lucide/svelte": "^0.511.0"
55
- },
56
54
  "devDependencies": {
55
+ "@lucide/svelte": "^0.511.0",
57
56
  "@sveltejs/package": "^2.5.7",
58
57
  "@sveltejs/vite-plugin-svelte": "^4.0.4",
59
58
  "@testing-library/svelte": "^5.3.1",
@@ -151,6 +151,8 @@
151
151
  * Body 16px 1.55 0 400 Default body copy *
152
152
  * Caption 13px 1.50 0 400 Labels, metadata *
153
153
  * Mono 18px 1.50 0 500 Code, eyebrows *
154
+ * The ramp the guideline states, for marketing surfaces. *
155
+ * The scale the console renders is declared below it. *
154
156
  * */
155
157
  --text-display: 4rem; /* 64px */
156
158
  --text-h1: 2.75rem; /* 44px */
@@ -173,6 +175,57 @@
173
175
  --tracking-h2: -0.02em;
174
176
  --tracking-h3: -0.015em;
175
177
 
178
+ /* -- Typography - console type scale --------------------------- *
179
+ * Token Size Line Where it renders *
180
+ * text-xs 12px 16px labels, table heads, metadata, hints*
181
+ * text-sm 14px 20px body copy and every control *
182
+ * text-base 16px 24px a card title, the large button *
183
+ * text-lg 18px 28px a dialog title, a small stat value *
184
+ * text-xl 20px 28px the large wordmark *
185
+ * text-2xl 24px 32px a stat value, an empty-state glyph *
186
+ *
187
+ * Two scales, on purpose, and this is the one the components
188
+ * are drawn in.
189
+ *
190
+ * The ramp above is the brand guideline's, and it is a marketing
191
+ * ramp: it runs 64 / 44 / 32 / 22 / 16 / 13, holds nothing
192
+ * between 16px and 22px, and has no step at 14px at all. A
193
+ * console's body copy is 14px and its labels are 12px, both
194
+ * below the ramp's floor, so adopting the ramp wholesale would
195
+ * have meant restyling every screen to fit a scale drawn for a
196
+ * landing page. The ramp keeps its steps for the surfaces it was
197
+ * drawn for, and the two overlap at exactly the two steps a
198
+ * console does reach: PageHeader takes H2 for the page title and
199
+ * SectionHeading takes H3 for a section head.
200
+ *
201
+ * These are Tailwind's own steps at Tailwind's own values,
202
+ * stated here rather than inherited silently. Nothing renders
203
+ * one pixel differently for it. What changes is that the scale
204
+ * the components are built from is now declared beside the one
205
+ * they are not, so a designer reading this file sees both, and
206
+ * moving console body copy to 15px is one line here instead of
207
+ * 49 class attributes. Restated rather than renamed: an app
208
+ * writing `text-sm` in its own markup and a component writing it
209
+ * in the kit's must mean the same size, and a private spelling
210
+ * of the same 14px would be a second name for one thing.
211
+ *
212
+ * A step is declared with its leading, because a --text-* token
213
+ * sets font-size and nothing else. Only the six steps the kit
214
+ * actually renders are declared; the larger ones a marketing
215
+ * page reaches for stay as Tailwind ships them. */
216
+ --text-xs: 0.75rem; /* 12px */
217
+ --text-xs--line-height: calc(1 / 0.75); /* 16px */
218
+ --text-sm: 0.875rem; /* 14px */
219
+ --text-sm--line-height: calc(1.25 / 0.875); /* 20px */
220
+ --text-base: 1rem; /* 16px */
221
+ --text-base--line-height: calc(1.5 / 1); /* 24px */
222
+ --text-lg: 1.125rem; /* 18px */
223
+ --text-lg--line-height: calc(1.75 / 1.125); /* 28px */
224
+ --text-xl: 1.25rem; /* 20px */
225
+ --text-xl--line-height: calc(1.75 / 1.25); /* 28px */
226
+ --text-2xl: 1.5rem; /* 24px */
227
+ --text-2xl--line-height: calc(2 / 1.5); /* 32px */
228
+
176
229
  /* ── Spacing grid · 4px base (semantic shortcuts) ────────────── *
177
230
  * Tailwind's default 0.25rem (4px) grid is unchanged. *
178
231
  * These tokens add named slots for common component spacing. */