@signal9/era-ui 4.12.1 → 4.14.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 (37) hide show
  1. package/dist/ai/conversation/conversation-empty-state.svelte +1 -1
  2. package/dist/ai/error-panel/error-panel.svelte +1 -1
  3. package/dist/ai/message/message-content.svelte +1 -1
  4. package/dist/ai/prompt-input/prompt-input-textarea.svelte +1 -1
  5. package/dist/ai/response/response.svelte +1 -1
  6. package/dist/ai/swarm/swarm-artifact.svelte +1 -1
  7. package/dist/ai/tool/tool-output.svelte +1 -1
  8. package/dist/apps/llm-shell/llm-shell.svelte +1 -1
  9. package/dist/dev/audit/audits/index.d.ts +2 -1
  10. package/dist/dev/audit/audits/index.js +3 -1
  11. package/dist/dev/audit/audits/prose-leading.d.ts +2 -0
  12. package/dist/dev/audit/audits/prose-leading.js +91 -0
  13. package/dist/era-ui.css +1 -1
  14. package/dist/generated-docs/dialog.md +14 -0
  15. package/dist/generated-docs/llms-full.txt +14 -0
  16. package/dist/generated-docs/manifest.json +3 -2
  17. package/dist/os/notification-center.svelte +3 -1
  18. package/dist/os/toast.svelte +6 -3
  19. package/dist/styles/density.css +19 -0
  20. package/dist/styles/index.css +4 -0
  21. package/dist/styles/themes.css +21 -9
  22. package/dist/ui/accordion/accordion-content.svelte +1 -1
  23. package/dist/ui/alert-dialog/alert-dialog-content.svelte +1 -1
  24. package/dist/ui/alert-dialog/alert-dialog-description.svelte +1 -1
  25. package/dist/ui/code-block/code-block.svelte +1 -1
  26. package/dist/ui/dialog/dialog-content.svelte +25 -3
  27. package/dist/ui/dialog/dialog-content.svelte.d.ts +12 -1
  28. package/dist/ui/dialog/dialog-description.svelte +1 -1
  29. package/dist/ui/dialog/dialog-header.svelte +56 -0
  30. package/dist/ui/dialog/dialog-header.svelte.d.ts +10 -0
  31. package/dist/ui/dialog/index.d.ts +1 -0
  32. package/dist/ui/dialog/index.js +1 -0
  33. package/dist/ui/sheet/index.d.ts +1 -1
  34. package/dist/ui/sheet/index.js +1 -1
  35. package/dist/ui/sheet/sheet-content.svelte +44 -9
  36. package/dist/ui/sheet/sheet-content.svelte.d.ts +49 -13
  37. package/package.json +1 -1
@@ -16,6 +16,7 @@ Composed from sub-components — compose explicitly at the call site:
16
16
  - `Dialog.Title`
17
17
  - `Dialog.Description`
18
18
  - `Dialog.Close`
19
+ - `Dialog.Header`
19
20
 
20
21
  ## Dialog.Trigger
21
22
 
@@ -33,6 +34,11 @@ Inherits all props from `PartProps<Dialog.ContentProps>`.
33
34
 
34
35
  | Prop | Type | Default | Notes |
35
36
  |------|------|---------|-------|
37
+ | `header?` | `Snippet` | — | A top bar in the pane's grammar — pass `Dialog.Header` (usually holding
38
+ `Dialog.Title`). With it the panel goes sectioned like a Card: the bar
39
+ runs edge to edge, the body keeps the padding, and the radius drops to
40
+ rd-md because the panel's corners are now formed by an md bar holding xxs
41
+ controls — the identical law that sizes the pane's own corners. |
36
42
  | `ref?` | `(forwarded)` | `null` | bindable |
37
43
 
38
44
  ## Dialog.Overlay
@@ -68,3 +74,11 @@ Inherits all props from `Omit<ButtonVariants, 'active'>`.
68
74
  |------|------|---------|-------|
69
75
  | `children?` | `Snippet` | — | — |
70
76
  | `ref?` | `(forwarded)` | `null` | bindable |
77
+
78
+ ## Dialog.Header
79
+
80
+ | Prop | Type | Default | Notes |
81
+ |------|------|---------|-------|
82
+ | `dismissable?` | `boolean` | `true` | Render the trailing × (a `Dialog.Close`). Off for flows that must resolve. |
83
+ | `children?` | `Snippet` | — | — |
84
+ | `class?` | `string` | — | — |
@@ -2594,6 +2594,7 @@ Composed from sub-components — compose explicitly at the call site:
2594
2594
  - `Dialog.Title`
2595
2595
  - `Dialog.Description`
2596
2596
  - `Dialog.Close`
2597
+ - `Dialog.Header`
2597
2598
 
2598
2599
  ## Dialog.Trigger
