@estiva-app/ui 0.24.0 → 0.25.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 (85) hide show
  1. package/dist/ErrorBoundary.d.ts +32 -0
  2. package/dist/ErrorBoundary.d.ts.map +1 -0
  3. package/dist/ListColumn.d.ts +42 -0
  4. package/dist/ListColumn.d.ts.map +1 -0
  5. package/dist/Menu.d.ts +7 -0
  6. package/dist/Menu.d.ts.map +1 -1
  7. package/dist/Select.d.ts +1 -0
  8. package/dist/Select.d.ts.map +1 -1
  9. package/dist/Toolbar.d.ts +18 -0
  10. package/dist/Toolbar.d.ts.map +1 -1
  11. package/dist/index.d.ts +4 -2
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +183 -108
  14. package/dist/index.js.map +4 -4
  15. package/package.json +1 -1
  16. package/registry.json +211 -4
  17. package/src/AppShell.mdx +8 -1
  18. package/src/AttachmentCard.mdx +3 -1
  19. package/src/Avatar.mdx +7 -2
  20. package/src/Banner.mdx +4 -5
  21. package/src/Breadcrumb.mdx +2 -0
  22. package/src/Button.mdx +3 -2
  23. package/src/Button.tsx +2 -2
  24. package/src/Card.mdx +5 -1
  25. package/src/Checkbox.mdx +1 -0
  26. package/src/ChipInput.mdx +1 -1
  27. package/src/CollapsibleSection.mdx +4 -4
  28. package/src/ConfirmDialog.mdx +2 -2
  29. package/src/ContainerHeader.mdx +12 -3
  30. package/src/DialogShell.mdx +1 -1
  31. package/src/Divider.mdx +5 -2
  32. package/src/EditableText.mdx +6 -1
  33. package/src/EmptyState.mdx +2 -2
  34. package/src/ErrorBoundary.mdx +66 -0
  35. package/src/ErrorBoundary.stories.tsx +64 -0
  36. package/src/ErrorBoundary.test.tsx +79 -0
  37. package/src/ErrorBoundary.tsx +54 -0
  38. package/src/Field.mdx +3 -4
  39. package/src/Form.mdx +1 -1
  40. package/src/IconButton.mdx +1 -1
  41. package/src/IdentityMenu.mdx +1 -1
  42. package/src/InlineChip.mdx +3 -0
  43. package/src/Kbd.mdx +2 -2
  44. package/src/Link.mdx +3 -1
  45. package/src/ListColumn.mdx +95 -0
  46. package/src/ListColumn.stories.tsx +160 -0
  47. package/src/ListColumn.test.tsx +63 -0
  48. package/src/ListColumn.tsx +86 -0
  49. package/src/Menu.mdx +7 -2
  50. package/src/Menu.stories.tsx +6 -7
  51. package/src/Menu.test.tsx +19 -1
  52. package/src/Menu.tsx +11 -1
  53. package/src/MenuItem.mdx +3 -0
  54. package/src/NavItem.mdx +4 -0
  55. package/src/Person.mdx +3 -0
  56. package/src/PersonTrigger.mdx +2 -1
  57. package/src/PersonTrigger.tsx +1 -1
  58. package/src/Popover.mdx +2 -0
  59. package/src/Rail.mdx +3 -0
  60. package/src/RailItem.mdx +4 -0
  61. package/src/RailItem.tsx +1 -1
  62. package/src/Reaction.mdx +1 -0
  63. package/src/Reaction.tsx +1 -1
  64. package/src/ScrollArea.mdx +3 -0
  65. package/src/SectionHeader.mdx +10 -2
  66. package/src/SectionHeader.tsx +1 -1
  67. package/src/SectionLabel.mdx +10 -4
  68. package/src/Select.mdx +3 -2
  69. package/src/Select.test.tsx +12 -0
  70. package/src/Select.tsx +4 -3
  71. package/src/Skeleton.mdx +5 -1
  72. package/src/Skeleton.tsx +1 -1
  73. package/src/TextInput.mdx +1 -0
  74. package/src/TextInput.tsx +1 -1
  75. package/src/Toolbar.mdx +9 -2
  76. package/src/Toolbar.stories.tsx +19 -2
  77. package/src/Toolbar.test.tsx +24 -1
  78. package/src/Toolbar.tsx +22 -0
  79. package/src/Tooltip.mdx +2 -1
  80. package/src/Tooltip.test.tsx +22 -0
  81. package/src/Tooltip.tsx +1 -1
  82. package/src/TopBar.mdx +8 -1
  83. package/src/heights.test.tsx +78 -0
  84. package/src/index.ts +4 -2
  85. package/src/registry/registry.test.ts +16 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@estiva-app/ui",
