@estiva-app/ui 0.22.0 → 0.24.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.
- package/README.md +21 -0
- package/dist/AppShell.d.ts.map +1 -1
- package/dist/AttachmentCard.d.ts.map +1 -1
- package/dist/Banner.d.ts +20 -2
- package/dist/Banner.d.ts.map +1 -1
- package/dist/ContainerHeader.d.ts +25 -0
- package/dist/ContainerHeader.d.ts.map +1 -0
- package/dist/EmptyState.d.ts +10 -1
- package/dist/EmptyState.d.ts.map +1 -1
- package/dist/Form.d.ts.map +1 -1
- package/dist/IconButton.d.ts +9 -1
- package/dist/IconButton.d.ts.map +1 -1
- package/dist/PreviewCard.d.ts +9 -2
- package/dist/PreviewCard.d.ts.map +1 -1
- package/dist/SectionHeader.d.ts +7 -1
- package/dist/SectionHeader.d.ts.map +1 -1
- package/dist/Select.d.ts +11 -3
- package/dist/Select.d.ts.map +1 -1
- package/dist/Tooltip.d.ts +13 -1
- package/dist/Tooltip.d.ts.map +1 -1
- package/dist/eslint/index.js +2 -1
- package/dist/eslint/index.js.map +2 -2
- package/dist/eslint/no-restyled-part.d.ts.map +1 -1
- package/dist/gates/app-checks.d.ts.map +1 -1
- package/dist/gates/{chunk-ZGJ2J5NU.js → chunk-EA33NP5B.js} +275 -11
- package/dist/gates/chunk-EA33NP5B.js.map +7 -0
- package/dist/gates/{chunk-AUXD4GCY.js → chunk-GTQZEHPC.js} +15 -1
- package/dist/gates/chunk-GTQZEHPC.js.map +7 -0
- package/dist/gates/cli.js +1 -1
- package/dist/gates/create-app.d.ts +2 -0
- package/dist/gates/create-app.d.ts.map +1 -1
- package/dist/gates/create-app.js +1 -1
- package/dist/gates/index.js +25 -3
- package/dist/gates/index.js.map +2 -2
- package/dist/gates/status.d.ts +2 -0
- package/dist/gates/status.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +196 -156
- package/dist/index.js.map +4 -4
- package/dist/registry/app-DQI6WLHR.js +10 -0
- package/dist/registry/app.d.ts +21 -0
- package/dist/registry/app.d.ts.map +1 -0
- package/dist/registry/build-LGFCCOLR.js +27 -0
- package/dist/registry/build-LGFCCOLR.js.map +7 -0
- package/dist/registry/build.d.ts +68 -2
- package/dist/registry/build.d.ts.map +1 -1
- package/dist/registry/{chunk-MRSBS5OP.js → chunk-E4JNV7PC.js} +68 -12
- package/dist/registry/chunk-E4JNV7PC.js.map +7 -0
- package/dist/registry/{chunk-QDYGB3QN.js → chunk-NJN4MQAM.js} +60 -15
- package/dist/registry/chunk-NJN4MQAM.js.map +7 -0
- package/dist/registry/{chunk-IJNCYVH4.js → chunk-W2B2G7OE.js} +34 -9
- package/dist/registry/chunk-W2B2G7OE.js.map +7 -0
- package/dist/registry/chunk-WMFF3MPP.js +648 -0
- package/dist/registry/chunk-WMFF3MPP.js.map +7 -0
- package/dist/registry/cli.js +81 -26
- package/dist/registry/cli.js.map +3 -3
- package/dist/registry/find.d.ts +12 -1
- package/dist/registry/find.d.ts.map +1 -1
- package/dist/registry/index.d.ts +5 -2
- package/dist/registry/index.d.ts.map +1 -1
- package/dist/registry/index.js +11 -3
- package/dist/registry/schema.d.ts +83 -6
- package/dist/registry/schema.d.ts.map +1 -1
- package/package.json +2 -2
- package/registry.json +283 -95
- package/src/AppShell.mdx +10 -0
- package/src/AppShell.test.tsx +16 -0
- package/src/AppShell.tsx +4 -1
- package/src/AttachmentCard.mdx +9 -0
- package/src/AttachmentCard.test.tsx +10 -0
- package/src/AttachmentCard.tsx +8 -5
- package/src/Avatar.mdx +13 -3
- package/src/AvatarGroup.mdx +4 -0
- package/src/Banner.mdx +30 -7
- package/src/Banner.stories.tsx +22 -0
- package/src/Banner.test.tsx +35 -0
- package/src/Banner.tsx +34 -9
- package/src/Breadcrumb.mdx +10 -0
- package/src/Button.mdx +14 -3
- package/src/Card.mdx +9 -0
- package/src/Checkbox.mdx +10 -0
- package/src/Chip.mdx +4 -0
- package/src/ChipInput.mdx +12 -0
- package/src/CollapsibleSection.mdx +9 -0
- package/src/CommandPalette.mdx +14 -2
- package/src/ConfirmDialog.mdx +11 -0
- package/src/ContainerHeader.mdx +60 -0
- package/src/ContainerHeader.stories.tsx +62 -0
- package/src/ContainerHeader.test.tsx +47 -0
- package/src/ContainerHeader.tsx +45 -0
- package/src/DialogShell.mdx +14 -0
- package/src/Divider.mdx +9 -1
- package/src/EditableText.mdx +11 -0
- package/src/EmptyState.mdx +27 -4
- package/src/EmptyState.stories.tsx +6 -1
- package/src/EmptyState.test.tsx +18 -1
- package/src/EmptyState.tsx +14 -1
- package/src/Field.mdx +10 -1
- package/src/FieldLine.mdx +9 -1
- package/src/FilePicker.mdx +8 -0
- package/src/Form.mdx +10 -0
- package/src/Form.test.tsx +26 -0
- package/src/Form.tsx +7 -0
- package/src/IconButton.mdx +16 -2
- package/src/IconButton.stories.tsx +3 -0
- package/src/IconButton.test.tsx +25 -0
- package/src/IconButton.tsx +61 -46
- package/src/IdentityMenu.mdx +9 -0
- package/src/InlineChip.mdx +8 -0
- package/src/Kbd.mdx +4 -0
- package/src/Link.mdx +10 -0
- package/src/Menu.mdx +16 -1
- package/src/MenuItem.mdx +12 -2
- package/src/NavItem.mdx +8 -0
- package/src/Person.mdx +4 -0
- package/src/PersonTrigger.mdx +9 -0
- package/src/Popover.mdx +21 -0
- package/src/PreviewCard.mdx +19 -4
- package/src/PreviewCard.tsx +11 -4
- package/src/ProgressBar.mdx +8 -0
- package/src/Property.mdx +4 -0
- package/src/Rail.mdx +9 -1
- package/src/RailItem.mdx +8 -0
- package/src/Reaction.mdx +9 -0
- package/src/ReactionPicker.mdx +8 -0
- package/src/ScrollArea.mdx +13 -2
- package/src/SearchInput.mdx +9 -0
- package/src/SectionHeader.mdx +13 -0
- package/src/SectionHeader.stories.tsx +9 -0
- package/src/SectionHeader.test.tsx +9 -0
- package/src/SectionHeader.tsx +8 -2
- package/src/SectionLabel.mdx +4 -0
- package/src/Select.mdx +21 -2
- package/src/Select.stories.tsx +4 -1
- package/src/Select.test.tsx +17 -0
- package/src/Select.tsx +34 -13
- package/src/Sidebar.mdx +8 -0
- package/src/Skeleton.mdx +4 -0
- package/src/Tabs.mdx +12 -1
- package/src/TextInput.mdx +8 -0
- package/src/Textarea.mdx +8 -0
- package/src/Toast.mdx +12 -1
- package/src/Toolbar.mdx +11 -0
- package/src/Tooltip.mdx +26 -2
- package/src/Tooltip.stories.tsx +26 -0
- package/src/Tooltip.test.tsx +29 -0
- package/src/Tooltip.tsx +17 -4
- package/src/TopBar.mdx +4 -0
- package/src/eslint/no-rebuilt-behaviour.ts +1 -1
- package/src/eslint/no-restyled-part.ts +1 -0
- package/src/gates/app-checks.ts +23 -1
- package/src/gates/create-app.test.ts +77 -6
- package/src/gates/create-app.ts +288 -15
- package/src/gates/status.ts +16 -0
- package/src/index.ts +1 -0
- package/src/pages.test.ts +142 -0
- package/src/registry/app.test.ts +562 -0
- package/src/registry/app.ts +854 -0
- package/src/registry/build.ts +114 -25
- package/src/registry/cli.ts +105 -42
- package/src/registry/find.ts +45 -8
- package/src/registry/index.ts +8 -1
- package/src/registry/registry.test.ts +5 -5
- package/src/registry/schema.ts +163 -12
- package/tailwind-preset.js +22 -1
- package/dist/gates/chunk-AUXD4GCY.js.map +0 -7
- package/dist/gates/chunk-ZGJ2J5NU.js.map +0 -7
- package/dist/registry/build-GOVLABI6.js +0 -13
- package/dist/registry/chunk-IJNCYVH4.js.map +0 -7
- package/dist/registry/chunk-MRSBS5OP.js.map +0 -7
- package/dist/registry/chunk-QDYGB3QN.js.map +0 -7
- /package/dist/registry/{build-GOVLABI6.js.map → app-DQI6WLHR.js.map} +0 -0
package/src/IconButton.tsx
CHANGED
|
@@ -37,6 +37,14 @@ export interface IconButtonProps extends ComponentPropsWithRef<'button'> {
|
|
|
37
37
|
/** Why the action cannot succeed right now. Disables the button, keeps it
|
|
38
38
|
* reachable by keyboard, and shows the reason as the tooltip. */
|
|
39
39
|
disabledReason?: string
|
|
40
|
+
/**
|
|
41
|
+
* A link that looks like an icon button: an anchor, so the address stays
|
|
42
|
+
* real for a modified click or a new tab, as `Link` keeps it. A router app
|
|
43
|
+
* passes `onClick`, prevents the default and navigates. A link cannot be
|
|
44
|
+
* disabled, so while it would be — `disabled`, `disabledReason`, a busy
|
|
45
|
+
* Form — it is the button (UIG-14, Katerina, 18 September).
|
|
46
|
+
*/
|
|
47
|
+
href?: string
|
|
40
48
|
/** The icon: 16px, stroke 1.5. */
|
|
41
49
|
children: ReactNode
|
|
42
50
|
}
|
|
@@ -53,56 +61,63 @@ export function IconButton({
|
|
|
53
61
|
tooltipShortcut,
|
|
54
62
|
tooltipPlacement,
|
|
55
63
|
type = 'button',
|
|
64
|
+
href,
|
|
56
65
|
...props
|
|
57
66
|
}: IconButtonProps) {
|
|
58
67
|
// Inside a busy Form: switched off, and looking it (formBusy.ts).
|
|
59
68
|
const formBusy = useFormBusy()
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
69
|
+
const unusable = disabled || !!disabledReason || formBusy
|
|
70
|
+
const look = (off: boolean) =>
|
|
71
|
+
cn(
|
|
72
|
+
// `shrink-0` stops a flex parent squashing the button; `self-center`
|
|
73
|
+
// stops one stretching it. Two different failures, and both have
|
|
74
|
+
// happened here: a row with no `items-*` drew this 24 wide and 228
|
|
75
|
+
// tall in Peek's TopicMoreMenu story (Katerina, 2026-09-11). A button
|
|
76
|
+
// is the size of its icon and its padding, whatever box it lands in.
|
|
77
|
+
//
|
|
78
|
+
// This is the one of the two that needs it: an IconButton states no
|
|
79
|
+
// height, so its cross size is `auto` and a stretching parent takes
|
|
80
|
+
// it. Measured in the same 260px row on 2026-09-12: 24px with this
|
|
81
|
+
// class, 226px without. `Button` states `h-8`/`h-6`, so it cannot be
|
|
82
|
+
// stretched and carries no `self-center` — see the note there.
|
|
83
|
+
'flex items-center justify-center p-1 rounded-lg transition-colors shrink-0 self-center cursor-pointer',
|
|
84
|
+
!off && variant === 'primary' && 'bg-accent-primary hover:bg-accent-hover text-text-inverse',
|
|
85
|
+
!off && (variant === 'muted' || variant === 'resolve') && 'text-text-secondary hover:bg-bg-hover hover:text-text-primary',
|
|
86
|
+
!off && variant === 'outlined' && 'border border-border-default hover:bg-bg-hover text-text-secondary',
|
|
87
|
+
!off && variant === 'current' && 'hover:bg-bg-hover',
|
|
88
|
+
off && variant === 'primary' && 'bg-bg-disabled text-text-disabled',
|
|
89
|
+
off && (variant === 'muted' || variant === 'resolve') && 'text-text-disabled',
|
|
90
|
+
off && variant === 'outlined' && 'border border-border-default text-text-disabled',
|
|
91
|
+
// Always, disabled too: the colour of where it sits, and Resolve's green.
|
|
92
|
+
variant === 'current' && 'text-current',
|
|
93
|
+
variant === 'resolve' && 'signal:hover:bg-success-muted signal:hover:text-success-default',
|
|
94
|
+
pressed && 'bg-bg-active text-text-primary',
|
|
95
|
+
glow && 'signal:shadow-glow-accent',
|
|
96
|
+
// `pointer-events-none` only where the button is truly out of reach:
|
|
97
|
+
// with a `disabledReason` the button IS the tooltip's trigger, and a
|
|
98
|
+
// trigger the pointer cannot land on never opens one. Base UI already
|
|
99
|
+
// swallows the click.
|
|
100
|
+
off && !disabledReason && 'pointer-events-none',
|
|
101
|
+
off && 'cursor-not-allowed',
|
|
102
|
+
className,
|
|
103
|
+
)
|
|
104
|
+
const button =
|
|
105
|
+
href && !unusable ? (
|
|
106
|
+
<a href={href} className={look(false)} {...(props as unknown as ComponentPropsWithRef<'a'>)}>
|
|
107
|
+
{children}
|
|
108
|
+
</a>
|
|
109
|
+
) : (
|
|
110
|
+
<BaseButton
|
|
111
|
+
type={type}
|
|
112
|
+
disabled={unusable}
|
|
113
|
+
focusableWhenDisabled={!!disabledReason}
|
|
114
|
+
className={(state) => look(state.disabled)}
|
|
115
|
+
aria-pressed={pressed}
|
|
116
|
+
{...props}
|
|
117
|
+
>
|
|
118
|
+
{children}
|
|
119
|
+
</BaseButton>
|
|
120
|
+
)
|
|
106
121
|
|
|
107
122
|
const label = disabledReason ?? tooltip
|
|
108
123
|
if (label) {
|
package/src/IdentityMenu.mdx
CHANGED
|
@@ -62,6 +62,15 @@ import { IdentityMenu } from '@estiva-app/ui'
|
|
|
62
62
|
row's full height, and the panel hung from the bottom of *that* — measured
|
|
63
63
|
360px below the face, with a scrollbar it should not have had.
|
|
64
64
|
|
|
65
|
+
## What it owns
|
|
66
|
+
|
|
67
|
+
It does these by itself. Write none of them around it.
|
|
68
|
+
|
|
69
|
+
| It does | So you never write | Refused when written by hand |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| Everything a Menu owns: it floats, stays attached, closes on a press outside or on Escape, and walks its rows with the arrow keys | a menu of your own around the face | |
|
|
72
|
+
| Gives focus back to the face when it closes | focus code | |
|
|
73
|
+
|
|
65
74
|
## Keys
|
|
66
75
|
|
|
67
76
|
Since stage 4 the panel is a Base UI `Menu`, and **the arrow keys walk the
|
package/src/InlineChip.mdx
CHANGED
|
@@ -51,6 +51,14 @@ 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
|
+
## What it owns
|
|
55
|
+
|
|
56
|
+
It does these by itself. Write none of them around it.
|
|
57
|
+
|
|
58
|
+
| It does | So you never write | Refused when written by hand |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
| With `href`, keeps a real address, so a modified click or a new tab still works | a `<span>` with a click handler | |
|
|
61
|
+
|
|
54
62
|
## Props
|
|
55
63
|
|
|
56
64
|
<Controls of={InlineChipStories.Neutral} />
|
package/src/Kbd.mdx
CHANGED
|
@@ -72,6 +72,10 @@ It renders what it is given and formats nothing. A modifier is called Cmd on
|
|
|
72
72
|
Apple platforms and Ctrl elsewhere, and only the caller knows which it is
|
|
73
73
|
looking at — so decide there and pass the finished label.
|
|
74
74
|
|
|
75
|
+
## What it owns
|
|
76
|
+
|
|
77
|
+
Nothing. It only draws. Clicks and keys are yours.
|
|
78
|
+
|
|
75
79
|
## Props
|
|
76
80
|
|
|
77
81
|
<Controls of={KbdStories.Default} />
|
package/src/Link.mdx
CHANGED
|
@@ -28,6 +28,7 @@ the app decides how it navigates.
|
|
|
28
28
|
- An action — something happens, nothing is navigated to → **Button**.
|
|
29
29
|
- A row of a sidebar → **NavItem**. A tile of a rail → **RailItem**.
|
|
30
30
|
- The trail back up → **Breadcrumb**.
|
|
31
|
+
- A link that looks like an icon button → **IconButton** with `href`.
|
|
31
32
|
- A word standing for a person or a thing, drawn in a small box → **InlineChip**
|
|
32
33
|
with `href`.
|
|
33
34
|
|
|
@@ -60,6 +61,15 @@ import { Link } from '@estiva-app/ui'
|
|
|
60
61
|
sit, so put those on your own element around the link, not on the link —
|
|
61
62
|
the lint refuses a look passed in `className` (UIG-9).
|
|
62
63
|
|
|
64
|
+
## What it owns
|
|
65
|
+
|
|
66
|
+
It does these by itself. Write none of them around it.
|
|
67
|
+
|
|
68
|
+
| It does | So you never write | Refused when written by hand |
|
|
69
|
+
|---|---|---|
|
|
70
|
+
| Says what it is to assistive technology: a link | `role="link"` | ✓ |
|
|
71
|
+
| Is reachable with Tab | a `tabIndex` | ✓ |
|
|
72
|
+
|
|
63
73
|
## Props
|
|
64
74
|
|
|
65
75
|
<Controls of={LinkStories.Text} />
|
package/src/Menu.mdx
CHANGED
|
@@ -41,7 +41,7 @@ definition either way.
|
|
|
41
41
|
- Picking one value for a form field → **Select**.
|
|
42
42
|
- Rows that toggle and stay open → that is a listbox, not a menu; a Checkbox
|
|
43
43
|
inside a `menuitem` is invalid. Build it as a list in a **DialogShell**.
|
|
44
|
-
- A strip of icon actions →
|
|
44
|
+
- A strip of icon actions → **Toolbar**.
|
|
45
45
|
- Who-you-are and where-you-are → **IdentityMenu**, already built on this.
|
|
46
46
|
|
|
47
47
|
## How
|
|
@@ -76,6 +76,21 @@ the trigger as "close" rather than as a press outside.
|
|
|
76
76
|
|
|
77
77
|
<Canvas of={MenuStories.OnACard} />
|
|
78
78
|
|
|
79
|
+
## What it owns
|
|
80
|
+
|
|
81
|
+
It does these by itself. Write none of them around it.
|
|
82
|
+
|
|
83
|
+
| It does | So you never write | Refused when written by hand |
|
|
84
|
+
|---|---|---|
|
|
85
|
+
| Floats on top of the page | a portal, or a `z-index` to win | ✓ |
|
|
86
|
+
| Stays attached to its anchor on scroll and resize | a scroll or resize listener, or placement arithmetic | ✓ |
|
|
87
|
+
| Closes on a press outside | a click listener on the page | ✓ |
|
|
88
|
+
| Takes its keys by itself: Enter and Space choose a row, Escape closes it | a key listener on the page | ✓ |
|
|
89
|
+
| Moves through its items with the arrow keys, and jumps to a row by its first letters | an arrow-key handler | ✓ |
|
|
90
|
+
| Says what it is to assistive technology: a menu, and each row a menu item | `role="menu"` or `role="menuitem"` | ✓ |
|
|
91
|
+
| Keeps its own open state, and closes when a row is chosen | `useState` for open | |
|
|
92
|
+
| Gives focus back to the trigger when it closes | focus code | |
|
|
93
|
+
|
|
79
94
|
## Keys
|
|
80
95
|
|
|
81
96
|
| Input | What happens |
|
package/src/MenuItem.mdx
CHANGED
|
@@ -25,8 +25,9 @@ other two), `destructive` in the error colour, `selected` as the fill.
|
|
|
25
25
|
|
|
26
26
|
## When not
|
|
27
27
|
|
|
28
|
-
-
|
|
29
|
-
|
|
28
|
+
- A single action that is not a row in a list → **Button**. Outside a
|
|
29
|
+
**Menu**, MenuItem is a plain button with a row's look, so a row-shaped
|
|
30
|
+
action at the top of a list — "Add members" — is fine.
|
|
30
31
|
- Picking a form value → **Select**'s own options.
|
|
31
32
|
- A row that toggles a checkbox → not a menu at all; see the Menu page.
|
|
32
33
|
|
|
@@ -50,6 +51,15 @@ import { IconPencil } from '@tabler/icons-react'
|
|
|
50
51
|
- A destructive row's `leading` icon takes the error colour itself
|
|
51
52
|
(`text-error-default`) — the row colours only its label.
|
|
52
53
|
|
|
54
|
+
## What it owns
|
|
55
|
+
|
|
56
|
+
It does these by itself. Write none of them around it.
|
|
57
|
+
|
|
58
|
+
| It does | So you never write | Refused when written by hand |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
| Is a row of its Menu: the Menu highlights it, walks to it with the arrow keys, and closes when it is chosen | a highlight or a key handler on the row | |
|
|
61
|
+
| Opens its `submenu` with → and closes it with ← | a submenu of your own | |
|
|
62
|
+
|
|
53
63
|
## Props
|
|
54
64
|
|
|
55
65
|
<Controls of={MenuItemStories.Default} />
|
package/src/NavItem.mdx
CHANGED
|
@@ -48,6 +48,14 @@ import { NavItem } from '@estiva-app/ui'
|
|
|
48
48
|
anchor prop passes through.
|
|
49
49
|
- The label truncates; the count never gives up its width.
|
|
50
50
|
|
|
51
|
+
## What it owns
|
|
52
|
+
|
|
53
|
+
It does these by itself. Write none of them around it.
|
|
54
|
+
|
|
55
|
+
| It does | So you never write | Refused when written by hand |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| Marks the current place for assistive technology when `active` | `aria-current` | |
|
|
58
|
+
|
|
51
59
|
## Props
|
|
52
60
|
|
|
53
61
|
<Controls of={NavItemStories.Default} />
|
package/src/Person.mdx
CHANGED
|
@@ -39,6 +39,10 @@ import { Person } from '@estiva-app/ui'
|
|
|
39
39
|
and words are always set together (the Sizes story shows the pairings).
|
|
40
40
|
- The name truncates; give the container `min-w-0` room to let it.
|
|
41
41
|
|
|
42
|
+
## What it owns
|
|
43
|
+
|
|
44
|
+
Nothing. It only draws. Clicks and keys are yours.
|
|
45
|
+
|
|
42
46
|
## Props
|
|
43
47
|
|
|
44
48
|
<Controls of={PersonStories.Named} />
|
package/src/PersonTrigger.mdx
CHANGED
|
@@ -53,6 +53,15 @@ import { Menu, PersonTrigger } from '@estiva-app/ui'
|
|
|
53
53
|
- It is a native `<button>` on Base UI's Button: it takes a `ref` and every
|
|
54
54
|
native button prop.
|
|
55
55
|
|
|
56
|
+
## What it owns
|
|
57
|
+
|
|
58
|
+
It does these by itself. Write none of them around it.
|
|
59
|
+
|
|
60
|
+
| It does | So you never write | Refused when written by hand |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| Is a button: Tab reaches it, Enter and Space call your `onClick` | a clickable `<div>` | |
|
|
63
|
+
| Names itself from the person's name, and says whether what it opens is open (`open`) | `aria-label` or `aria-expanded` | |
|
|
64
|
+
|
|
56
65
|
## Keys
|
|
57
66
|
|
|
58
67
|
| Key | Does |
|
package/src/Popover.mdx
CHANGED
|
@@ -83,7 +83,15 @@ so it is the one case the caller drives:
|
|
|
83
83
|
open={selectionRect !== null}
|
|
84
84
|
onOpenChange={(open) => !open && setSelectionRect(null)}
|
|
85
85
|
ariaLabel="Formatting"
|
|
86
|
+
side="top"
|
|
87
|
+
contentClassName="p-1"
|
|
86
88
|
>
|
|
89
|
+
<Toolbar aria-label="Formatting">
|
|
90
|
+
<ToolbarButton aria-label="Bold" tooltip="Bold" onClick={bold}>
|
|
91
|
+
<IconBold size={16} stroke={1.5} />
|
|
92
|
+
</ToolbarButton>
|
|
93
|
+
</Toolbar>
|
|
94
|
+
</Popover>
|
|
87
95
|
```
|
|
88
96
|
|
|
89
97
|
- **`anchor`** takes an element or a rect. A new rect moves the panel, so
|
|
@@ -116,6 +124,19 @@ something belongs on a `trigger`.
|
|
|
116
124
|
|
|
117
125
|
<Canvas of={PopoverStories.FlippedForRoom} />
|
|
118
126
|
|
|
127
|
+
## What it owns
|
|
128
|
+
|
|
129
|
+
It does these by itself. Write none of them around it.
|
|
130
|
+
|
|
131
|
+
| It does | So you never write | Refused when written by hand |
|
|
132
|
+
|---|---|---|
|
|
133
|
+
| Floats on top of the page | a portal, or a `z-index` to win | ✓ |
|
|
134
|
+
| Stays attached to its anchor on scroll and resize, and flips side when there is no room | a scroll or resize listener, or placement arithmetic | ✓ |
|
|
135
|
+
| Closes on a press outside | a click listener on the page | ✓ |
|
|
136
|
+
| Takes its keys by itself: Escape closes it | a key listener on the page | ✓ |
|
|
137
|
+
| Moves focus in when it opens, and back to the trigger when it closes | focus code | |
|
|
138
|
+
| Keeps its own open state | `useState` for open — unless you must know it (`open` / `onOpenChange`) | |
|
|
139
|
+
|
|
119
140
|
## Keys
|
|
120
141
|
|
|
121
142
|
| Input | What happens |
|
package/src/PreviewCard.mdx
CHANGED
|
@@ -45,7 +45,7 @@ pointer never sees it.
|
|
|
45
45
|
import { PreviewCard } from '@estiva-app/ui'
|
|
46
46
|
|
|
47
47
|
<PreviewCard content={<ItemDetail id={row.id} />} wrapperClassName="block w-full">
|
|
48
|
-
<ListRow
|
|
48
|
+
<ListRow item={row} />
|
|
49
49
|
</PreviewCard>
|
|
50
50
|
```
|
|
51
51
|
|
|
@@ -57,9 +57,12 @@ import { PreviewCard } from '@estiva-app/ui'
|
|
|
57
57
|
- **It prefers the right of the trigger and flips** when that side has no room,
|
|
58
58
|
staying 12px from the trigger and 8px clear of every screen edge. `side`
|
|
59
59
|
changes the preference, not the promise.
|
|
60
|
-
- **Width
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
- **Width is yours** through `className`; **padding and rhythm** through
|
|
61
|
+
`contentClassName`. The default is 360px wide, 12px of padding, capped at
|
|
62
|
+
300px or the room available, whichever is smaller — a preview of one kind of
|
|
63
|
+
thing is not the size of a preview of another.
|
|
64
|
+
- The padding sits inside the scrolling box, as in `Popover`, so the
|
|
65
|
+
scrollbar hugs the card's edge: its thumb 3px from it, like every panel's.
|
|
63
66
|
- The trigger wrapper is `inline-flex`; a full-width row wants
|
|
64
67
|
`wrapperClassName="block w-full"`.
|
|
65
68
|
|
|
@@ -82,6 +85,18 @@ lands.
|
|
|
82
85
|
|
|
83
86
|
<Canvas of={PreviewCardStories.Loading} />
|
|
84
87
|
|
|
88
|
+
## What it owns
|
|
89
|
+
|
|
90
|
+
It does these by itself. Write none of them around it.
|
|
91
|
+
|
|
92
|
+
| It does | So you never write | Refused when written by hand |
|
|
93
|
+
|---|---|---|
|
|
94
|
+
| Floats on top of the page | a portal, or a `z-index` to win | ✓ |
|
|
95
|
+
| Stays attached to its anchor on scroll and resize | a scroll or resize listener, or placement arithmetic | ✓ |
|
|
96
|
+
| Closes on a press outside | a click listener on the page | ✓ |
|
|
97
|
+
| Opens after the pointer rests, and stays up while the pointer moves into it | a hover timer | |
|
|
98
|
+
| Closes on Escape | a key handler | |
|
|
99
|
+
|
|
85
100
|
## Keys
|
|
86
101
|
|
|
87
102
|
There is no keyboard trigger, by design — see above.
|
package/src/PreviewCard.tsx
CHANGED
|
@@ -35,8 +35,15 @@ export interface PreviewCardProps {
|
|
|
35
35
|
delay?: number
|
|
36
36
|
/** After the pointer leaves, in ms — the grace that lets you cross the gap into the card. */
|
|
37
37
|
closeDelay?: number
|
|
38
|
-
/** On the card's surface: its width
|
|
38
|
+
/** On the card's surface: its width. */
|
|
39
39
|
className?: string
|
|
40
|
+
/**
|
|
41
|
+
* On the scrolling content: its padding (default 12px) and rhythm. The
|
|
42
|
+
* padding sits inside the scrolling box, as in `Popover`, so the scrollbar
|
|
43
|
+
* hugs the card's edge — its thumb 3px from it, like every panel's
|
|
44
|
+
* (Katerina, 19 September; it sat 12px in, behind the card's padding).
|
|
45
|
+
*/
|
|
46
|
+
contentClassName?: string
|
|
40
47
|
/** Extra classes on the trigger wrapper — e.g. `block w-full` for a row. */
|
|
41
48
|
wrapperClassName?: string
|
|
42
49
|
}
|
|
@@ -58,7 +65,7 @@ const OPEN_DELAY = 350
|
|
|
58
65
|
* here the card can be reached, so it can. */
|
|
59
66
|
const CLOSE_DELAY = 200
|
|
60
67
|
|
|
61
|
-
export function PreviewCard({ content, children, side = 'right', delay = OPEN_DELAY, closeDelay = CLOSE_DELAY, className, wrapperClassName }: PreviewCardProps) {
|
|
68
|
+
export function PreviewCard({ content, children, side = 'right', delay = OPEN_DELAY, closeDelay = CLOSE_DELAY, className, contentClassName, wrapperClassName }: PreviewCardProps) {
|
|
62
69
|
return (
|
|
63
70
|
<BasePreviewCard.Root>
|
|
64
71
|
{/* The part renders an `<a>` by default, which a row is not; `render`
|
|
@@ -80,10 +87,10 @@ export function PreviewCard({ content, children, side = 'right', delay = OPEN_DE
|
|
|
80
87
|
className="z-50 data-[anchor-hidden]:hidden"
|
|
81
88
|
>
|
|
82
89
|
<BasePreviewCard.Popup
|
|
83
|
-
className={cn('w-[360px] p-
|
|
90
|
+
className={cn('w-[360px] p-0 outline-none', className)}
|
|
84
91
|
render={<MenuPanel />}
|
|
85
92
|
>
|
|
86
|
-
<ScrollArea viewportClassName="max-h-[
|
|
93
|
+
<ScrollArea viewportClassName="max-h-[min(300px,var(--available-height))]" contentClassName={cn('flex flex-col gap-3 p-3', contentClassName)}>
|
|
87
94
|
{content}
|
|
88
95
|
</ScrollArea>
|
|
89
96
|
</BasePreviewCard.Popup>
|
package/src/ProgressBar.mdx
CHANGED
|
@@ -42,6 +42,14 @@ import { ProgressBar } from '@estiva-app/ui'
|
|
|
42
42
|
of the button's.
|
|
43
43
|
- `className` is for placement: a width, a margin, `flex-1` in a row.
|
|
44
44
|
|
|
45
|
+
## What it owns
|
|
46
|
+
|
|
47
|
+
It does these by itself. Write none of them around it.
|
|
48
|
+
|
|
49
|
+
| It does | So you never write | Refused when written by hand |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| Says what it is to assistive technology: a progress bar, with its value | `role="progressbar"` or `aria-valuenow` | ✓ |
|
|
52
|
+
|
|
45
53
|
## Props
|
|
46
54
|
|
|
47
55
|
<Controls of={ProgressBarStories.Default} />
|
package/src/Property.mdx
CHANGED
|
@@ -54,6 +54,10 @@ WithControls story shows the pairings).
|
|
|
54
54
|
a child of the row or the column: a control with `flex-1` still fills the
|
|
55
55
|
row, and two values still sit the row's gap apart.
|
|
56
56
|
|
|
57
|
+
## What it owns
|
|
58
|
+
|
|
59
|
+
Nothing. It only draws. Clicks and keys are yours.
|
|
60
|
+
|
|
57
61
|
## Props
|
|
58
62
|
|
|
59
63
|
<Controls of={PropertyStories.Row} />
|
package/src/Rail.mdx
CHANGED
|
@@ -25,7 +25,7 @@ nothing else: **RailItem** is the tile, and where a tile goes is the app's.
|
|
|
25
25
|
needs more than a word needs a row.
|
|
26
26
|
- More entries than fit down a screen → fewer entries. The rail does not
|
|
27
27
|
scroll, on purpose.
|
|
28
|
-
- A strip of *actions* rather than places →
|
|
28
|
+
- A strip of *actions* rather than places → **Toolbar**.
|
|
29
29
|
|
|
30
30
|
<Canvas of={RailStories.ManyItems} />
|
|
31
31
|
|
|
@@ -52,6 +52,14 @@ import { Rail, RailItem } from '@estiva-app/ui'
|
|
|
52
52
|
|
|
53
53
|
<Canvas of={RailStories.OneItem} />
|
|
54
54
|
|
|
55
|
+
## What it owns
|
|
56
|
+
|
|
57
|
+
It does these by itself. Write none of them around it.
|
|
58
|
+
|
|
59
|
+
| It does | So you never write | Refused when written by hand |
|
|
60
|
+
|---|---|---|
|
|
61
|
+
| Is a navigation, named "Navigation" unless you name it | `<nav>` or `aria-label` | |
|
|
62
|
+
|
|
55
63
|
## Keys
|
|
56
64
|
|
|
57
65
|
The rail has no keyboard of its own: its tiles are links, and Tab walks them in
|
package/src/RailItem.mdx
CHANGED
|
@@ -45,6 +45,14 @@ import { Rail, RailItem } from '@estiva-app/ui'
|
|
|
45
45
|
click, navigate.
|
|
46
46
|
- Icons are 16px at stroke 1.5.
|
|
47
47
|
|
|
48
|
+
## What it owns
|
|
49
|
+
|
|
50
|
+
It does these by itself. Write none of them around it.
|
|
51
|
+
|
|
52
|
+
| It does | So you never write | Refused when written by hand |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| Marks the current place for assistive technology when `active` | `aria-current` | |
|
|
55
|
+
|
|
48
56
|
## Props
|
|
49
57
|
|
|
50
58
|
<Controls of={RailItemStories.Default} />
|
package/src/Reaction.mdx
CHANGED
|
@@ -50,6 +50,15 @@ import { Reaction } from '@estiva-app/ui'
|
|
|
50
50
|
another client can change the count.
|
|
51
51
|
- `disabled` takes it out of the Tab order and ignores presses.
|
|
52
52
|
|
|
53
|
+
## What it owns
|
|
54
|
+
|
|
55
|
+
It does these by itself. Write none of them around it.
|
|
56
|
+
|
|
57
|
+
| It does | So you never write | Refused when written by hand |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| Is a toggle: pressed or not, and says so to assistive technology | `aria-pressed` | |
|
|
60
|
+
| Presses on Enter and Space; Tab skips it while disabled | a key handler | |
|
|
61
|
+
|
|
53
62
|
## Keys
|
|
54
63
|
|
|
55
64
|
| Key | What happens |
|
package/src/ReactionPicker.mdx
CHANGED
|
@@ -68,6 +68,14 @@ and, when it is yours, the accent fill. The picker asks; the pill answers.
|
|
|
68
68
|
|
|
69
69
|
<Canvas of={ReactionPickerStories.AndWhatItProduces} />
|
|
70
70
|
|
|
71
|
+
## What it owns
|
|
72
|
+
|
|
73
|
+
It does these by itself. Write none of them around it.
|
|
74
|
+
|
|
75
|
+
| It does | So you never write | Refused when written by hand |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| Everything a Toolbar owns: one Tab stop, arrow keys between the reactions, wrapping at the ends | a key handler, or `tabIndex` on each reaction | |
|
|
78
|
+
|
|
71
79
|
## Keys
|
|
72
80
|
|
|
73
81
|
| Input | What happens |
|
package/src/ScrollArea.mdx
CHANGED
|
@@ -13,8 +13,10 @@ the content is moving; nothing moves when it appears.
|
|
|
13
13
|
|
|
14
14
|
## When
|
|
15
15
|
|
|
16
|
-
- Any box that can hold more than fits
|
|
17
|
-
|
|
16
|
+
- Any box that can hold more than fits — **a list that grows with its data
|
|
17
|
+
counts, even when today's data fits**: a menu or a dropdown list, a panel, a
|
|
18
|
+
sidebar, the page's content column. Not a rail: a rail does not scroll, on
|
|
19
|
+
purpose.
|
|
18
20
|
- `orientation="horizontal"` for a row wider than its box — a table, a
|
|
19
21
|
board; `"both"` when a thing can run over either way.
|
|
20
22
|
|
|
@@ -53,6 +55,15 @@ import { ScrollArea } from '@estiva-app/ui'
|
|
|
53
55
|
Chrome: wheel down over a sideways region inside a page, the page moves
|
|
54
56
|
and the region does not; a sideways swipe, the other way round.
|
|
55
57
|
|
|
58
|
+
## What it owns
|
|
59
|
+
|
|
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
|
+
| Scrolls with our scrollbar | `overflow-auto` or `overflow-scroll` | ✓ |
|
|
65
|
+
| Scrolls with the keyboard when it has focus, like any scrolling box | key handlers | |
|
|
66
|
+
|
|
56
67
|
## Keys
|
|
57
68
|
|
|
58
69
|
| Key | Does |
|
package/src/SearchInput.mdx
CHANGED
|
@@ -38,6 +38,15 @@ import { SearchInput } from '@estiva-app/ui'
|
|
|
38
38
|
and this usually stands alone in a top bar rather than in a `Field`.
|
|
39
39
|
Inside one, the label names it and you add nothing.
|
|
40
40
|
|
|
41
|
+
## What it owns
|
|
42
|
+
|
|
43
|
+
It does these by itself. Write none of them around it.
|
|
44
|
+
|
|
45
|
+
| It does | So you never write | Refused when written by hand |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| Inside a `Field`, takes its name from the Field's label | an `id` / `htmlFor` pair | |
|
|
48
|
+
| Shows its focus ring while anything inside it has focus | a focus style of your own | |
|
|
49
|
+
|
|
41
50
|
## Props
|
|
42
51
|
|
|
43
52
|
<Controls of={SearchInputStories.Default} />
|
package/src/SectionHeader.mdx
CHANGED
|
@@ -53,6 +53,19 @@ import { IconPlus } from '@tabler/icons-react'
|
|
|
53
53
|
- `render` swaps the title's element in Base UI's manner; it is how
|
|
54
54
|
CollapsibleSection makes the title a `Collapsible.Trigger`.
|
|
55
55
|
|
|
56
|
+
- `hover="none"` keeps the row still under the pointer: for a heading whose
|
|
57
|
+
actions are always shown (`showActions="always"`), where the buttons light up
|
|
58
|
+
on their own. The default, `fill`, lights a row that toggles or has actions.
|
|
59
|
+
|
|
60
|
+
## What it owns
|
|
61
|
+
|
|
62
|
+
It does these by itself. Write none of them around it.
|
|
63
|
+
|
|
64
|
+
| It does | So you never write | Refused when written by hand |
|
|
65
|
+
|---|---|---|
|
|
66
|
+
| With `chevron`, its title is a button that calls `onToggle` on a click, Enter or Space, and says whether it is open | a button of your own, or `aria-expanded` | |
|
|
67
|
+
| Shows its actions while one of them has focus | a focus style of your own | |
|
|
68
|
+
|
|
56
69
|
## Keys
|
|
57
70
|
|
|
58
71
|
| Key | Does |
|
|
@@ -35,6 +35,15 @@ export const PersistentActions: Story = {
|
|
|
35
35
|
},
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/** `hover="none"`: actions always shown, and the row stays still under the pointer — only the buttons light up. */
|
|
39
|
+
export const StillOnHover: Story = {
|
|
40
|
+
args: {
|
|
41
|
+
showActions: 'always',
|
|
42
|
+
hover: 'none',
|
|
43
|
+
actions: [{ icon: <IconPlus size={16} stroke={1.5} />, tooltip: 'Add', onClick: () => {} }],
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
|
|
38
47
|
/** A count beside the title, held on screen while the actions come and go. */
|
|
39
48
|
export const WithTrailing: Story = {
|
|
40
49
|
args: {
|
|
@@ -23,6 +23,15 @@ describe('SectionHeader', () => {
|
|
|
23
23
|
expect(onToggle).toHaveBeenCalledTimes(2)
|
|
24
24
|
})
|
|
25
25
|
|
|
26
|
+
it('fills under the pointer when it does something, and hover="none" keeps it still', () => {
|
|
27
|
+
const actions = [{ icon: <i />, tooltip: 'Add', onClick: () => {} }]
|
|
28
|
+
const lit = render(<SectionHeader title="Section" actions={actions} />)
|
|
29
|
+
expect((lit.container.firstElementChild as HTMLElement).className).toContain('hover:bg-bg-hover')
|
|
30
|
+
cleanup()
|
|
31
|
+
const still = render(<SectionHeader title="Section" actions={actions} hover="none" />)
|
|
32
|
+
expect((still.container.firstElementChild as HTMLElement).className).not.toContain('hover:bg-bg-hover')
|
|
33
|
+
})
|
|
34
|
+
|
|
26
35
|
it('an action beside the title acts, and never toggles', async () => {
|
|
27
36
|
const onToggle = vi.fn()
|
|
28
37
|
const add = vi.fn()
|