2599
2600
 
@@ -2611,6 +2612,11 @@ Inherits all props from `PartProps<Dialog.ContentProps>`.
2611
2612
 
2612
2613
  | Prop | Type | Default | Notes |
2613
2614
  |------|------|---------|-------|
2615
+ | `header?` | `Snippet` | — | A top bar in the pane's grammar — pass `Dialog.Header` (usually holding
2616
+ `Dialog.Title`). With it the panel goes sectioned like a Card: the bar
2617
+ runs edge to edge, the body keeps the padding, and the radius drops to
2618
+ rd-md because the panel's corners are now formed by an md bar holding xxs
2619
+ controls — the identical law that sizes the pane's own corners. |
2614
2620
  | `ref?` | `(forwarded)` | `null` | bindable |
2615
2621
 
2616
2622
  ## Dialog.Overlay
@@ -2647,6 +2653,14 @@ Inherits all props from `Omit<ButtonVariants, 'active'>`.
2647
2653
  | `children?` | `Snippet` | — | — |
2648
2654
  | `ref?` | `(forwarded)` | `null` | bindable |
2649
2655
 
2656
+ ## Dialog.Header
2657
+
2658
+ | Prop | Type | Default | Notes |
2659
+ |------|------|---------|-------|
2660
+ | `dismissable?` | `boolean` | `true` | Render the trailing × (a `Dialog.Close`). Off for flows that must resolve. |
2661
+ | `children?` | `Snippet` | — | — |
2662
+ | `class?` | `string` | — | — |
2663
+
2650
2664
  <!-- end: dialog -->
2651
2665
 
2652
2666
  <!-- begin: dropdown-menu -->
@@ -572,7 +572,7 @@
572
572
  "slug": "dialog",
573
573
  "title": "Dialog",
574
574
  "summary": "A modal window for content or user input.",
575
- "tokenEstimate": 407,
575
+ "tokenEstimate": 576,
576
576
  "sections": [
577
577
  "Import",
578
578
  "Dialog.Trigger",
@@ -580,7 +580,8 @@
580
580
  "Dialog.Overlay",
581
581
  "Dialog.Title",
582
582
  "Dialog.Description",
583
- "Dialog.Close"
583
+ "Dialog.Close",
584
+ "Dialog.Header"
584
585
  ],
585
586
  "file": "dialog.md"
586
587
  },
@@ -83,7 +83,9 @@
83
83
  <p class="min-w-0 flex-1 truncate text-body text-bright">{n.title}</p>
84
84
  <span class="shrink-0 text-body text-muted tabular-nums">{ago(n.createdAt)}</span>
85
85
  </div>