3
- "version": "0.24.0",
3
+ "version": "0.25.0",
4
4
  "description": "Estiva's design tokens (the contract) and a small set of primitives (a convenience) for every Estiva app.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/registry.json CHANGED
@@ -4,9 +4,9 @@
4
4
  "kind": "package",
5
5
  "repo": "estiva-ui",
6
6
  "package": "@estiva-app/ui",
7
- "packageVersion": "0.24.0",
8
- "exports": 82,
9
- "typeExports": 92,
7
+ "packageVersion": "0.25.0",
8
+ "exports": 86,
9
+ "typeExports": 95,
10
10
  "files": null
11
11
  },
12
12
  "storybook": {
@@ -397,7 +397,7 @@
397
397
  "kind": "component",
398
398
  "importPath": "@estiva-app/ui",
399
399
  "sourceFile": "src/Button.tsx",
400
- "purpose": "The push-button for an action: a verb, clicked once — \"Create\", \"Save\", \"Delete project\".",
400
+ "purpose": "The push-button for an action: a verb, clicked once — \"Create\", \"Save\", \"Delete\".",
401
401
  "purposeFrom": "page",
402
402
  "props": [
403
403
  {
@@ -1680,6 +1680,49 @@
1680
1680
  "docPage": null,
1681
1681
  "app": null
1682
1682
  },
1683
+ {
1684
+ "name": "ErrorBoundary",
1685
+ "repo": "estiva-ui",
1686
+ "kind": "component",
1687
+ "importPath": "@estiva-app/ui",
1688
+ "sourceFile": "src/ErrorBoundary.tsx",
1689
+ "purpose": "Keeps a crash inside what it holds: that part shows \"Something went wrong … Your data is safe — try again.\" with a Try again button, and the rest of the page keeps working.",
1690
+ "purposeFrom": "page",
1691
+ "props": [
1692
+ {
1693
+ "name": "children",
1694
+ "takes": "anything",
1695
+ "required": true,
1696
+ "note": null
1697
+ },
1698
+ {
1699
+ "name": "label",
1700
+ "takes": "text",
1701
+ "required": false,
1702
+ "note": "What it holds, said in the message and the console line: \"Something went wrong in the {label}.\""
1703
+ },
1704
+ {
1705
+ "name": "fallbackClassName",
1706
+ "takes": "text",
1707
+ "required": false,
1708
+ "note": "The room the message fills where no flex column holds it — the whole screen, for the app's own."
1709
+ },
1710
+ {
1711
+ "name": "frame",
1712
+ "takes": "a handler",
1713
+ "required": false,
1714
+ "note": "Draws the message inside a frame of yours, in the same place: `ListColumn` keeps its title this way."
1715
+ }
1716
+ ],
1717
+ "variants": [],
1718
+ "ownsBehaviours": [],
1719
+ "status": "stable",
1720
+ "migrationStage": null,
1721
+ "docsId": "feedback-errorboundary--docs",
1722
+ "storyId": "feedback-errorboundary--in-a-panel",
1723
+ "docPage": "src/ErrorBoundary.mdx",
1724
+ "app": null
1725
+ },
1683
1726
  {
1684
1727
  "name": "Field",
1685
1728
  "repo": "estiva-ui",
@@ -2395,6 +2438,67 @@
2395
2438
  "docPage": "src/Link.mdx",
2396
2439
  "app": null
2397
2440
  },
2441
+ {
2442
+ "name": "ListColumn",
2443
+ "repo": "estiva-ui",
2444
+ "kind": "component",
2445
+ "importPath": "@estiva-app/ui",
2446
+ "sourceFile": "src/ListColumn.tsx",
2447
+ "purpose": "The list column of a page: 290px wide with a line on its right, its name in a `ContainerHeader`, and the list under it, which scrolls.",
2448
+ "purposeFrom": "page",
2449
+ "props": [
2450
+ {
2451
+ "name": "title",
2452
+ "takes": "anything",
2453
+ "required": true,
2454
+ "note": "The column's name, in its header. A node for a title that is more than words."
2455
+ },
2456
+ {
2457
+ "name": "chevron",
2458
+ "takes": "true/false",
2459
+ "required": false,
2460
+ "note": "A chevron after the title, as `ContainerHeader` draws it."
2461
+ },
2462
+ {
2463
+ "name": "actions",
2464
+ "takes": "anything",
2465
+ "required": false,
2466
+ "note": "The column's own actions, at the header's right — a `Toolbar` of `ToolbarButton`s, or one `IconButton`."
2467
+ },
2468
+ {
2469
+ "name": "above",
2470
+ "takes": "anything",
2471
+ "required": false,
2472
+ "note": "A row between the header and the list that stays put while the list scrolls — a field that adds to the list."
2473
+ },
2474
+ {
2475
+ "name": "collapsed",
2476
+ "takes": "true/false",
2477
+ "required": false,
2478
+ "note": "Closes the column with the rail: it narrows to nothing and fades."
2479
+ },
2480
+ {
2481
+ "name": "children",
2482
+ "takes": "anything",
2483
+ "required": true,
2484
+ "note": "The rows, 2px apart. Groups are headed by a `CollapsibleSection` or a `SectionHeader`."
2485
+ },
2486
+ {
2487
+ "name": "className",
2488
+ "takes": "text",
2489
+ "required": false,
2490
+ "note": "Placement only."
2491
+ }
2492
+ ],
2493
+ "variants": [],
2494
+ "ownsBehaviours": [],
2495
+ "status": "stable",
2496
+ "migrationStage": null,
2497
+ "docsId": "layout-listcolumn--docs",
2498
+ "storyId": "layout-listcolumn--default",
2499
+ "docPage": "src/ListColumn.mdx",
2500
+ "app": null
2501
+ },
2398
2502
  {
2399
2503
  "name": "Menu",
2400
2504
  "repo": "estiva-ui",
@@ -2691,6 +2795,24 @@
2691
2795
  "docPage": null,
2692
2796
  "app": null
2693
2797
  },
2798
+ {
2799
+ "name": "MenuSeparator",
2800
+ "repo": "estiva-ui",
2801
+ "kind": "component",
2802
+ "importPath": "@estiva-app/ui",
2803
+ "sourceFile": "src/Menu.tsx",
2804
+ "purpose": "The line between two groups of a menu, with its own 4px above and below (UIG-14, Katerina 19 September).",
2805
+ "purposeFrom": "comment",
2806
+ "props": [],
2807
+ "variants": [],
2808
+ "ownsBehaviours": [],
2809
+ "status": "stable",
2810
+ "migrationStage": null,
2811
+ "docsId": "overlays-menu--docs",
2812
+ "storyId": "overlays-menu--items",
2813
+ "docPage": null,
2814
+ "app": null
2815
+ },
2694
2816
  {
2695
2817
  "name": "MenuSub",
2696
2818
  "repo": "estiva-ui",
@@ -4383,6 +4505,91 @@
4383
4505
  "docPage": null,
4384
4506
  "app": null
4385
4507
  },
4508
+ {
4509
+ "name": "ToolbarLink",
4510
+ "repo": "estiva-ui",
4511
+ "kind": "component",
4512
+ "importPath": "@estiva-app/ui",
4513
+ "sourceFile": "src/Toolbar.tsx",
4514
+ "purpose": "An `IconButton` that is a link, and an item of a `Toolbar`: the arrow keys reach it like the buttons beside it.",
4515
+ "purposeFrom": "comment",
4516
+ "props": [
4517
+ {
4518
+ "name": "href",
4519
+ "takes": "text",
4520
+ "required": true,
4521
+ "note": null
4522
+ },
4523
+ {
4524
+ "name": "ref",
4525
+ "takes": "Ref<HTMLAnchorElement>",
4526
+ "required": false,
4527
+ "note": null
4528
+ },
4529
+ {
4530
+ "name": "variant",
4531
+ "takes": "muted | outlined | primary | current | resolve",
4532
+ "required": false,
4533
+ "note": "`current` takes the colour of what it sits in — the ✕ on a `Banner` wears the banner's tone."
4534
+ },
4535
+ {
4536
+ "name": "glow",
4537
+ "takes": "true/false",
4538
+ "required": false,
4539
+ "note": "A glow in Signal — the send arrow while there is something to send."
4540
+ },
4541
+ {
4542
+ "name": "tooltip",
4543
+ "takes": "text",
4544
+ "required": false,
4545
+ "note": null
4546
+ },
4547
+ {
4548
+ "name": "tooltipShortcut",
4549
+ "takes": "text",
4550
+ "required": false,
4551
+ "note": "A key hint drawn as the `Kbd` chip inside the tooltip — for a button whose only other affordance is a keyboard shortcut."
4552
+ },
4553
+ {
4554
+ "name": "tooltipPlacement",
4555
+ "takes": "top | bottom",
4556
+ "required": false,
4557
+ "note": null
4558
+ },
4559
+ {
4560
+ "name": "children",
4561
+ "takes": "anything",
4562
+ "required": true,
4563
+ "note": "The icon: 16px, stroke 1.5."
4564
+ }
4565
+ ],
4566
+ "variants": [
4567
+ {
4568
+ "prop": "variant",
4569
+ "values": [
4570
+ "muted",
4571
+ "outlined",
4572
+ "primary",
4573
+ "current",
4574
+ "resolve"
4575
+ ]
4576
+ },
4577
+ {
4578
+ "prop": "tooltipPlacement",
4579
+ "values": [
4580
+ "top",
4581
+ "bottom"
4582
+ ]
4583
+ }
4584
+ ],
4585
+ "ownsBehaviours": [],
4586
+ "status": "stable",
4587
+ "migrationStage": null,
4588
+ "docsId": "primitives-toolbar--docs",
4589
+ "storyId": "primitives-toolbar--default",
4590
+ "docPage": null,
4591
+ "app": null
4592
+ },
4386
4593
  {
4387
4594
  "name": "ToolbarSeparator",
4388
4595
  "repo": "estiva-ui",
package/src/AppShell.mdx CHANGED
@@ -52,7 +52,6 @@ flex column. A page is a flex child of `main`, and says one of two things:
52
52
  Never `h-full`: the frame's content box is *at least* the viewport's
53
53
  height and grows with a tall page — which is what lets the bar know the
54
54
  page changed — so a percentage height has nothing to resolve against.
55
- Measured on all four of Ship's pages (2026-09-09).
56
55
 
57
56
  ```tsx
58
57
  import { AppShell, Banner, IdentityMenu, Rail, RailItem } from '@estiva-app/ui'
@@ -78,6 +77,14 @@ import { AppShell, Banner, IdentityMenu, Rail, RailItem } from '@estiva-app/ui'
78
77
  - In the Signal theme the floating frame draws the control-room dot grid on
79
78
  its ground, behind the card. Nothing to add: the preset makes it.
80
79
 
80
+ - In the floating manner the bar takes the top 52px; the card sits 16px from
81
+ the right and the bottom, with 16px corners, beside the 64px rail.
82
+ - In the floating manner the card owns `overflow-hidden`, so anything in it
83
+ that can grow sits in a **ScrollArea** — a list column most of all, which
84
+ **ListColumn** draws with its own. Without one it is cut off at the fold.
85
+ - **Planned:** `SplitLayout`, resizable panels, in `@estiva-app/ui/layout`
86
+ (migration stage 8). It may take the list and side columns' widths.
87
+
81
88
  ## What it owns
82
89
 
83
90
  It does these by itself. Write none of them around it.
@@ -53,10 +53,12 @@ import { AttachmentCard } from '@estiva-app/ui'
53
53
  - The name truncates. When it is cut off, the full name is on hover; a name
54
54
  that fits, and a size, show nothing extra. `noteHint` is always on hover, since
55
55
  it says more than the line.
56
- - The remove control is Peek's 20px round badge on the card's corner, a button
56
+ - The remove control is a 20px round badge on the card's corner, a button
57
57
  on Base UI's `Button` like InputChip's ✕ — not an `IconButton`, the 24px
58
58
  square that fills on hover.
59
59
 
60
+ - **Planned:** a Lightbox that opens a picture full screen, in migration stage 7.
61
+
60
62
  ## What it owns
61
63
 
62
64
  It does these by itself. Write none of them around it.
package/src/Avatar.mdx CHANGED
@@ -39,7 +39,7 @@ import { Avatar } from '@estiva-app/ui'
39
39
  two words, skipping words that open with a symbol. An unnamed person is
40
40
  the silhouette, not a `?` — a question mark reads as an error badge.
41
41
  - The initials are centred on the capitals themselves — cap height to
42
- baseline — not on the line of text around them (D27). A single face can
42
+ baseline — not on the line of text around them. A single face can
43
43
  still sit up to half a pixel off: the screen rounds text to its pixel grid
44
44
  wherever the tile lands.
45
45
  - The colour behind initials is per person, chosen from the name, the same
@@ -54,7 +54,7 @@ import { Avatar } from '@estiva-app/ui'
54
54
  **A face is silent by default**, and that is the important half: almost every
55
55
  face in the suite sits beside the name it belongs to — in a `Person`, in a
56
56
  `MenuItem`'s `leading`, in a row — and a picture that spoke there would say the
57
- name twice. It used to. Measured 2026-09-08, before this was true: a `Person`
57
+ name twice. It used to: a `Person`
58
58
  inside a button announced **"AD Ana Duarte"** (the initials read as text), and
59
59
  with a picture **"Ana Duarte Ana Duarte"**.
60
60
 
@@ -68,6 +68,11 @@ a stack of members — and the tile becomes one image with one name.
68
68
 
69
69
  <Canvas of={AvatarStories.Palette} />
70
70
 
71
+ - **Planned:** a Byline part — face · name · time — in migration stage 7.
72
+
73
+ - `initialsFor(name)` and `hueFor(name)` give the same initials and colour where
74
+ the part itself cannot be drawn.
75
+
71
76
  ## What it owns
72
77
 
73
78
  It does these by itself. Write none of them around it.
package/src/Banner.mdx CHANGED
@@ -27,7 +27,7 @@ line, one of four tones, gone when there is nothing to say.
27
27
  - Something that floats over the page and must **not** fade → still
28
28
  **Toast**, with `durationMs: 0` and an action. A banner is the strip
29
29
  under the header at the app's full width; a floating pill is not one, even
30
- a permanent one (Katerina, D21).
30
+ a permanent one.
31
31
  - A standing property of one thing → **Chip** on that thing.
32
32
  - More than one action, or a second line of text — that is a different
33
33
  component (not in the package yet).
@@ -45,9 +45,8 @@ import { Banner } from '@estiva-app/ui'
45
45
  - One line of text, `body-2`. If it needs a **second line**, it has outgrown
46
46
  this component.
47
47
  - `icon` puts a 16px icon before the line and `action` one small muted Button
48
- at the end of it, both in the tone (Katerina, 2026-09-18: Peek's strip above
49
- the composer became a Banner, `info`). With either, the banner is one line
50
- and the text truncates. One action at most.
48
+ at the end of it, both in the tone. With either, the banner is one line and
49
+ the text truncates. One action at most.
51
50
 
52
51
  <Canvas of={BannerStories.WithIconAndAction} />
53
52
 
@@ -57,7 +56,7 @@ import { Banner } from '@estiva-app/ui'
57
56
  </Banner>
58
57
  ```
59
58
 
60
- - `onDismiss` adds an ✕ at the right-hand end (Katerina, D21). Use it where
59
+ - `onDismiss` adds an ✕ at the right-hand end. Use it where
61
60
  the reader decides the notice is done with, rather than the app. The row
62
61
  is 40px rather than 36px with it, because the button is taller than the
63
62
  line of text; without it, nothing about the strip changes.
@@ -56,6 +56,8 @@ import { Breadcrumb } from '@estiva-app/ui'
56
56
  `min-w-0` container so it can truncate at all.
57
57
  - The separators are `aria-hidden` — the trail reads as its places.
58
58
 
59
+ - 6px between each place and its separator.
60
+
59
61
  ## What it owns
60
62
 
61
63
  It does these by itself. Write none of them around it.
package/src/Button.mdx CHANGED
@@ -6,7 +6,7 @@ import * as ButtonStories from './Button.stories'
6
6
  # Button
7
7
 
8
8
  The push-button for an action: a verb, clicked once — "Create", "Save",
9
- "Delete project". Four variants on one geometry: 32px default / 24px small,
9
+ "Delete". Four variants on one geometry: 32px default / 24px small,
10
10
  6px radius, medium weight.
11
11
 
12
12
  <Canvas of={ButtonStories.AllVariants} />
@@ -67,7 +67,7 @@ import { IconPlus } from '@tabler/icons-react'
67
67
  has the same look.
68
68
  - **Placed from outside, never restyled.** `className` takes space, size,
69
69
  flex and grid, and position; a colour, a text size, a border, a corner or a
70
- shadow is refused by the lint (`estiva/no-restyled-part`, UIG-9).
70
+ shadow is refused by the lint (`estiva/no-restyled-part`).
71
71
 
72
72
  ## What it owns
73
73
 
@@ -78,6 +78,7 @@ It does these by itself. Write none of them around it.
78
78
  | Says what it is to assistive technology: a button | `role="button"` | ✓ |
79
79
  | Is reachable with Tab, also when it is disabled with a reason | a `tabIndex` | ✓ |
80
80
  | Presses on Enter and Space, and does nothing while disabled | a key handler | |
81
+ | Keeps its 32px (24px small) in a scrolling column | `shrink-0` | |
81
82
 
82
83
  ## Keys
83
84
 
package/src/Button.tsx CHANGED
@@ -86,8 +86,8 @@ export function Button({
86
86
  // this package's own story frames, each of which asks for the top.
87
87
  // A control does not get to decide where its caller puts it.
88
88
  'inline-flex items-center justify-center gap-1 rounded-md transition-colors font-sans font-medium',
89
- size === 'default' && 'h-8 text-btn-default',
90
- size === 'small' && 'h-6 text-btn-small',
89
+ size === 'default' && 'h-8 min-h-8 text-btn-default',
90
+ size === 'small' && 'h-6 min-h-6 text-btn-small',
91
91
  // Extra right padding beside a leading icon, for optical balance.
92
92
  size === 'default' && (hasLeadingIcon ? 'pl-2 pr-3' : 'px-2'),
93
93
  size === 'small' && (hasLeadingIcon ? 'pl-1.5 pr-2' : 'px-1.5'),
package/src/Card.mdx CHANGED
@@ -12,6 +12,8 @@ hairline. What goes inside, and the space around it, is yours.
12
12
 
13
13
  ## When
14
14
 
15
+ - **It draws no padding and no layout**: both are yours.
16
+
15
17
  A box is a card when it stands for **one thing you could name**, and there are
16
18
  many of it or you treat the whole box as that thing — open it, point at it,
17
19
  move it.
@@ -73,9 +75,11 @@ import { Card } from '@estiva-app/ui'
73
75
  picture that runs edge to edge. Without it their square corners show.
74
76
  - **Placed from outside, never restyled.** `className` takes space, size,
75
77
  flex and grid, and position; a colour, a text size, a border, a corner or a
76
- shadow is refused by the lint (`estiva/no-restyled-part`, UIG-9). What is inside the card is yours: its padding and its words go on
78
+ shadow is refused by the lint (`estiva/no-restyled-part`). What is inside the card is yours: its padding and its words go on
77
79
  your own element inside.
78
80
 
81
+ - **Planned:** the inside layout of a linked object's card, in migration stage 7.
82
+
79
83
  ## What it owns
80
84
 
81
85
  It does these by itself. Write none of them around it.
package/src/Checkbox.mdx CHANGED
@@ -88,6 +88,7 @@ It does these by itself. Write none of them around it.
88
88
  | Says what it is to assistive technology: a checkbox, ticked or not | `role="checkbox"` or `aria-checked` | ✓ |
89
89
  | Toggles on Space and on a click; Enter is left to the form | a click or key handler | |
90
90
  | Is one Tab stop, in a `row` too | a `tabIndex` on the row | |
91
+ | Keeps a `row`'s 40px in a scrolling column | `shrink-0` | |
91
92
 
92
93
  ## Keys
93
94
 
package/src/ChipInput.mdx CHANGED
@@ -66,7 +66,7 @@ import { ChipInput } from '@estiva-app/ui'
66
66
  ```
67
67
 
68
68
  - **`InputChip` on its own** names its ✕ `Remove <label>`. Where the ✕ does
69
- something else, say what with `removeLabel` (a scope chip: "Leave Ship").
69
+ something else, say what with `removeLabel` (a chip for a group you belong to: "Leave Design").
70
70
  - To cap a long label, give the chip a `max-w-*` and `truncate`: the label is
71
71
  cut and the ✕ keeps its size. `truncate` is off unless asked, because
72
72
  cutting clips a letter's soft edge by up to 4px at 1x even when the label
@@ -14,11 +14,11 @@ toggle, and the rows under it, sliding shut and open.
14
14
  ## When
15
15
 
16
16
  - A group of rows in a sidebar or a panel that the reader may want out of
17
- the way — projects, folders, starred items.
17
+ the way — starred items, recent places.
18
18
  - `defaultOpen={false}` for a group that starts folded.
19
19
  - `storageKey` when the section should stay how the reader left it across
20
20
  reloads: this browser remembers, under that key. The app prefixes the
21
- key (`ship.sidebar.projects`).
21
+ key (`app.sidebar.starred`).
22
22
  - `open` + `onOpenChange` when the app owns the state.
23
23
  - `actions` for add, sort, filter beside the title — SectionHeader's,
24
24
  revealed on hover or focus.
@@ -31,7 +31,7 @@ composition lives:
31
31
  ## When not
32
32
 
33
33
  - A heading over rows that never fold → **SectionHeader** without
34
- `chevron`, or **SectionLabel** in a 32px row.
34
+ `chevron`. Never a row drawn by hand.
35
35
  - Several panels where opening one closes the others → not here; that is
36
36
  an accordion, and no app has asked for one.
37
37
  - Content that appears on demand from a control elsewhere → **Popover**.
@@ -41,7 +41,7 @@ composition lives:
41
41
  ```tsx
42
42
  import { CollapsibleSection, NavItem } from '@estiva-app/ui'
43
43
 
44
- <CollapsibleSection title="Projects" storageKey="app.sidebar.projects" className="mt-2 shrink-0" contentClassName="gap-px">
44
+ <CollapsibleSection title="Starred" storageKey="app.sidebar.starred" className="mt-2 shrink-0" contentClassName="gap-px">
45
45
  <NavItem href="/p/1" label="Item one" />
46
46
  <NavItem href="/p/2" label="Item two" />
47
47
  </CollapsibleSection>
@@ -45,7 +45,7 @@ import { ConfirmDialog } from '@estiva-app/ui'
45
45
  dialog open — your surface has said why; a resolved `void`/`true`
46
46
  closes it.
47
47
  - **Escape cancels; a press on the backdrop does not.** This is Base UI's
48
- `AlertDialog`, through the shell's `alert` (Katerina, D20, 2026-09-07): a
48
+ `AlertDialog`, through the shell's `alert`: a
49
49
  destructive question is answered rather than clicked away. The backdrop
50
50
  used to cancel, and that is the one behaviour stage 3 deliberately changed.
51
51
  - It announces itself as an alert (`role="alertdialog"`), so a screen reader
@@ -71,7 +71,7 @@ Different from `DialogShell`'s in one row, and it is the row that matters.
71
71
  | Input | What happens |
72
72
  |---|---|
73
73
  | Escape | cancels |
74
- | backdrop click | **nothing** — the question has to be answered (D20) |
74
+ | backdrop click | **nothing** — the question has to be answered |
75
75
  | close button | cancels |
76
76
  | Tab / Shift+Tab | cycles the controls inside the card, and cannot leave it |
77
77
  | on close | focus returns to whatever opened it |
@@ -7,8 +7,7 @@ import * as ContainerHeaderStories from './ContainerHeader.stories'
7
7
 
8
8
  The bar across the top of a column — a list, a thread, a side panel: its
9
9
  title, and the buttons that act on the whole column at the right edge. 48px,
10
- with a hairline under it. Peek's ContainerHeader, moved into the package as it
11
- looks (Katerina, 2026-09-18).
10
+ with a hairline under it.
12
11
 
13
12
  <Canvas of={ContainerHeaderStories.WithActions} />
14
13
 
@@ -26,6 +25,11 @@ looks (Katerina, 2026-09-18).
26
25
  - The top of the whole app → **TopBar**.
27
26
  - A dialog's title → **DialogShell** draws its own.
28
27
 
28
+ - A row you draw yourself at the top of a column — your own padding and a line
29
+ under it → this part. Its numbers are every column's.
30
+ - A whole list column, header and scrolling list → **ListColumn**, which draws
31
+ this bar at its top.
32
+
29
33
  ## How
30
34
 
31
35
  ```tsx
@@ -53,7 +57,12 @@ import { ContainerHeader, IconButton } from '@estiva-app/ui'
53
57
 
54
58
  ## What it owns
55
59
 
56
- Nothing. It only draws. Clicks and keys are yours.
60
+ It does these by itself. Write none of them around it.
61
+
62
+ | It does | So you never write | Refused when written by hand |
63
+ |---|---|---|
64
+ | Draws the column's bar: 48px tall, the title 20px in at 14px medium, the actions 16px from the right, a hairline under it | a row of your own with padding and a border | |
65
+ | Keeps its 48px in a scrolling column | `shrink-0` | |
57
66
 
58
67
  ## Props
59
68
 
@@ -71,7 +71,7 @@ import { DialogShell, Button } from '@estiva-app/ui'
71
71
  worth setting where typing is the point — the trap decides where focus
72
72
  *cannot* go, not where it starts.
73
73
  - `alert` for a question that has to be answered: a press on the backdrop
74
- stops closing it (Katerina, D20). Escape and the ✕ still do.
74
+ stops closing it. Escape and the ✕ still do.
75
75
  `ConfirmDialog` sets it; a form or a roster should not.
76
76
 
77
77
  ## What it owns
package/src/Divider.mdx CHANGED
@@ -14,8 +14,6 @@ orientation for assistive tech.
14
14
 
15
15
  ## When
16
16
 
17
- - Between groups in a **Menu** (`className="my-1"` gives it its breathing
18
- room there).
19
17
  - Between sections of a panel, a rail or a card.
20
18
  - Vertical: between clusters in a toolbar or a dense row.
21
19
 
@@ -30,6 +28,8 @@ orientation for assistive tech.
30
28
 
31
29
  ## When not
32
30
 
31
+ - Between groups in a menu → **MenuSeparator**: this line with its own 4px
32
+ above and below.
33
33
  - Between every row of a list — spacing does that; a hairline per row is
34
34
  noise.
35
35
  - To title what follows → **SectionLabel** or **SectionHeader**; a heading
@@ -49,6 +49,8 @@ import { Divider } from '@estiva-app/ui'
49
49
  - **Inside a `Menu` or a `Popover` it has no inset**: the hairline runs the
50
50
  width of the rows. The panel tells it so; nothing is passed in.
51
51
 
52
+ - The line is 1px; the room around it is yours.
53
+
52
54
  ## What it owns
53
55
 
54
56
  It does these by itself. Write none of them around it.
@@ -56,6 +58,7 @@ It does these by itself. Write none of them around it.
56
58
  | It does | So you never write | Refused when written by hand |
57
59
  |---|---|---|
58
60
  | Says what it is to assistive technology: a separator | `role="separator"` | ✓ |
61
+ | Keeps its 1px line in a scrolling column | `shrink-0` | |
59
62
 
60
63
  ## Props
61
64
 
@@ -13,6 +13,9 @@ edit is never silently lost.
13
13
 
14
14
  ## When
15
15
 
16
+ - **It has no size of its own.** Give it the size of the text it stands in for —
17
+ a pane title's `text-body-2-strong` — on your element around it; the text and
18
+ the field both take it.
16
19
  - A title or description edited where it is shown — no dialog, no separate
17
20
  form.
18
21
  - A reader who cannot write gets `readOnly` — the value without the edit
@@ -55,7 +58,9 @@ import { EditableText } from '@estiva-app/ui'
55
58
  - **Its text size is where it sits**, like `Person`'s: put `text-h2` on your own
56
59
  element around it, and the text and the field both take it (a field inherits
57
60
  the page's font). `className` places it — `-mx-2` to line its text up with
58
- a header's inset — and a size passed there is refused by the lint (UIG-9).
61
+ a header's inset — and a size passed there is refused by the lint.
62
+
63
+ - **Planned:** rich text edited where it is shown (UIG-30).
59
64
 
60
65
  ## What it owns
61
66
 
@@ -57,14 +57,14 @@ import { EmptyState } from '@estiva-app/ui'
57
57
 
58
58
  **When there is one thing to do about it, give it as `action`.** It is drawn
59
59
  16px under the line as the package's Button, outlined — the same look wherever
60
- it appears (Katerina, 2026-09-18). Never put a Button beside an EmptyState in a
60
+ it appears. Never put a Button beside an EmptyState in a
61
61
  box of your own: the box places it, and no two boxes place it alike. A `page`
62
62
  only; a section's line takes no action.
63
63
 
64
64
  <Canvas of={EmptyStateStories.WithAction} />
65
65
 
66
66
  ```tsx
67
- <EmptyState icon={<IconMessagePlus size={16} stroke={1.5} />} message="No topics yet." action={{ label: 'New topic', onClick: openNewTopic }} />
67
+ <EmptyState icon={<IconMessagePlus size={16} stroke={1.5} />} message="Nothing here yet." action={{ label: 'Create', onClick: create }} />
68
68
  ```
69
69
 
70
70
  **Put a section's empty state inside the box its rows live in, and give it no