@estiva-app/ui 0.24.0 → 0.24.1

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 (74) hide show
  1. package/dist/ListColumn.d.ts +41 -0
  2. package/dist/ListColumn.d.ts.map +1 -0
  3. package/dist/Select.d.ts +1 -0
  4. package/dist/Select.d.ts.map +1 -1
  5. package/dist/Toolbar.d.ts +18 -0
  6. package/dist/Toolbar.d.ts.map +1 -1
  7. package/dist/index.d.ts +2 -1
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +65 -38
  10. package/dist/index.js.map +4 -4
  11. package/package.json +1 -1
  12. package/registry.json +164 -4
  13. package/src/AppShell.mdx +8 -1
  14. package/src/AttachmentCard.mdx +3 -1
  15. package/src/Avatar.mdx +7 -2
  16. package/src/Banner.mdx +4 -5
  17. package/src/Breadcrumb.mdx +2 -0
  18. package/src/Button.mdx +3 -2
  19. package/src/Button.tsx +2 -2
  20. package/src/Card.mdx +5 -1
  21. package/src/Checkbox.mdx +1 -0
  22. package/src/ChipInput.mdx +1 -1
  23. package/src/CollapsibleSection.mdx +3 -3
  24. package/src/ConfirmDialog.mdx +2 -2
  25. package/src/ContainerHeader.mdx +12 -3
  26. package/src/DialogShell.mdx +1 -1
  27. package/src/Divider.mdx +3 -0
  28. package/src/EditableText.mdx +6 -1
  29. package/src/EmptyState.mdx +2 -2
  30. package/src/Field.mdx +3 -4
  31. package/src/Form.mdx +1 -1
  32. package/src/IconButton.mdx +1 -1
  33. package/src/IdentityMenu.mdx +1 -1
  34. package/src/InlineChip.mdx +3 -0
  35. package/src/Kbd.mdx +2 -2
  36. package/src/Link.mdx +3 -1
  37. package/src/ListColumn.mdx +87 -0
  38. package/src/ListColumn.stories.tsx +136 -0
  39. package/src/ListColumn.test.tsx +50 -0
  40. package/src/ListColumn.tsx +63 -0
  41. package/src/Menu.mdx +2 -0
  42. package/src/MenuItem.mdx +3 -0
  43. package/src/NavItem.mdx +4 -0
  44. package/src/Person.mdx +3 -0
  45. package/src/PersonTrigger.mdx +2 -1
  46. package/src/PersonTrigger.tsx +1 -1
  47. package/src/Popover.mdx +2 -0
  48. package/src/Rail.mdx +3 -0
  49. package/src/RailItem.mdx +4 -0
  50. package/src/RailItem.tsx +1 -1
  51. package/src/Reaction.mdx +1 -0
  52. package/src/Reaction.tsx +1 -1
  53. package/src/ScrollArea.mdx +3 -0
  54. package/src/SectionHeader.mdx +5 -1
  55. package/src/SectionHeader.tsx +1 -1
  56. package/src/SectionLabel.mdx +6 -2
  57. package/src/Select.mdx +3 -2
  58. package/src/Select.test.tsx +12 -0
  59. package/src/Select.tsx +4 -3
  60. package/src/Skeleton.mdx +5 -1
  61. package/src/Skeleton.tsx +1 -1
  62. package/src/TextInput.mdx +1 -0
  63. package/src/TextInput.tsx +1 -1
  64. package/src/Toolbar.mdx +9 -2
  65. package/src/Toolbar.stories.tsx +19 -2
  66. package/src/Toolbar.test.tsx +24 -1
  67. package/src/Toolbar.tsx +22 -0
  68. package/src/Tooltip.mdx +2 -1
  69. package/src/Tooltip.test.tsx +22 -0
  70. package/src/Tooltip.tsx +1 -1
  71. package/src/TopBar.mdx +8 -1
  72. package/src/heights.test.tsx +78 -0
  73. package/src/index.ts +2 -1
  74. 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.24.1",
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.24.1",
8
+ "exports": 84,
9
+ "typeExports": 94,
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
  {
@@ -2395,6 +2395,81 @@
2395
2395
  "docPage": "src/Link.mdx",
2396
2396
  "app": null
2397
2397
  },
