@signal9/era-ui 4.12.0 → 4.13.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.
@@ -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
  },
@@ -31,14 +31,17 @@
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
47
  {#if n.body}<p class="mt-0.5 text-body break-words text-muted">{n.body}</p>{/if}
@@ -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}
@@ -5,7 +5,13 @@
5
5
  // min-w matches height (design rule #5): a single glyph — a count, a
6
6
  // keycap letter — renders as a perfect square/circle instead of a
7
7
  // skinny pill; wider content is unaffected.
8
- base: 'inline-flex h-(--era-h-xxs) min-w-(--era-h-xxs) items-center justify-center gap-(--era-gap) rounded-(--era-rd-xxs) px-(--era-pill-xxs) font-mono text-body leading-none has-[svg]:gap-(--era-inset-xxs) era-text-trim',
8
+ //
9
+ // shrink-0 + whitespace-nowrap: a pill IS its text's size — it has no slack
10
+ // to give up, so letting a flex ancestor squeeze it just slid the background
11
+ // out from under the ink and the label bled past the fill. A pill that must
12
+ // fit a tight row does it the Tool.Chip way: the BOX holds and an inner
13
+ // min-w-0 span truncates. Enforced at runtime by layout/pill-holds-its-ink.
14
+ base: 'inline-flex h-(--era-h-xxs) min-w-(--era-h-xxs) shrink-0 items-center justify-center gap-(--era-gap) rounded-(--era-rd-xxs) px-(--era-pill-xxs) font-mono text-body leading-none whitespace-nowrap has-[svg]:gap-(--era-inset-xxs) era-text-trim',
9
15
  variants: {
10
16
  tone: {
11
17
  default: 'bg-hover text-fg',
@@ -6,7 +6,7 @@ export declare const badgeVariants: import("tailwind-variants").TVReturnType<{
6
6
  success: "bg-success text-success-fg";
7
7
  warning: "bg-warning text-warning-fg";
8
8
  };
9
- }, undefined, "inline-flex h-(--era-h-xxs) min-w-(--era-h-xxs) items-center justify-center gap-(--era-gap) rounded-(--era-rd-xxs) px-(--era-pill-xxs) font-mono text-body leading-none has-[svg]:gap-(--era-inset-xxs) era-text-trim", {
9
+ }, undefined, "inline-flex h-(--era-h-xxs) min-w-(--era-h-xxs) shrink-0 items-center justify-center gap-(--era-gap) rounded-(--era-rd-xxs) px-(--era-pill-xxs) font-mono text-body leading-none whitespace-nowrap has-[svg]:gap-(--era-inset-xxs) era-text-trim", {
10
10
  tone: {
11
11
  default: "bg-hover text-fg";
12
12
  accent: "bg-primary text-primary-fg";
@@ -19,7 +19,10 @@
19
19
  <div
20
20
  bind:this={ref}
21
21
  class={cn(
22
- 'flex gap-(--era-xs-inset-sm)',
22
+ // shrink-0: a segmented control is its buttons' width — Badge/Chip's law
23
+ // one tier up. Squeezed, its rounded fill slid out from under the end
24
+ // buttons (measured 4px at touch on the board).
25
+ 'flex shrink-0 gap-(--era-xs-inset-sm)',
23
26
  orientation === 'vertical'
24
27
  ? 'flex-col items-stretch p-(--era-xs-inset-sm) [&>*]:justify-start'
25
28
  : 'items-center',
@@ -11,7 +11,9 @@
11
11
  // select trigger): h-xxs = sp + 10, leaving the 3/5/7
12
12
  // concentric gap so it sits evenly inset, not cramped.
13
13
  export const chipVariants = tv({
14
- base: 'inline-flex items-center bg-hover font-mono text-body leading-none text-fg shadow-(--era-shadow) era-text-trim',
14
+ // shrink-0 + nowrap for the same reason Badge carries them: the pill is
15
+ // already exactly its text's size, so any squeeze puts ink outside the fill.
16
+ base: 'inline-flex shrink-0 items-center bg-hover font-mono text-body leading-none whitespace-nowrap text-fg shadow-(--era-shadow) era-text-trim',
15
17
  variants: {
16
18
  size: {
17
19
  sm: 'h-(--era-h-sm) min-w-(--era-h-sm) rounded-(--era-rd-sm) pl-(--era-pill-sm)',
@@ -7,7 +7,7 @@ export declare const chipVariants: import("tailwind-variants").TVReturnType<{
7
7
  true: "";
8
8
  false: "";
9
9
  };
10
- }, undefined, "inline-flex items-center bg-hover font-mono text-body leading-none text-fg shadow-(--era-shadow) era-text-trim", {
10
+ }, undefined, "inline-flex shrink-0 items-center bg-hover font-mono text-body leading-none whitespace-nowrap text-fg shadow-(--era-shadow) era-text-trim", {
11
11
  size: {
12
12
  sm: "h-(--era-h-sm) min-w-(--era-h-sm) rounded-(--era-rd-sm) pl-(--era-pill-sm)";
13
13
  xxs: "h-(--era-h-xxs) min-w-(--era-h-xxs) rounded-(--era-rd-xxs) pl-(--era-pill-xxs)";
@@ -207,7 +207,14 @@
207
207
  itself by the font's own metrics. The input and the ghost mirror BOTH inherit
208
208
  it, so they centre by the identical mechanism — no native-input-centering vs
209
209
  flex-centering divergence that reads as the ghost sitting a hair high. -->
210
- <div class="relative h-full min-w-0 flex-1 text-body leading-(--era-h-md) text-bright">
210
+ <!-- overflow-hidden: the transparent <input> clips its text natively, but the
211
+ MIRROR that actually paints it did not — so in a host narrower than the
212
+ prompt line (placeholder included), the text bled past the bar's rounded
213
+ end where any real input would clip. Safe with the line-height centring:
214
+ the box is the bar's full h-md, well above the ink (clipped-glyphs' law). -->
215
+ <div
216
+ class="relative h-full min-w-0 flex-1 overflow-hidden text-body leading-(--era-h-md) text-bright"
217
+ >
211
218
  <!-- Transparent input: it owns editing and the caret only, so the visible text
212
219
  can come entirely from the mirror below (see it for why). Everything but
213
220
  the see-through fill is inherited: font + line-height from the container,
@@ -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;
@@ -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';
@@ -21,7 +21,10 @@
21
21
  <span
22
22
  bind:this={ref}
23
23
  class={cn(
24
- 'inline-flex h-(--era-h-xxs) items-center overflow-hidden rounded-(--era-rd-xxs) bg-hover font-mono text-body era-text-trim leading-none shadow-(--era-shadow)',
24
+ // shrink-0 + nowrap like Badge/Chip: the overflow-hidden here is for the
25
+ // key cell's squared corners, not a licence to crush — squeezed, this pill
26
+ // clipped its value instead of bleeding it, which is quieter but no truer.
27
+ 'inline-flex h-(--era-h-xxs) shrink-0 items-center overflow-hidden rounded-(--era-rd-xxs) bg-hover font-mono text-body era-text-trim leading-none whitespace-nowrap shadow-(--era-shadow)',
25
28
  className
26
29
  )}
27
30
  {...restProps}
@@ -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.0",
3
+ "version": "4.13.0",
4
4
  "scripts": {
5
5
  "dev": "vite dev --host",
6
6
  "build": "vite build && npm run prepack",