@estiva-app/ui 0.23.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/dist/AppShell.d.ts.map +1 -1
- package/dist/AttachmentCard.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/index.js +127 -108
- package/dist/index.js.map +2 -2
- package/package.json +1 -1
- package/registry.json +51 -8
- 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 +13 -3
- 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 +4 -0
- package/src/DialogShell.mdx +14 -0
- package/src/Divider.mdx +9 -1
- package/src/EditableText.mdx +11 -0
- package/src/EmptyState.mdx +15 -4
- 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/pages.test.ts +142 -0
- package/tailwind-preset.js +22 -1
package/package.json
CHANGED
package/registry.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"kind": "package",
|
|
5
5
|
"repo": "estiva-ui",
|
|
6
6
|
"package": "@estiva-app/ui",
|
|
7
|
-
"packageVersion": "0.
|
|
7
|
+
"packageVersion": "0.24.0",
|
|
8
8
|
"exports": 82,
|
|
9
9
|
"typeExports": 92,
|
|
10
10
|
"files": null
|
|
@@ -1436,7 +1436,7 @@
|
|
|
1436
1436
|
},
|
|
1437
1437
|
{
|
|
1438
1438
|
"id": "page-keys",
|
|
1439
|
-
"behaviour": "
|
|
1439
|
+
"behaviour": "Takes its keys by itself"
|
|
1440
1440
|
},
|
|
1441
1441
|
{
|
|
1442
1442
|
"id": "focus",
|
|
@@ -1927,6 +1927,12 @@
|
|
|
1927
1927
|
"required": false,
|
|
1928
1928
|
"note": "Why the action cannot succeed right now."
|
|
1929
1929
|
},
|
|
1930
|
+
{
|
|
1931
|
+
"name": "href",
|
|
1932
|
+
"takes": "text",
|
|
1933
|
+
"required": false,
|
|
1934
|
+
"note": "A link that looks like an icon button: an anchor, so the address stays real for a modified click or a new tab, as `Link` keeps it."
|
|
1935
|
+
},
|
|
1930
1936
|
{
|
|
1931
1937
|
"name": "children",
|
|
1932
1938
|
"takes": "anything",
|
|
@@ -2477,7 +2483,7 @@
|
|
|
2477
2483
|
},
|
|
2478
2484
|
{
|
|
2479
2485
|
"id": "page-keys",
|
|
2480
|
-
"behaviour": "
|
|
2486
|
+
"behaviour": "Takes its keys by itself"
|
|
2481
2487
|
},
|
|
2482
2488
|
{
|
|
2483
2489
|
"id": "follow",
|
|
@@ -3019,7 +3025,7 @@
|
|
|
3019
3025
|
},
|
|
3020
3026
|
{
|
|
3021
3027
|
"id": "page-keys",
|
|
3022
|
-
"behaviour": "
|
|
3028
|
+
"behaviour": "Takes its keys by itself"
|
|
3023
3029
|
},
|
|
3024
3030
|
{
|
|
3025
3031
|
"id": "follow",
|
|
@@ -3076,7 +3082,13 @@
|
|
|
3076
3082
|
"name": "className",
|
|
3077
3083
|
"takes": "text",
|
|
3078
3084
|
"required": false,
|
|
3079
|
-
"note": "On the card's surface: its width
|
|
3085
|
+
"note": "On the card's surface: its width."
|
|
3086
|
+
},
|
|
3087
|
+
{
|
|
3088
|
+
"name": "contentClassName",
|
|
3089
|
+
"takes": "text",
|
|
3090
|
+
"required": false,
|
|
3091
|
+
"note": "On the scrolling content: its padding (default 12px) and rhythm."
|
|
3080
3092
|
},
|
|
3081
3093
|
{
|
|
3082
3094
|
"name": "wrapperClassName",
|
|
@@ -3587,6 +3599,12 @@
|
|
|
3587
3599
|
"required": false,
|
|
3588
3600
|
"note": "`hover` reveals the actions while the row is hovered or focused; `always` keeps them."
|
|
3589
3601
|
},
|
|
3602
|
+
{
|
|
3603
|
+
"name": "hover",
|
|
3604
|
+
"takes": "fill | none",
|
|
3605
|
+
"required": false,
|
|
3606
|
+
"note": "`fill` lights the row under the pointer when it does something (a toggle, actions); `none` keeps it still — a heading whose actions are always shown, where the buttons light up on their own (UIG-14, Katerina, 19 September)."
|
|
3607
|
+
},
|
|
3590
3608
|
{
|
|
3591
3609
|
"name": "render",
|
|
3592
3610
|
"takes": "useRender.RenderProp",
|
|
@@ -3607,6 +3625,13 @@
|
|
|
3607
3625
|
"hover",
|
|
3608
3626
|
"always"
|
|
3609
3627
|
]
|
|
3628
|
+
},
|
|
3629
|
+
{
|
|
3630
|
+
"prop": "hover",
|
|
3631
|
+
"values": [
|
|
3632
|
+
"fill",
|
|
3633
|
+
"none"
|
|
3634
|
+
]
|
|
3610
3635
|
}
|
|
3611
3636
|
],
|
|
3612
3637
|
"ownsBehaviours": [],
|
|
@@ -3719,6 +3744,12 @@
|
|
|
3719
3744
|
"required": false,
|
|
3720
3745
|
"note": null
|
|
3721
3746
|
},
|
|
3747
|
+
{
|
|
3748
|
+
"name": "disabledReason",
|
|
3749
|
+
"takes": "text",
|
|
3750
|
+
"required": false,
|
|
3751
|
+
"note": "Why it cannot be changed right now (UIG-14, Katerina, 19 September)."
|
|
3752
|
+
},
|
|
3722
3753
|
{
|
|
3723
3754
|
"name": "className",
|
|
3724
3755
|
"takes": "text",
|
|
@@ -3750,7 +3781,7 @@
|
|
|
3750
3781
|
},
|
|
3751
3782
|
{
|
|
3752
3783
|
"id": "page-keys",
|
|
3753
|
-
"behaviour": "
|
|
3784
|
+
"behaviour": "Takes its keys by itself"
|
|
3754
3785
|
},
|
|
3755
3786
|
{
|
|
3756
3787
|
"id": "follow",
|
|
@@ -3964,7 +3995,7 @@
|
|
|
3964
3995
|
},
|
|
3965
3996
|
{
|
|
3966
3997
|
"id": "page-keys",
|
|
3967
|
-
"behaviour": "
|
|
3998
|
+
"behaviour": "Takes its keys by itself"
|
|
3968
3999
|
},
|
|
3969
4000
|
{
|
|
3970
4001
|
"id": "walking",
|
|
@@ -4204,7 +4235,7 @@
|
|
|
4204
4235
|
},
|
|
4205
4236
|
{
|
|
4206
4237
|
"id": "page-keys",
|
|
4207
|
-
"behaviour": "
|
|
4238
|
+
"behaviour": "Takes its keys by itself"
|
|
4208
4239
|
},
|
|
4209
4240
|
{
|
|
4210
4241
|
"id": "walking",
|
|
@@ -4279,6 +4310,12 @@
|
|
|
4279
4310
|
"required": false,
|
|
4280
4311
|
"note": "Why the action cannot succeed right now."
|
|
4281
4312
|
},
|
|
4313
|
+
{
|
|
4314
|
+
"name": "href",
|
|
4315
|
+
"takes": "text",
|
|
4316
|
+
"required": false,
|
|
4317
|
+
"note": "A link that looks like an icon button: an anchor, so the address stays real for a modified click or a new tab, as `Link` keeps it."
|
|
4318
|
+
},
|
|
4282
4319
|
{
|
|
4283
4320
|
"name": "children",
|
|
4284
4321
|
"takes": "anything",
|
|
@@ -4576,6 +4613,12 @@
|
|
|
4576
4613
|
"required": false,
|
|
4577
4614
|
"note": "Extra classes on the wrapper — e.g. `min-w-0 shrink` so a truncating label keeps truncating inside it."
|
|
4578
4615
|
},
|
|
4616
|
+
{
|
|
4617
|
+
"name": "inline",
|
|
4618
|
+
"takes": "true/false",
|
|
4619
|
+
"required": false,
|
|
4620
|
+
"note": "For a trigger inside a line of text — a reference, a name in a sentence: the wrapper is a `<span>`, because a `<div>` is not allowed inside a paragraph (UIG-14, Katerina, 19 September)."
|
|
4621
|
+
},
|
|
4579
4622
|
{
|
|
4580
4623
|
"name": "children",
|
|
4581
4624
|
"takes": "anything",
|
package/src/AppShell.mdx
CHANGED
|
@@ -75,6 +75,16 @@ import { AppShell, Banner, IdentityMenu, Rail, RailItem } from '@estiva-app/ui'
|
|
|
75
75
|
- In the floating manner the card owns `overflow-hidden` — your content
|
|
76
76
|
manages its own scrolling inside it.
|
|
77
77
|
- Desktop only for now — no narrow-screen behaviour is provided.
|
|
78
|
+
- In the Signal theme the floating frame draws the control-room dot grid on
|
|
79
|
+
its ground, behind the card. Nothing to add: the preset makes it.
|
|
80
|
+
|
|
81
|
+
## What it owns
|
|
82
|
+
|
|
83
|
+
It does these by itself. Write none of them around it.
|
|
84
|
+
|
|
85
|
+
| It does | So you never write | Refused when written by hand |
|
|
86
|
+
|---|---|---|
|
|
87
|
+
| Scrolls the page's content column, in a ScrollArea | `overflow-auto` on the page or on `main` | |
|
|
78
88
|
|
|
79
89
|
## Props
|
|
80
90
|
|
package/src/AppShell.test.tsx
CHANGED
|
@@ -36,4 +36,20 @@ describe('AppShell', () => {
|
|
|
36
36
|
expect(main.className).toContain('overflow-hidden')
|
|
37
37
|
expect(main.closest('[style*="overflow: scroll"]')).toBeNull()
|
|
38
38
|
})
|
|
39
|
+
|
|
40
|
+
it('carries the Signal canvas on the floating frame, and only there', () => {
|
|
41
|
+
const { container, unmount } = render(
|
|
42
|
+
<AppShell variant="floating" nav={<nav>Nav</nav>}>
|
|
43
|
+
<p>Page</p>
|
|
44
|
+
</AppShell>,
|
|
45
|
+
)
|
|
46
|
+
expect((container.firstElementChild as HTMLElement).className).toContain('signal-canvas')
|
|
47
|
+
unmount()
|
|
48
|
+
const solid = render(
|
|
49
|
+
<AppShell nav={<nav>Nav</nav>}>
|
|
50
|
+
<p>Page</p>
|
|
51
|
+
</AppShell>,
|
|
52
|
+
)
|
|
53
|
+
expect(solid.container.querySelector('.signal-canvas')).toBeNull()
|
|
54
|
+
})
|
|
39
55
|
})
|
package/src/AppShell.tsx
CHANGED
|
@@ -45,7 +45,10 @@ export function AppShell({ variant = 'solid', menu, logo, search, identity, bann
|
|
|
45
45
|
|
|
46
46
|
if (variant === 'floating') {
|
|
47
47
|
return (
|
|
48
|
-
|
|
48
|
+
// `signal-canvas`: in the Signal theme, the preset draws the control-room dot
|
|
49
|
+
// grid behind everything in the frame (Peek's, moved here with the frame:
|
|
50
|
+
// UIG-14, Katerina, 19 September — the canvas is the theme's, not an app's).
|
|
51
|
+
<div className="signal-canvas relative h-full min-h-0 overflow-hidden bg-bg-base">
|
|
49
52
|
{bar}
|
|
50
53
|
<div className="flex h-full pb-4 pr-4 pt-[52px]">
|
|
51
54
|
{nav}
|
package/src/AttachmentCard.mdx
CHANGED
|
@@ -57,6 +57,15 @@ import { AttachmentCard } from '@estiva-app/ui'
|
|
|
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
|
+
## 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
|
+
| Stops the clicks on its picture, its download and its remove control, so they never also click the card or row around it | `stopPropagation` | |
|
|
67
|
+
| Shows the full name on hover when the name is cut off | a tooltip of your own | |
|
|
68
|
+
|
|
60
69
|
## Props
|
|
61
70
|
|
|
62
71
|
<Controls of={AttachmentCardStories.Document} />
|
|
@@ -169,4 +169,14 @@ describe('AttachmentCard', () => {
|
|
|
169
169
|
expect(onRemove).toHaveBeenCalledTimes(1)
|
|
170
170
|
expect(outside).not.toHaveBeenCalled()
|
|
171
171
|
})
|
|
172
|
+
|
|
173
|
+
it('the ✕ shows when the keyboard reaches it, not only on hover (C4)', async () => {
|
|
174
|
+
render(<AttachmentCard pending name="a.png" size={1} onRemove={() => {}} />)
|
|
175
|
+
const remove = screen.getByRole('button', { name: 'Remove a.png' })
|
|
176
|
+
// Hidden at rest; each of these reveals it. jsdom draws nothing, so the classes are the contract.
|
|
177
|
+
expect(remove.className).toContain('opacity-0')
|
|
178
|
+
for (const reveal of ['group-hover:opacity-100', 'group-focus-within:opacity-100', 'focus-visible:opacity-100']) expect(remove.className).toContain(reveal)
|
|
179
|
+
await userEvent.tab()
|
|
180
|
+
expect(document.activeElement).toBe(remove)
|
|
181
|
+
})
|
|
172
182
|
})
|
package/src/AttachmentCard.tsx
CHANGED
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
import { Card } from './Card'
|
|
21
21
|
import { IconButton } from './IconButton'
|
|
22
22
|
import { Link } from './Link'
|
|
23
|
-
import { WithTooltip } from './Tooltip'
|
|
23
|
+
import { WithTooltip, type WithTooltipProps } from './Tooltip'
|
|
24
24
|
import { cn } from './cn'
|
|
25
25
|
|
|
26
26
|
/**
|
|
@@ -98,7 +98,7 @@ function TypeIcon({ name }: { name: string }) {
|
|
|
98
98
|
* the fallback face can fit and then not, in a line whose box never changes. The wrapper is `flex-col` so the
|
|
99
99
|
* text stretches across it, as wide as it is without one.
|
|
100
100
|
*/
|
|
101
|
-
function Truncating({ text, hint, className, wrapperClassName }: { text: string; hint?: string; className: string; wrapperClassName?: string }) {
|
|
101
|
+
function Truncating({ text, hint, className, wrapperClassName, placement }: { text: string; hint?: string; className: string; wrapperClassName?: string; placement?: WithTooltipProps['placement'] }) {
|
|
102
102
|
const ref = useRef<HTMLSpanElement>(null)
|
|
103
103
|
const [cut, setCut] = useState(false)
|
|
104
104
|
useLayoutEffect(() => {
|
|
@@ -127,7 +127,7 @@ function Truncating({ text, hint, className, wrapperClassName }: { text: string;
|
|
|
127
127
|
)
|
|
128
128
|
if (!hint && !cut) return line
|
|
129
129
|
return (
|
|
130
|
-
<WithTooltip label={hint ?? text} wrapperClassName={cn('min-w-0 flex-col', wrapperClassName)}>
|
|
130
|
+
<WithTooltip label={hint ?? text} placement={placement} wrapperClassName={cn('min-w-0 flex-col', wrapperClassName)}>
|
|
131
131
|
{line}
|
|
132
132
|
</WithTooltip>
|
|
133
133
|
)
|
|
@@ -206,7 +206,8 @@ export function AttachmentCard({
|
|
|
206
206
|
)}
|
|
207
207
|
</div>
|
|
208
208
|
<div className="flex flex-col gap-[1px] min-w-0">
|
|
209
|
-
|
|
209
|
+
{/* Below, not above: above, a cut name's tooltip covered the ✕ on the corner (UIG-14, C4). */}
|
|
210
|
+
<Truncating text={name} className={NAME_CLASSES} placement={onRemove ? 'bottom' : undefined} />
|
|
210
211
|
<Truncating
|
|
211
212
|
text={(failed || warning ? note : state === 'uploading' ? (note ?? 'Uploading…') : (note ?? sizeText)) ?? ''}
|
|
212
213
|
hint={noteHint}
|
|
@@ -219,7 +220,9 @@ export function AttachmentCard({
|
|
|
219
220
|
<BaseButton
|
|
220
221
|
type="button"
|
|
221
222
|
aria-label={`Remove ${name}`}
|
|
222
|
-
|
|
223
|
+
// Shows with the card's hover and with keyboard focus, as Download does: hover-only left it invisible to
|
|
224
|
+
// the keyboard that had reached it (UIG-14, C4, Katerina 19 September).
|
|
225
|
+
className="absolute -top-1.5 -right-1.5 size-5 rounded-full bg-bg-elevated border border-border-strong flex items-center justify-center text-text-secondary hover:text-text-primary opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 focus-visible:opacity-100 transition-opacity"
|
|
223
226
|
onClick={(event) => {
|
|
224
227
|
event.stopPropagation()
|
|
225
228
|
onRemove()
|
package/src/Avatar.mdx
CHANGED
|
@@ -13,13 +13,15 @@ theirs, or — when there is no name to take initials from — a silhouette.
|
|
|
13
13
|
## When
|
|
14
14
|
|
|
15
15
|
- Anywhere a person appears at a glance: a row, a card, a picker, a byline.
|
|
16
|
-
- The scale is 16 · 24 · 32 · 36 (default)
|
|
17
|
-
inventing sizes.
|
|
16
|
+
- The scale is 16 · 18 · 20 · 24 · 32 · 36 (default): 18 in a group's reply
|
|
17
|
+
row, 20 in a **Person**. Pick from it rather than inventing sizes.
|
|
18
18
|
|
|
19
19
|
## When not
|
|
20
20
|
|
|
21
21
|
- Several people at once → **AvatarGroup**.
|
|
22
|
-
- The face beside
|
|
22
|
+
- The face beside a name, with nothing else on the line → **Person**. A byline
|
|
23
|
+
that sets the name its own way — bold, with a time — is an Avatar beside its
|
|
24
|
+
own text.
|
|
23
25
|
- The face as a button that opens a menu → **PersonTrigger**.
|
|
24
26
|
|
|
25
27
|
## How
|
|
@@ -66,6 +68,14 @@ a stack of members — and the tile becomes one image with one name.
|
|
|
66
68
|
|
|
67
69
|
<Canvas of={AvatarStories.Palette} />
|
|
68
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
|
+
| Shows initials from the name when there is no picture, or when it fails to load | an `onError`, or initials of your own | |
|
|
78
|
+
|
|
69
79
|
## Props
|
|
70
80
|
|
|
71
81
|
<Controls of={AvatarStories.Initials} />
|
package/src/AvatarGroup.mdx
CHANGED
|
@@ -34,6 +34,10 @@ import { AvatarGroup } from '@estiva-app/ui'
|
|
|
34
34
|
- The ring rides on the Avatar itself (one box); don't wrap each face in a
|
|
35
35
|
clipping window — a window minus a border crops the initials.
|
|
36
36
|
|
|
37
|
+
## What it owns
|
|
38
|
+
|
|
39
|
+
Nothing. It only draws. Clicks and keys are yours.
|
|
40
|
+
|
|
37
41
|
## Props
|
|
38
42
|
|
|
39
43
|
<Controls of={AvatarGroupStories.ThreeMembers} />
|
package/src/Banner.mdx
CHANGED
|
@@ -12,8 +12,8 @@ line, one of four tones, gone when there is nothing to say.
|
|
|
12
12
|
|
|
13
13
|
## When
|
|
14
14
|
|
|
15
|
-
- The
|
|
16
|
-
|
|
15
|
+
- The state of the whole surface: the reason the last read failed, a read-only
|
|
16
|
+
notice.
|
|
17
17
|
- **`ok`** confirms, **`info`** informs, **`warning`** cautions,
|
|
18
18
|
**`error`** reports a failure — and only `error` announces itself
|
|
19
19
|
(`role="alert"`); the others are polite (`role="status"`).
|
|
@@ -22,7 +22,8 @@ line, one of four tones, gone when there is nothing to say.
|
|
|
22
22
|
|
|
23
23
|
## When not
|
|
24
24
|
|
|
25
|
-
- Feedback about one action, briefly → **Toast**.
|
|
25
|
+
- Feedback about one action, briefly → **Toast**. A failure is the exception:
|
|
26
|
+
it stays, in the Banner, until the person acts on it.
|
|
26
27
|
- Something that floats over the page and must **not** fade → still
|
|
27
28
|
**Toast**, with `durationMs: 0` and an action. A banner is the strip
|
|
28
29
|
under the header at the app's full width; a floating pill is not one, even
|
|
@@ -61,6 +62,15 @@ import { Banner } from '@estiva-app/ui'
|
|
|
61
62
|
is 40px rather than 36px with it, because the button is taller than the
|
|
62
63
|
line of text; without it, nothing about the strip changes.
|
|
63
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
|
+
| Says what it is to assistive technology: an alert for an error, a status for the rest | `role="alert"` or `role="status"` | ✓ |
|
|
72
|
+
| Draws its own ✕ when you pass `onDismiss`, and its one small action when you pass `action` | a close button or an action button of your own | |
|
|
73
|
+
|
|
64
74
|
## Props
|
|
65
75
|
|
|
66
76
|
<Controls of={BannerStories.Ok} />
|
package/src/Breadcrumb.mdx
CHANGED
|
@@ -56,6 +56,16 @@ 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
|
+
## What it owns
|
|
60
|
+
|
|
61
|
+
It does these by itself. Write none of them around it.
|
|
62
|
+
|
|
63
|
+
| It does | So you never write | Refused when written by hand |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| Shortens a trail that does not fit, and shows a cut crumb in full on hover | truncation code, or a tooltip | |
|
|
66
|
+
| Names itself as a navigation, and marks the last crumb as the current page | `<nav>`, `aria-label` or `aria-current` | |
|
|
67
|
+
| Hides the separators from screen readers, so the trail reads as its places | `aria-hidden` on each separator | |
|
|
68
|
+
|
|
59
69
|
## Props
|
|
60
70
|
|
|
61
71
|
<Controls of={BreadcrumbStories.OnAnItem} />
|
package/src/Button.mdx
CHANGED
|
@@ -27,12 +27,13 @@ The push-button for an action: a verb, clicked once — "Create", "Save",
|
|
|
27
27
|
## When not
|
|
28
28
|
|
|
29
29
|
- An icon with no label → **IconButton**.
|
|
30
|
-
- Going somewhere rather than doing something → **Tabs
|
|
31
|
-
or a
|
|
30
|
+
- Going somewhere rather than doing something → **Link**; **Tabs** or
|
|
31
|
+
**Breadcrumb** when it is a view or a trail.
|
|
32
32
|
- Choosing a value → **Select**. Acting from a popover list → **MenuItem**.
|
|
33
33
|
- A control the person may never be allowed to use is **absent, not
|
|
34
34
|
disabled**. `disabled` is for *momentarily* unavailable — and it says why,
|
|
35
|
-
with `disabledReason` ("Available after sign-in").
|
|
35
|
+
with `disabledReason` ("Available after sign-in"). A send button waiting for
|
|
36
|
+
its fields needs no reason.
|
|
36
37
|
|
|
37
38
|
## How
|
|
38
39
|
|
|
@@ -68,6 +69,16 @@ import { IconPlus } from '@tabler/icons-react'
|
|
|
68
69
|
flex and grid, and position; a colour, a text size, a border, a corner or a
|
|
69
70
|
shadow is refused by the lint (`estiva/no-restyled-part`, UIG-9).
|
|
70
71
|
|
|
72
|
+
## What it owns
|
|
73
|
+
|
|
74
|
+
It does these by itself. Write none of them around it.
|
|
75
|
+
|
|
76
|
+
| It does | So you never write | Refused when written by hand |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| Says what it is to assistive technology: a button | `role="button"` | ✓ |
|
|
79
|
+
| Is reachable with Tab, also when it is disabled with a reason | a `tabIndex` | ✓ |
|
|
80
|
+
| Presses on Enter and Space, and does nothing while disabled | a key handler | |
|
|
81
|
+
|
|
71
82
|
## Keys
|
|
72
83
|
|
|
73
84
|
| Key | Does |
|
package/src/Card.mdx
CHANGED
|
@@ -76,6 +76,15 @@ import { Card } from '@estiva-app/ui'
|
|
|
76
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
|
|
77
77
|
your own element inside.
|
|
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
|
+
| With `href`, becomes a link: Link's anchor, with a real address | an anchor around the card | |
|
|
86
|
+
| Without `href`, shows it can be clicked when you pass `onClick` | `cursor-pointer` | |
|
|
87
|
+
|
|
79
88
|
## Props
|
|
80
89
|
|
|
81
90
|
<Controls of={CardStories.Surface} />
|
package/src/Checkbox.mdx
CHANGED
|
@@ -79,6 +79,16 @@ import { Checkbox } from '@estiva-app/ui'
|
|
|
79
79
|
- It does not move when it toggles: the tick is always in the box, hidden
|
|
80
80
|
when unchecked, so both states hang on a line of text the same way.
|
|
81
81
|
|
|
82
|
+
## What it owns
|
|
83
|
+
|
|
84
|
+
It does these by itself. Write none of them around it.
|
|
85
|
+
|
|
86
|
+
| It does | So you never write | Refused when written by hand |
|
|
87
|
+
|---|---|---|
|
|
88
|
+
| Says what it is to assistive technology: a checkbox, ticked or not | `role="checkbox"` or `aria-checked` | ✓ |
|
|
89
|
+
| Toggles on Space and on a click; Enter is left to the form | a click or key handler | |
|
|
90
|
+
| Is one Tab stop, in a `row` too | a `tabIndex` on the row | |
|
|
91
|
+
|
|
82
92
|
## Keys
|
|
83
93
|
|
|
84
94
|
| Key | Does |
|
package/src/Chip.mdx
CHANGED
|
@@ -38,6 +38,10 @@ import { Chip } from '@estiva-app/ui'
|
|
|
38
38
|
it never rides the text baseline.
|
|
39
39
|
- A count alone works too — `label="3"`, no icon.
|
|
40
40
|
|
|
41
|
+
## What it owns
|
|
42
|
+
|
|
43
|
+
Nothing. It only draws. Clicks and keys are yours.
|
|
44
|
+
|
|
41
45
|
## Props
|
|
42
46
|
|
|
43
47
|
<Controls of={ChipStories.Neutral} />
|
package/src/ChipInput.mdx
CHANGED
|
@@ -74,6 +74,18 @@ import { ChipInput } from '@estiva-app/ui'
|
|
|
74
74
|
|
|
75
75
|
<Canvas of={ChipInputStories.ALongLabelCut} />
|
|
76
76
|
|
|
77
|
+
## What it owns
|
|
78
|
+
|
|
79
|
+
It does these by itself. Write none of them around it.
|
|
80
|
+
|
|
81
|
+
| It does | So you never write | Refused when written by hand |
|
|
82
|
+
|---|---|---|
|
|
83
|
+
| Floats on top of the page: its suggestions | a portal, or a `z-index` to win | ✓ |
|
|
84
|
+
| Moves through its items with the arrow keys, through the suggestions | an arrow-key handler | ✓ |
|
|
85
|
+
| Says what it is to assistive technology: a combobox and its options | `role="combobox"`, `listbox` or `option` | ✓ |
|
|
86
|
+
| Filters as you type; Enter adds the highlighted entry, Backspace on an empty query removes the last chip | a filter, or key handlers | |
|
|
87
|
+
| Escape clears the query first, and only then lets the surface around it act | an Escape handler | |
|
|
88
|
+
|
|
77
89
|
## Keys
|
|
78
90
|
|
|
79
91
|
| Input | What happens |
|
|
@@ -60,6 +60,15 @@ import { CollapsibleSection, NavItem } from '@estiva-app/ui'
|
|
|
60
60
|
with `open`.
|
|
61
61
|
- A browser that blocks storage still gets a working section; it forgets.
|
|
62
62
|
|
|
63
|
+
## What it owns
|
|
64
|
+
|
|
65
|
+
It does these by itself. Write none of them around it.
|
|
66
|
+
|
|
67
|
+
| It does | So you never write | Refused when written by hand |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| Opens and closes from its title, on a click, Enter or Space | open state, or a key handler | |
|
|
70
|
+
| Tells assistive technology whether it is open | `aria-expanded` | |
|
|
71
|
+
|
|
63
72
|
## Keys
|
|
64
73
|
|
|
65
74
|
| Key | Does |
|
package/src/CommandPalette.mdx
CHANGED
|
@@ -49,8 +49,8 @@ import { CommandPalette, CommandPaletteSearch } from '@estiva-app/ui'
|
|
|
49
49
|
onQueryChange={setQuery}
|
|
50
50
|
placeholder="Search, or choose a place"
|
|
51
51
|
groups={[
|
|
52
|
-
{ label: 'Recent', rows: [{ id: 'item-one', label: 'Item one', icon: <IconSquareRounded size={16} stroke={1.5} />, onSelect:
|
|
53
|
-
{ label: 'Places', rows: [{ id: 'place-one', label: 'Place one', icon
|
|
52
|
+
{ label: 'Recent', rows: [{ id: 'item-one', label: 'Item one', icon: <IconSquareRounded size={16} stroke={1.5} />, onSelect: openItem, onForget: forget }] },
|
|
53
|
+
{ label: 'Places', rows: [{ id: 'place-one', label: 'Place one', icon: <IconSquareRounded size={16} stroke={1.5} />, onSelect: goIn, onGoIn: goIn }] },
|
|
54
54
|
]}
|
|
55
55
|
/>
|
|
56
56
|
</CommandPalette>
|
|
@@ -102,6 +102,18 @@ import { CommandPalette, CommandPaletteSearch } from '@estiva-app/ui'
|
|
|
102
102
|
- **Focus never falls out of a level.** When the element that had it goes — a
|
|
103
103
|
row picked, a level swapped — focus goes to the level's first control.
|
|
104
104
|
|
|
105
|
+
## What it owns
|
|
106
|
+
|
|
107
|
+
It does these by itself. Write none of them around it.
|
|
108
|
+
|
|
109
|
+
| It does | So you never write | Refused when written by hand |
|
|
110
|
+
|---|---|---|
|
|
111
|
+
| Floats on top of the page | a portal, or a `z-index` to win | ✓ |
|
|
112
|
+
| Holds focus inside while open, and gives it back to whatever opened it | focus code, or a Tab handler | ✓ |
|
|
113
|
+
| Moves through its items with the arrow keys, through the rows, stopping at the ends | an arrow-key handler | ✓ |
|
|
114
|
+
| Says what it is to assistive technology: a list of options you search | `role="combobox"`, `listbox` or `option` | ✓ |
|
|
115
|
+
| Closes on Esc or a click outside; Esc closes an open list first | close handlers | |
|
|
116
|
+
|
|
105
117
|
## Keys
|
|
106
118
|
|
|
107
119
|
| Where | Input | What happens |
|
package/src/ConfirmDialog.mdx
CHANGED
|
@@ -53,6 +53,17 @@ import { ConfirmDialog } from '@estiva-app/ui'
|
|
|
53
53
|
- Focus is trapped in the card and returns to whatever opened it, as for
|
|
54
54
|
every dialog since stage 3.
|
|
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
|
+
| Floats on top of the page | a portal, or a `z-index` to win | ✓ |
|
|
63
|
+
| Says what it is to assistive technology: a dialog that must be answered | `role="alertdialog"` | ✓ |
|
|
64
|
+
| Keeps focus inside while open, as DialogShell does, and gives it back to whatever opened it | focus code | |
|
|
65
|
+
| Ignores a press outside, because the question has to be answered; Escape cancels | a backdrop handler | |
|
|
66
|
+
|
|
56
67
|
## Keys
|
|
57
68
|
|
|
58
69
|
Different from `DialogShell`'s in one row, and it is the row that matters.
|
package/src/ContainerHeader.mdx
CHANGED
|
@@ -51,6 +51,10 @@ import { ContainerHeader, IconButton } from '@estiva-app/ui'
|
|
|
51
51
|
- Only offer a button that does something: a button with nothing behind it is
|
|
52
52
|
a promise the column cannot keep.
|
|
53
53
|
|
|
54
|
+
## What it owns
|
|
55
|
+
|
|
56
|
+
Nothing. It only draws. Clicks and keys are yours.
|
|
57
|
+
|
|
54
58
|
## Props
|
|
55
59
|
|
|
56
60
|
<Controls of={ContainerHeaderStories.Default} />
|
package/src/DialogShell.mdx
CHANGED
|
@@ -28,6 +28,7 @@ footer for the buttons. A dialog is just what goes in the three slots.
|
|
|
28
28
|
dialog rebuilt by hand, which is the one thing this bullet exists to stop.
|
|
29
29
|
Its canvases and its nine tests are on that page.
|
|
30
30
|
- Telling someone what happened → **Toast**.
|
|
31
|
+
- Searching for something and acting on it → **CommandPalette**.
|
|
31
32
|
|
|
32
33
|
## How
|
|
33
34
|
|
|
@@ -73,6 +74,19 @@ import { DialogShell, Button } from '@estiva-app/ui'
|
|
|
73
74
|
stops closing it (Katerina, D20). Escape and the ✕ still do.
|
|
74
75
|
`ConfirmDialog` sets it; a form or a roster should not.
|
|
75
76
|
|
|
77
|
+
## What it owns
|
|
78
|
+
|
|
79
|
+
It does these by itself. Write none of them around it.
|
|
80
|
+
|
|
81
|
+
| It does | So you never write | Refused when written by hand |
|
|
82
|
+
|---|---|---|
|
|
83
|
+
| Floats on top of the page | a portal, or a `z-index` to win | ✓ |
|
|
84
|
+
| Closes on a press outside, except with `alert` | a click listener on the page | ✓ |
|
|
85
|
+
| Takes its keys by itself: Escape closes it | a key listener on the page | ✓ |
|
|
86
|
+
| Holds focus inside while open, and gives it back to whatever opened it | focus code, or a Tab handler | ✓ |
|
|
87
|
+
| Stops the page behind it scrolling | `overflow: hidden` on the page | ✓ |
|
|
88
|
+
| Says what it is to assistive technology: a dialog | `role="dialog"` | ✓ |
|
|
89
|
+
|
|
76
90
|
## Keys
|
|
77
91
|
|
|
78
92
|
| Input | What happens |
|
package/src/Divider.mdx
CHANGED
|
@@ -16,7 +16,7 @@ orientation for assistive tech.
|
|
|
16
16
|
|
|
17
17
|
- Between groups in a **Menu** (`className="my-1"` gives it its breathing
|
|
18
18
|
room there).
|
|
19
|
-
- Between sections of a panel or
|
|
19
|
+
- Between sections of a panel, a rail or a card.
|
|
20
20
|
- Vertical: between clusters in a toolbar or a dense row.
|
|
21
21
|
|
|
22
22
|
<Canvas of={DividerStories.Vertical} />
|
|
@@ -49,6 +49,14 @@ 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
|
+
## What it owns
|
|
53
|
+
|
|
54
|
+
It does these by itself. Write none of them around it.
|
|
55
|
+
|
|
56
|
+
| It does | So you never write | Refused when written by hand |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| Says what it is to assistive technology: a separator | `role="separator"` | ✓ |
|
|
59
|
+
|
|
52
60
|
## Props
|
|
53
61
|
|
|
54
62
|
<Controls of={DividerStories.Default} />
|
package/src/EditableText.mdx
CHANGED
|
@@ -23,6 +23,8 @@ edit is never silently lost.
|
|
|
23
23
|
- A field in a form or dialog → **TextInput** / **Textarea** in a
|
|
24
24
|
**Field**.
|
|
25
25
|
- Composing something new → the field, not an edit-in-place of nothing.
|
|
26
|
+
- An edit that publishes something, like a message → **Textarea** with Save
|
|
27
|
+
and Cancel. EditableText saves when you leave it.
|
|
26
28
|
|
|
27
29
|
## How
|
|
28
30
|
|
|
@@ -55,6 +57,15 @@ import { EditableText } from '@estiva-app/ui'
|
|
|
55
57
|
the page's font). `className` places it — `-mx-2` to line its text up with
|
|
56
58
|
a header's inset — and a size passed there is refused by the lint (UIG-9).
|
|
57
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
|
+
| Opens its field on a click, with the value selected | an edit mode of your own | |
|
|
67
|
+
| Enter and leaving the field save; Escape cancels and puts the value back (`multiline`: Shift+Enter is a new line) | key or blur handlers | |
|
|
68
|
+
|
|
58
69
|
## Keys
|
|
59
70
|
|
|
60
71
|
| Input | What happens |
|