2398
+ {
2399
+ "name": "ListColumn",
2400
+ "repo": "estiva-ui",
2401
+ "kind": "component",
2402
+ "importPath": "@estiva-app/ui",
2403
+ "sourceFile": "src/ListColumn.tsx",
2404
+ "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.",
2405
+ "purposeFrom": "page",
2406
+ "props": [
2407
+ {
2408
+ "name": "title",
2409
+ "takes": "anything",
2410
+ "required": true,
2411
+ "note": "The column's name, in its header. A node for a title that is more than words."
2412
+ },
2413
+ {
2414
+ "name": "chevron",
2415
+ "takes": "true/false",
2416
+ "required": false,
2417
+ "note": "A chevron after the title, as `ContainerHeader` draws it."
2418
+ },
2419
+ {
2420
+ "name": "actions",
2421
+ "takes": "anything",
2422
+ "required": false,
2423
+ "note": "The column's own actions, at the header's right — a `Toolbar` of `ToolbarButton`s, or one `IconButton`."
2424
+ },
2425
+ {
2426
+ "name": "above",
2427
+ "takes": "anything",
2428
+ "required": false,
2429
+ "note": "A row between the header and the list that stays put while the list scrolls — a field that adds to the list."
2430
+ },
2431
+ {
2432
+ "name": "spacing",
2433
+ "takes": "rows | sections",
2434
+ "required": false,
2435
+ "note": "The room between rows. `rows` (2px) for a list of one kind of row; `sections` (4px) for groups with labels and dividers of their own."
2436
+ },
2437
+ {
2438
+ "name": "collapsed",
2439
+ "takes": "true/false",
2440
+ "required": false,
2441
+ "note": "Closes the column with the rail: it narrows to nothing and fades."
2442
+ },
2443
+ {
2444
+ "name": "children",
2445
+ "takes": "anything",
2446
+ "required": true,
2447
+ "note": "The rows."
2448
+ },
2449
+ {
2450
+ "name": "className",
2451
+ "takes": "text",
2452
+ "required": false,
2453
+ "note": "Placement only."
2454
+ }
2455
+ ],
2456
+ "variants": [
2457
+ {
2458
+ "prop": "spacing",
2459
+ "values": [
2460
+ "rows",
2461
+ "sections"
2462
+ ]
2463
+ }
2464
+ ],
2465
+ "ownsBehaviours": [],
2466
+ "status": "stable",
2467
+ "migrationStage": null,
2468
+ "docsId": "layout-listcolumn--docs",
2469
+ "storyId": "layout-listcolumn--default",
2470
+ "docPage": "src/ListColumn.mdx",
2471
+ "app": null
2472
+ },
2398
2473
  {
2399
2474
  "name": "Menu",
2400
2475
  "repo": "estiva-ui",
@@ -4383,6 +4458,91 @@
4383
4458
  "docPage": null,
4384
4459
  "app": null
4385
4460
  },