86
- {#if n.body}<p class="mt-0.5 text-body break-words text-muted">{n.body}</p>{/if}
86
+ {#if n.body}<p class="mt-0.5 text-body leading-body break-words text-muted">
87
+ {n.body}
88
+ </p>{/if}
87
89
  </div>
88
90
  </div>
89
91
  {:else}
@@ -31,17 +31,20 @@
31
31
  <div class="min-w-0 flex-1">
32
32
  <div class="flex items-start gap-(--era-gap)">
33
33
  <p class="min-w-0 flex-1 truncate text-body text-bright">{n.title}</p>
34
+ <!-- The dismiss family's ×: xxs tier, whose default glyph is the 12px
35
+ dismiss ink (the size class that sat on the <X> lost to the Button
36
+ variant's [&_svg] selector and never applied). -->
34
37
  <Button
35
38
  icon
36
- size="icon"
39
+ size="xxs"
37
40
  aria-label="Dismiss"
38
41
  class="-mt-1 -mr-1 shrink-0"
39
42
  onclick={() => notif.dismiss(n.id)}
40
43
  >
41
- <X class="size-(--era-h-xs)" />
44
+ <X />
42
45
  </Button>
43
46
  </div>
44
- {#if n.body}<p class="mt-0.5 text-body break-words text-muted">{n.body}</p>{/if}
47
+ {#if n.body}<p class="mt-0.5 text-body leading-body break-words text-muted">{n.body}</p>{/if}
45
48
  {#if n.actions.length}
46
49
  <div class="mt-(--era-inset-sm) flex flex-wrap gap-(--era-gap)">
47
50
  {#each n.actions as action (action.label)}
@@ -154,6 +154,25 @@
154
154
  /* Inter-item gap */
155
155
  --era-gap: calc(var(--era-sp) * 0.5); /* 4/5/6 */
156
156
 
157
+ /* PROSE LEADING — the line-height of text that WRAPS.
158
+ *
159
+ * Single-line control text has no leading question: era-text-trim collapses
160
+ * the line box to 1 and centres the ink, which is the whole point of it.
161
+ * Wrapping text is the opposite problem — the space BETWEEN lines is the
162
+ * only thing holding the paragraph apart, and it must never inherit a
163
+ * control's line-height (a `leading-none` row with a wrapping child sets its
164
+ * lines colliding).
165
+ *
166
+ * A ratio, not a length: leading scales with the type, not with --era-sp, so
167
+ * it stays a single number across every density mode — dense's 12px text
168
+ * gets 19.2px lines, balanced's 14px gets 22.4px, both the same paragraph
169
+ * colour. It replaces a 50/50 split of Tailwind's leading-relaxed (1.625)
170
+ * and leading-normal (1.5) that had grown between the prose components; 1.6
171
+ * is between them and is the value long-form UI text is usually set at.
172
+ *
173
+ * Public name: `leading-body`, the pair to `text-body`. */
174
+ --era-leading: 1.6;
175
+
157
176
  /* Content insets — text-symmetric padding (vertical centering, reused as
158
177
  horizontal only where the content is a single square glyph). */
159
178
  --era-inset-xxs: max(0px, calc((var(--era-h-xxs) - var(--era-text)) / 2)); /* 3/3/4/6 */
@@ -298,6 +298,10 @@
298
298
  @theme inline {
299
299
  /* Density-derived body size: `text-body` replaces text-[length:var(--era-text)] */
300
300
  --text-body: var(--era-text);
301
+ /* Prose leading — `leading-body` is the pair to `text-body`, and every piece
302
+ * of text in the library that can wrap to a second line carries it. Enforced
303
+ * at runtime by typography/prose-leading. */
304
+ --leading-body: var(--era-leading);
301
305
  /* Radius ladder — Tailwind's own rounded-* names now walk era's concentric
302
306
  * ladder (rd-T = rd-xs + (h-T − h-xs)/2), ride the corners toggle, and
303
307
  * collapse to 0 on square. rounded-item is the menu-row radius
@@ -50,7 +50,19 @@
50
50
  --color-hover: oklch(0.25 0 0);
51
51
  --color-fg: var(--color-11);
52
52
  --color-bright: var(--color-12);
53
- --color-muted: var(--color-10);
53
+ /* Muted is body copy, not a de-emphasis wash — the /text page sets whole
54
+ * paragraphs in it — so it does NOT sit flat on step 10: it leans 30% of the
55
+ * way to --color-fg. Step 10 alone read as "greyed out" at paragraph length
56
+ * (7.80:1 on the default dark page); the lean lands it at 9.11:1, still a
57
+ * clear step below fg for the mixed-tone case (muted prose beside a bright
58
+ * literal) but no longer receding. The lean is toward fg rather than a fixed
59
+ * lightness so it mirrors correctly in light mode (muted DARKENS toward fg)
60
+ * and keeps each theme's own hue family — the tinted scales (adobe, sand)
61
+ * interpolate between two steps of their own ramp, never toward neutral.
62
+ *
63
+ * signalnine sets its muted by hand from measured source colours and is
64
+ * deliberately exempt. */
65
+ --color-muted: color-mix(in oklch, var(--color-10) 70%, var(--color-fg));
54
66
  /* Inline link ink. Declared once here and inherited by every theme —
55
67
  * var(--color-bright) resolves in the element's own context, so a theme only
56
68
  * names this if it wants links to differ from emphasis text (signalnine
@@ -121,7 +133,7 @@
121
133
  --color-hover: oklch(0.8 0 0);
122
134
  --color-fg: var(--color-11);
123
135
  --color-bright: var(--color-12);
124
- --color-muted: var(--color-10);
136
+ --color-muted: color-mix(in oklch, var(--color-10) 70%, var(--color-fg));
125
137
 
126
138
  /* Accent tokens */
127
139
  --color-primary: var(--color-fg);
@@ -160,7 +172,7 @@
160
172
  --color-hover: oklch(0.8 0 0);
161
173
  --color-fg: var(--color-11);
162
174
  --color-bright: var(--color-12);
163
- --color-muted: var(--color-10);
175
+ --color-muted: color-mix(in oklch, var(--color-10) 70%, var(--color-fg));
164
176
 
165
177
  /* Accent tokens */
166
178
  --color-primary: var(--color-fg);
@@ -204,7 +216,7 @@
204
216
  --color-hover: oklch(0.252 0.024 56);
205
217
  --color-fg: var(--color-11);
206
218
  --color-bright: var(--color-12);
207
- --color-muted: var(--color-10);
219
+ --color-muted: color-mix(in oklch, var(--color-10) 70%, var(--color-fg));
208
220
 
209
221
  /* Warm sandstone primary + unexpected turquoise support. */
210
222
  --color-primary: oklch(0.74 0.108 68);
@@ -248,7 +260,7 @@
248
260
  --color-hover: oklch(0.81 0.022 73);
249
261
  --color-fg: var(--color-11);
250
262
  --color-bright: var(--color-12);
251
- --color-muted: var(--color-10);
263
+ --color-muted: color-mix(in oklch, var(--color-10) 70%, var(--color-fg));
252
264
 
253
265
  --color-primary: oklch(0.515 0.16 38);
254
266
  --color-primary-fg: oklch(0.96 0.014 78);
@@ -283,7 +295,7 @@
283
295
  --color-hover: oklch(0.81 0.022 73);
284
296
  --color-fg: var(--color-11);
285
297
  --color-bright: var(--color-12);
286
- --color-muted: var(--color-10);
298
+ --color-muted: color-mix(in oklch, var(--color-10) 70%, var(--color-fg));
287
299
 
288
300
  --color-primary: oklch(0.515 0.16 38);
289
301
  --color-primary-fg: oklch(0.96 0.014 78);
@@ -321,7 +333,7 @@
321
333
  --color-hover: color-mix(in oklch, var(--color-3) 80%, var(--color-2));
322
334
  --color-fg: var(--color-11);
323
335
  --color-bright: var(--color-12);
324
- --color-muted: var(--color-10);
336
+ --color-muted: color-mix(in oklch, var(--color-10) 70%, var(--color-fg));
325
337
 
326
338
  --color-primary: oklch(0.8 0.034 54);
327
339
  --color-primary-fg: var(--color-1);
@@ -359,7 +371,7 @@
359
371
  --color-hover: oklch(0.88 0.016 72);
360
372
  --color-fg: var(--color-11);
361
373
  --color-bright: var(--color-12);
362
- --color-muted: var(--color-10);
374
+ --color-muted: color-mix(in oklch, var(--color-10) 70%, var(--color-fg));
363
375
 
364
376
  --color-primary: oklch(0.55 0.12 58);
365
377
  --color-primary-fg: oklch(0.97 0.008 80);
@@ -394,7 +406,7 @@
394
406
  --color-hover: oklch(0.88 0.016 72);
395
407
  --color-fg: var(--color-11);
396
408
  --color-bright: var(--color-12);
397
- --color-muted: var(--color-10);
409
+ --color-muted: color-mix(in oklch, var(--color-10) 70%, var(--color-fg));
398
410
 
399
411
  --color-primary: oklch(0.55 0.12 58);
400
412
  --color-primary-fg: oklch(0.97 0.008 80);
@@ -8,7 +8,7 @@
8
8
  <Accordion.Content
9
9
  bind:ref
10
10
  class={cn(
11
- 'overflow-hidden border-t border-divider-faded p-(--era-content-p) text-body leading-relaxed text-muted',
11
+ 'overflow-hidden border-t border-divider-faded p-(--era-content-p) text-body leading-body text-muted',
12
12
  className
13
13
  )}
14
14
  {...restProps}
@@ -15,7 +15,7 @@
15
15
  <AlertDialog.Content
16
16
  bind:ref
17
17
  class={cn(
18
- 'fixed top-1/2 left-1/2 z-50 flex w-full max-w-md -translate-x-1/2 -translate-y-1/2 flex-col gap-(--era-gap) rounded-(--era-rd-lg) bg-(--era-surface-bg) p-(--era-pad-md) shadow-(--era-shadow-lg) glass-blur',
18
+ 'fixed top-1/2 left-1/2 z-50 flex w-full max-w-md -translate-x-1/2 -translate-y-1/2 flex-col gap-(--era-gap) rounded-(--era-rd-lg) border border-divider bg-(--era-surface-bg) p-(--era-pad-md) shadow-(--era-shadow-lg) glass-blur',
19
19
  className
20
20
  )}
21
21
  {...restProps}
@@ -11,6 +11,6 @@
11
11
 
12
12
  <AlertDialog.Description
13
13
  bind:ref
14
- class={cn('text-body leading-normal text-muted', className)}
14
+ class={cn('text-body leading-body text-muted', className)}
15
15
  {...restProps}
16
16
  />
@@ -45,7 +45,7 @@
45
45
  {...restProps}
46
46
  >
47
47
  <pre
48
- class="overflow-x-auto px-(--era-field-px) py-(--era-inset-md) pr-[calc(var(--era-field-px)+var(--era-h-sm)+var(--era-field-px))] font-mono text-body leading-relaxed text-fg"><code
48
+ class="overflow-x-auto px-(--era-field-px) py-(--era-inset-md) pr-[calc(var(--era-field-px)+var(--era-h-sm)+var(--era-field-px))] font-mono text-body leading-body text-fg"><code
49
49
  >{#if children}{@render children()}{:else}{code}{/if}</code
50
50
  ></pre>
51
51
  {#if copyable}
@@ -1,25 +1,47 @@
1
1
  <script lang="ts">
2
+ import type { Snippet } from 'svelte';
2
3
  import { Dialog } from 'bits-ui';
3
4
  import { cn, type PartProps } from '../../utils/index.js';
4
5
 
5
6
  let {
6
7
  ref = $bindable(null),
7
8
  children,
9
+ header,
8
10
  class: className,
9
11
  ...restProps
10
- }: PartProps<Dialog.ContentProps> = $props();
12
+ }: PartProps<Dialog.ContentProps> & {
13
+ /**
14
+ * A top bar in the pane's grammar — pass `Dialog.Header` (usually holding
15
+ * `Dialog.Title`). With it the panel goes sectioned like a Card: the bar
16
+ * runs edge to edge, the body keeps the padding, and the radius drops to
17
+ * rd-md because the panel's corners are now formed by an md bar holding xxs
18
+ * controls — the identical law that sizes the pane's own corners.
19
+ */
20
+ header?: Snippet;
21
+ } = $props();
11
22
  </script>
12
23
 
13
24
  <Dialog.Portal>
14
25
  <Dialog.Overlay class="fixed inset-0 z-50 bg-(--era-overlay-bg)" />
26
+ <!-- border-divider: the pane's hairline-edge law. A floating panel must be
27
+ separable from what's behind it, and on the flat surface shadow-lg
28
+ collapses to none — without the hairline the whole edge was a tone step. -->
15
29
  <Dialog.Content
16
30
  bind:ref
17
31
  class={cn(
18
- 'fixed top-1/2 left-1/2 z-50 flex w-full max-w-md -translate-x-1/2 -translate-y-1/2 flex-col gap-(--era-gap) rounded-(--era-rd-lg) bg-(--era-surface-bg) p-(--era-pad-md) shadow-(--era-shadow-lg) glass-blur',
32
+ 'fixed top-1/2 left-1/2 z-50 flex w-full max-w-md -translate-x-1/2 -translate-y-1/2 flex-col border border-divider bg-(--era-surface-bg) shadow-(--era-shadow-lg) glass-blur',
33
+ header ? 'rounded-(--era-rd-md)' : 'gap-(--era-gap) rounded-(--era-rd-lg) p-(--era-pad-md)',
19
34
  className
20
35
  )}
21
36
  {...restProps}
22
37
  >
23
- {@render children?.()}
38
+ {#if header}
39
+ {@render header()}
40
+ <div class="flex min-h-0 flex-col gap-(--era-gap) p-(--era-pad-md)">
41
+ {@render children?.()}
42
+ </div>
43
+ {:else}
44
+ {@render children?.()}
45
+ {/if}
24
46
  </Dialog.Content>
25
47
  </Dialog.Portal>
@@ -1,5 +1,16 @@
1
+ import type { Snippet } from 'svelte';
1
2
  import { Dialog } from 'bits-ui';
2
3
  import { type PartProps } from '../../utils/index.js';
3
- declare const DialogContent: import("svelte").Component<PartProps<Dialog.ContentProps>, {}, "ref">;
4
+ type $$ComponentProps = PartProps<Dialog.ContentProps> & {
5
+ /**
6
+ * A top bar in the pane's grammar — pass `Dialog.Header` (usually holding
7
+ * `Dialog.Title`). With it the panel goes sectioned like a Card: the bar
8
+ * runs edge to edge, the body keeps the padding, and the radius drops to
9
+ * rd-md because the panel's corners are now formed by an md bar holding xxs
10
+ * controls — the identical law that sizes the pane's own corners.
11
+ */
12
+ header?: Snippet;
13
+ };
14
+ declare const DialogContent: import("svelte").Component<$$ComponentProps, {}, "ref">;
4
15
  type DialogContent = ReturnType<typeof DialogContent>;
5
16
  export default DialogContent;
@@ -7,6 +7,6 @@
7
7
 
8
8
  <Dialog.Description
9
9
  bind:ref
10
- class={cn('text-body leading-normal text-muted', className)}
10
+ class={cn('text-body leading-body text-muted', className)}
11
11
  {...restProps}
12
12
  />
@@ -0,0 +1,56 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import { Dialog } from 'bits-ui';
4
+ import X from '@lucide/svelte/icons/x';
5
+ import { Bar } from '../bar';
6
+ import { Button } from '../button';
7
+ import { cn } from '../../utils/index.js';
8
+
9
+ let {
10
+ dismissable = true,
11
+ children,
12
+ class: className
13
+ }: {
14
+ /** Render the trailing × (a `Dialog.Close`). Off for flows that must resolve. */
15
+ dismissable?: boolean;
16
+ children?: Snippet;
17
+ class?: string;
18
+ } = $props();
19
+ </script>
20
+
21
+ <!-- The pane handle's chrome, verbatim, for an overlay's top bar — one grammar
22
+ for every floating panel's header so a dialog and a pane read as the same
23
+ kind of surface:
24
+
25
+ · an md bar holding xxs controls, walls and gap --era-xxs-inset-md
26
+ · the divider drawn OUTSIDE the box (an inset one eats a pixel of every
27
+ child's bottom gap — see Pane.Handle)
28
+ · the leading side at --era-inset-md: a title has no corner to nest, so
29
+ it takes the text inset, the same asymmetry the pane and the
30
+ Conversations row document
31
+ · the × is the dismiss family's: an xxs Button whose tier default IS the
32
+ 12px ink, destructive like Pane.Close
33
+
34
+ Rendered flush by Dialog.Content / Sheet.Content when passed as their
35
+ `header` snippet, so the bar spans the panel edge to edge and the panel's
36
+ radius follows this corner region (rd-md — the same law that sizes the
37
+ pane's own corners). -->
38
+ <Bar
39
+ size="md"
40
+ content="xxs"
41
+ class={cn(
42
+ 'shrink-0 rounded-t-none rounded-b-(--era-rd-md) pl-(--era-inset-md) [box-shadow:0_1px_0_var(--color-divider-faded)] select-none',
43
+ className
44
+ )}
45
+ >
46
+ {@render children?.()}
47
+ {#if dismissable}
48
+ <Dialog.Close>
49
+ {#snippet child({ props })}
50
+ <Button icon size="xxs" tone="destructive" aria-label="Close" class="ml-auto" {...props}>
51
+ <X />
52
+ </Button>
53
+ {/snippet}
54
+ </Dialog.Close>
55
+ {/if}
56
+ </Bar>
@@ -0,0 +1,10 @@
1
+ import type { Snippet } from 'svelte';
2
+ type $$ComponentProps = {
3
+ /** Render the trailing × (a `Dialog.Close`). Off for flows that must resolve. */
4
+ dismissable?: boolean;
5
+ children?: Snippet;
6
+ class?: string;
7
+ };
8
+ declare const DialogHeader: import("svelte").Component<$$ComponentProps, {}, "">;
9
+ type DialogHeader = ReturnType<typeof DialogHeader>;
10
+ export default DialogHeader;
@@ -7,3 +7,4 @@ export { default as Overlay } from './dialog-overlay.svelte';
7
7
  export { default as Title } from './dialog-title.svelte';
8
8
  export { default as Description } from './dialog-description.svelte';
9
9
  export { default as Close } from './dialog-close.svelte';
10
+ export { default as Header } from './dialog-header.svelte';
@@ -7,3 +7,4 @@ export { default as Overlay } from './dialog-overlay.svelte';
7
7
  export { default as Title } from './dialog-title.svelte';
8
8
  export { default as Description } from './dialog-description.svelte';
9
9
  export { default as Close } from './dialog-close.svelte';
10
+ export { default as Header } from './dialog-header.svelte';
@@ -1,2 +1,2 @@
1
- export { Root, Portal, Trigger, Title, Description, Close } from '../dialog/index.js';
1
+ export { Root, Portal, Trigger, Title, Description, Close, Header } from '../dialog/index.js';
2
2
  export { default as Content, sheetVariants } from './sheet-content.svelte';
@@ -1,5 +1,5 @@
1
1
  // Sheet is a Dialog anchored to a screen edge. Root/Trigger/Title/Description/
2
2
  // Close are the Dialog wrappers verbatim (same behavior); only Content differs
3
3
  // (edge-positioned, with a `side` prop).
4
- export { Root, Portal, Trigger, Title, Description, Close } from '../dialog/index.js';
4
+ export { Root, Portal, Trigger, Title, Description, Close, Header } from '../dialog/index.js';
5
5
  export { default as Content, sheetVariants } from './sheet-content.svelte';
@@ -4,38 +4,73 @@
4
4
  // A Sheet is a Dialog pinned to a screen edge — the idiomatic mobile drawer
5
5
  // (bottom for controls, left/right for navigation). Positioning + which
6
6
  // corners round follow the `side`; everything else is the dialog surface.
7
+ // border-divider on every side: the pane's hairline-edge law — on flat,
8
+ // shadow-lg collapses to none and the sheet's edge was a bare tone step.
7
9
  export const sheetVariants = tv({
8
- base: 'fixed z-50 flex flex-col gap-(--era-gap) overflow-y-auto bg-(--era-surface-bg) p-(--era-pad-md) shadow-(--era-shadow-lg) glass-blur',
10
+ base: 'fixed z-50 flex flex-col border border-divider bg-(--era-surface-bg) shadow-(--era-shadow-lg) glass-blur',
9
11
  variants: {
10
12
  side: {
11
- top: 'inset-x-0 top-0 max-h-[85dvh] rounded-b-(--era-rd-lg)',
12
- bottom: 'inset-x-0 bottom-0 max-h-[85dvh] rounded-t-(--era-rd-lg)',
13
- left: 'inset-y-0 left-0 w-[min(20rem,85vw)] rounded-r-(--era-rd-lg)',
14
- right: 'inset-y-0 right-0 w-[min(20rem,85vw)] rounded-l-(--era-rd-lg)'
15
- }
13
+ top: 'inset-x-0 top-0 max-h-[85dvh]',
14
+ bottom: 'inset-x-0 bottom-0 max-h-[85dvh]',
15
+ left: 'inset-y-0 left-0 w-[min(20rem,85vw)]',
16
+ right: 'inset-y-0 right-0 w-[min(20rem,85vw)]'
17
+ },
18
+ /**
19
+ * With a header the sheet is sectioned (the bar flush, the BODY scrolls
20
+ * — scrolling the root would carry the bar away with the content) and
21
+ * its detached corners drop to rd-md: they are formed by an md bar
22
+ * holding xxs controls, the pane's own corner law. Without one, the
23
+ * padded rd-lg surface is unchanged.
24
+ */
25
+ header: { true: '', false: 'gap-(--era-gap) overflow-y-auto p-(--era-pad-md)' }
16
26
  },
17
- defaultVariants: { side: 'bottom' }
27
+ compoundVariants: [
28
+ { side: 'top', header: false, class: 'rounded-b-(--era-rd-lg)' },
29
+ { side: 'bottom', header: false, class: 'rounded-t-(--era-rd-lg)' },
30
+ { side: 'left', header: false, class: 'rounded-r-(--era-rd-lg)' },
31
+ { side: 'right', header: false, class: 'rounded-l-(--era-rd-lg)' },
32
+ { side: 'top', header: true, class: 'rounded-b-(--era-rd-md)' },
33
+ { side: 'bottom', header: true, class: 'rounded-t-(--era-rd-md)' },
34
+ { side: 'left', header: true, class: 'rounded-r-(--era-rd-md)' },
35
+ { side: 'right', header: true, class: 'rounded-l-(--era-rd-md)' }
36
+ ],
37
+ defaultVariants: { side: 'bottom', header: false }
18
38
  });
19
39
  </script>
20
40
 
21
41
  <script lang="ts">
42
+ import type { Snippet } from 'svelte';
22
43
  import { Dialog } from 'bits-ui';
23
44
 
24
45
  let {
25
46
  ref = $bindable(null),
26
47
  side = 'bottom',
27
48
  children,
49
+ header,
28
50
  class: className,
29
51
  ...restProps
30
52
  }: PartProps<Dialog.ContentProps> & {
31
53
  /** Screen edge the sheet is anchored to. */
32
54
  side?: 'top' | 'right' | 'bottom' | 'left';
55
+ /** A top bar in the pane's grammar — pass `Sheet.Header` holding `Sheet.Title`. */
56
+ header?: Snippet;
33
57
  } = $props();
34
58
  </script>
35
59
 
36
60
  <Dialog.Portal>
37
61
  <Dialog.Overlay class="fixed inset-0 z-50 bg-(--era-overlay-bg)" />
38
- <Dialog.Content bind:ref class={cn(sheetVariants({ side }), className)} {...restProps}>
39
- {@render children?.()}
62
+ <Dialog.Content
63
+ bind:ref
64
+ class={cn(sheetVariants({ side, header: header != null }), className)}
65
+ {...restProps}
66
+ >
67
+ {#if header}
68
+ {@render header()}
69
+ <div class="flex min-h-0 flex-1 flex-col gap-(--era-gap) overflow-y-auto p-(--era-pad-md)">
70
+ {@render children?.()}
71
+ </div>
72
+ {:else}
73
+ {@render children?.()}
74
+ {/if}
40
75
  </Dialog.Content>
41
76
  </Dialog.Portal>
@@ -1,30 +1,66 @@
1
1
  import { type PartProps } from '../../utils/index.js';
2
2
  export declare const sheetVariants: import("tailwind-variants").TVReturnType<{
3
3
  side: {
4
- top: "inset-x-0 top-0 max-h-[85dvh] rounded-b-(--era-rd-lg)";
5
- bottom: "inset-x-0 bottom-0 max-h-[85dvh] rounded-t-(--era-rd-lg)";
6
- left: "inset-y-0 left-0 w-[min(20rem,85vw)] rounded-r-(--era-rd-lg)";
7
- right: "inset-y-0 right-0 w-[min(20rem,85vw)] rounded-l-(--era-rd-lg)";
4
+ top: "inset-x-0 top-0 max-h-[85dvh]";
5
+ bottom: "inset-x-0 bottom-0 max-h-[85dvh]";
6
+ left: "inset-y-0 left-0 w-[min(20rem,85vw)]";
7
+ right: "inset-y-0 right-0 w-[min(20rem,85vw)]";
8
8
  };
9
- }, undefined, "fixed z-50 flex flex-col gap-(--era-gap) overflow-y-auto bg-(--era-surface-bg) p-(--era-pad-md) shadow-(--era-shadow-lg) glass-blur", {
9
+ /**
10
+ * With a header the sheet is sectioned (the bar flush, the BODY scrolls
11
+ * — scrolling the root would carry the bar away with the content) and
12
+ * its detached corners drop to rd-md: they are formed by an md bar
13
+ * holding xxs controls, the pane's own corner law. Without one, the
14
+ * padded rd-lg surface is unchanged.
15
+ */
16
+ header: {
17
+ true: "";
18
+ false: "gap-(--era-gap) overflow-y-auto p-(--era-pad-md)";
19
+ };
20
+ }, undefined, "fixed z-50 flex flex-col border border-divider bg-(--era-surface-bg) shadow-(--era-shadow-lg) glass-blur", {
10
21
  side: {
11
- top: "inset-x-0 top-0 max-h-[85dvh] rounded-b-(--era-rd-lg)";
12
- bottom: "inset-x-0 bottom-0 max-h-[85dvh] rounded-t-(--era-rd-lg)";
13
- left: "inset-y-0 left-0 w-[min(20rem,85vw)] rounded-r-(--era-rd-lg)";
14
- right: "inset-y-0 right-0 w-[min(20rem,85vw)] rounded-l-(--era-rd-lg)";
22
+ top: "inset-x-0 top-0 max-h-[85dvh]";
23
+ bottom: "inset-x-0 bottom-0 max-h-[85dvh]";
24
+ left: "inset-y-0 left-0 w-[min(20rem,85vw)]";
25
+ right: "inset-y-0 right-0 w-[min(20rem,85vw)]";
26
+ };
27
+ /**
28
+ * With a header the sheet is sectioned (the bar flush, the BODY scrolls
29
+ * — scrolling the root would carry the bar away with the content) and
30
+ * its detached corners drop to rd-md: they are formed by an md bar
31
+ * holding xxs controls, the pane's own corner law. Without one, the
32
+ * padded rd-lg surface is unchanged.
33
+ */
34
+ header: {
35
+ true: "";
36
+ false: "gap-(--era-gap) overflow-y-auto p-(--era-pad-md)";
15
37
  };
16
38
  }, undefined, import("tailwind-variants").TVReturnTypeLike<{
17
39
  side: {
18
- top: "inset-x-0 top-0 max-h-[85dvh] rounded-b-(--era-rd-lg)";
19
- bottom: "inset-x-0 bottom-0 max-h-[85dvh] rounded-t-(--era-rd-lg)";
20
- left: "inset-y-0 left-0 w-[min(20rem,85vw)] rounded-r-(--era-rd-lg)";
21
- right: "inset-y-0 right-0 w-[min(20rem,85vw)] rounded-l-(--era-rd-lg)";
40
+ top: "inset-x-0 top-0 max-h-[85dvh]";
41
+ bottom: "inset-x-0 bottom-0 max-h-[85dvh]";
42
+ left: "inset-y-0 left-0 w-[min(20rem,85vw)]";
43
+ right: "inset-y-0 right-0 w-[min(20rem,85vw)]";
44
+ };
45
+ /**
46
+ * With a header the sheet is sectioned (the bar flush, the BODY scrolls
47
+ * — scrolling the root would carry the bar away with the content) and
48
+ * its detached corners drop to rd-md: they are formed by an md bar
49
+ * holding xxs controls, the pane's own corner law. Without one, the
50
+ * padded rd-lg surface is unchanged.
51
+ */
52
+ header: {
53
+ true: "";
54
+ false: "gap-(--era-gap) overflow-y-auto p-(--era-pad-md)";
22
55
  };
23
56
  }, undefined>>;
57
+ import type { Snippet } from 'svelte';
24
58
  import { Dialog } from 'bits-ui';
25
59
  type $$ComponentProps = PartProps<Dialog.ContentProps> & {
26
60
  /** Screen edge the sheet is anchored to. */
27
61
  side?: 'top' | 'right' | 'bottom' | 'left';
62
+ /** A top bar in the pane's grammar — pass `Sheet.Header` holding `Sheet.Title`. */
63
+ header?: Snippet;
28
64
  };
29
65
  declare const SheetContent: import("svelte").Component<$$ComponentProps, {}, "ref">;
30
66
  type SheetContent = ReturnType<typeof SheetContent>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signal9/era-ui",
3
- "version": "4.12.1",
3
+ "version": "4.14.0",
4
4
  "scripts": {
5
5
  "dev": "vite dev --host",
6
6
  "build": "vite build && npm run prepack",