4461
+ {
4462
+ "name": "ToolbarLink",
4463
+ "repo": "estiva-ui",
4464
+ "kind": "component",
4465
+ "importPath": "@estiva-app/ui",
4466
+ "sourceFile": "src/Toolbar.tsx",
4467
+ "purpose": "An `IconButton` that is a link, and an item of a `Toolbar`: the arrow keys reach it like the buttons beside it.",
4468
+ "purposeFrom": "comment",
4469
+ "props": [
4470
+ {
4471
+ "name": "href",
4472
+ "takes": "text",
4473
+ "required": true,
4474
+ "note": null
4475
+ },
4476
+ {
4477
+ "name": "ref",
4478
+ "takes": "Ref<HTMLAnchorElement>",
4479
+ "required": false,
4480
+ "note": null
4481
+ },
4482
+ {
4483
+ "name": "variant",
4484
+ "takes": "muted | outlined | primary | current | resolve",
4485
+ "required": false,
4486
+ "note": "`current` takes the colour of what it sits in — the ✕ on a `Banner` wears the banner's tone."
4487
+ },
4488
+ {
4489
+ "name": "glow",
4490
+ "takes": "true/false",
4491
+ "required": false,
4492
+ "note": "A glow in Signal — the send arrow while there is something to send."
4493
+ },
4494
+ {
4495
+ "name": "tooltip",
4496
+ "takes": "text",
4497
+ "required": false,
4498
+ "note": null
4499
+ },
4500
+ {
4501
+ "name": "tooltipShortcut",
4502
+ "takes": "text",
4503
+ "required": false,
4504
+ "note": "A key hint drawn as the `Kbd` chip inside the tooltip — for a button whose only other affordance is a keyboard shortcut."
4505
+ },
4506
+ {
4507
+ "name": "tooltipPlacement",
4508
+ "takes": "top | bottom",
4509
+ "required": false,
4510
+ "note": null
4511
+ },
4512
+ {
4513
+ "name": "children",
4514
+ "takes": "anything",
4515
+ "required": true,
4516
+ "note": "The icon: 16px, stroke 1.5."
4517
+ }
4518
+ ],
4519
+ "variants": [
4520
+ {
4521
+ "prop": "variant",
4522
+ "values": [
4523
+ "muted",
4524
+ "outlined",
4525
+ "primary",
4526
+ "current",
4527
+ "resolve"
4528
+ ]
4529
+ },
4530
+ {
4531
+ "prop": "tooltipPlacement",
4532
+ "values": [
4533
+ "top",
4534
+ "bottom"
4535
+ ]
4536
+ }
4537
+ ],
4538
+ "ownsBehaviours": [],
4539
+ "status": "stable",
4540
+ "migrationStage": null,
4541
+ "docsId": "primitives-toolbar--docs",
4542
+ "storyId": "primitives-toolbar--default",
4543
+ "docPage": null,
4544
+ "app": null
4545
+ },
4386
4546
  {
4387
4547
  "name": "ToolbarSeparator",
4388
4548
  "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.
@@ -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
@@ -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 (`my-1` in a Menu).
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
package/src/Field.mdx CHANGED
@@ -43,14 +43,13 @@ import { Field, TextInput } from '@estiva-app/ui'
43
43
  reversed at stage 3: the Field used to win and override it.
44
44
  - `helper` is the line under the control — the format, or what happens if
45
45
  it is left empty. `error` **replaces** it and marks the control invalid,
46
- so you no longer pass `aria-invalid` yourself. Both are announced;
47
- Ship and Peek wrote this line by hand six times between them, and none of
48
- those was.
46
+ so you no longer pass `aria-invalid` yourself. Both are announced; a line
47
+ written by hand beside the control is not.
49
48
  - With neither, the control stays a direct child of the field, so nothing
50
49
  that predates the two props moved a pixel.
51
50
  - **`required` marks the control as well as the label.** The asterisk is a
52
51
  picture of the requirement; `aria-required` on the control is the word for
53
- it, and the control had neither until 2026-09-08 — measured. Base UI's
52
+ it. Base UI's
54
53
  `Field` has no `required` of its own, so the Field puts it there. A control
55
54
  that says so already is left alone, and a `children` of several elements
56
55
  keeps the asterisk and owes its own.
package/src/Form.mdx CHANGED
@@ -70,7 +70,7 @@ It does these by itself. Write none of them around it.
70
70
 
71
71
  ## Keys
72
72
 
73
- The same in every form (Katerina, 16 September).
73
+ The same in every form.
74
74
 
75
75
  | Key | Does |
76
76
  |---|---|
@@ -48,7 +48,7 @@ import { IconPencil } from '@tabler/icons-react'
48
48
  - `glow` lights it in Signal — the send arrow while there is something to send.
49
49
  - **Placed from outside, never restyled.** `className` takes space, size,
50
50
  flex and grid, and position; a colour, a text size, a border, a corner or a
51
- shadow is refused by the lint (`estiva/no-restyled-part`, UIG-9).
51
+ shadow is refused by the lint (`estiva/no-restyled-part`).
52
52
  - `type` defaults to `"button"`; it is a native `<button>` on Base UI's
53
53
  Button, and every native prop passes through.
54
54
  - `disabledReason="Read only"` disables it, keeps it reachable by Tab,
@@ -74,7 +74,7 @@ It does these by itself. Write none of them around it.
74
74
  ## Keys
75
75
 
76
76
  Since stage 4 the panel is a Base UI `Menu`, and **the arrow keys walk the
77
- actions only** (Katerina, D22): they step over the identity block, the
77
+ actions only**: they step over the identity block, the
78
78
  workspace line and the notes, because those are not things you can do.
79
79
 
80
80
  | Input | What happens |
@@ -51,6 +51,9 @@ import { InlineChip } from '@estiva-app/ui'
51
51
  - A rich-text editor that renders chips from strings cannot use a component.
52
52
  `inlineChipClassName(tone)` gives it the same classes.
53
53
 
54
+ - `INLINE_CHIP_CLASSES` and `INLINE_CHIP_TONE_CLASSES` give the same look where
55
+ only classes can go — inside an editor.
56
+
54
57
  ## What it owns
55
58
 
56
59
  It does these by itself. Write none of them around it.
package/src/Kbd.mdx CHANGED
@@ -41,8 +41,8 @@ Geist Mono owns `←` and `→` but draws them 10px wide, so a chip holding one
41
41
  comes out wider than the chips beside it. Measure before adding a symbol to
42
42
  this list; the font's coverage is not uniform.
43
43
 
44
- And a trigger is the character you would actually type. Peek's resolution
45
- block fires on `->`, so its row says `->` — there is no `→` key to press.
44
+ And a trigger is the character you would actually type. A shortcut that fires
45
+ on `->` shows `->` — there is no `→` key to press.
46
46
 
47
47
  ## When not
48
48
 
package/src/Link.mdx CHANGED
@@ -12,6 +12,8 @@ the app decides how it navigates.
12
12
 
13
13
  ## When
14
14
 
15
+ - **It takes the size and colour of the text around it**: put it inside that
16
+ text.
15
17
  - **`text`** — a link written inside text: the info colour, always
16
18
  underlined, dimming on hover.
17
19
  - **`quiet`** — a title or a time that is also a link. It takes the colour and
@@ -59,7 +61,7 @@ import { Link } from '@estiva-app/ui'
59
61
  Its size and colour still come from the text around it.
60
62
  - `quiet`, `underlined` and `plain` take their size and colour from where they
61
63
  sit, so put those on your own element around the link, not on the link —
62
- the lint refuses a look passed in `className` (UIG-9).
64
+ the lint refuses a look passed in `className`.
63
65
 
64
66
  ## What it owns
65
67