@estiva-app/ui 0.24.1 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ErrorBoundary.d.ts +32 -0
- package/dist/ErrorBoundary.d.ts.map +1 -0
- package/dist/ListColumn.d.ts +9 -8
- package/dist/ListColumn.d.ts.map +1 -1
- package/dist/Menu.d.ts +7 -0
- package/dist/Menu.d.ts.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +142 -94
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
- package/registry.json +66 -19
- package/src/CollapsibleSection.mdx +1 -1
- package/src/Divider.mdx +3 -3
- package/src/ErrorBoundary.mdx +66 -0
- package/src/ErrorBoundary.stories.tsx +64 -0
- package/src/ErrorBoundary.test.tsx +79 -0
- package/src/ErrorBoundary.tsx +54 -0
- package/src/ListColumn.mdx +11 -3
- package/src/ListColumn.stories.tsx +34 -10
- package/src/ListColumn.test.tsx +20 -7
- package/src/ListColumn.tsx +34 -11
- package/src/Menu.mdx +5 -2
- package/src/Menu.stories.tsx +6 -7
- package/src/Menu.test.tsx +19 -1
- package/src/Menu.tsx +11 -1
- package/src/SectionHeader.mdx +5 -1
- package/src/SectionLabel.mdx +4 -2
- package/src/index.ts +2 -1
- package/src/registry/registry.test.ts +6 -6
package/src/Menu.mdx
CHANGED
|
@@ -140,8 +140,11 @@ Four surfaces used to claim menu items of nothing.
|
|
|
140
140
|
|
|
141
141
|
A menu whose rows are `px-3` (tall picker rows) aligns its headings by passing
|
|
142
142
|
`className="px-3"` to `MenuSection` — the heading row is `px-2` by default,
|
|
143
|
-
matching the default rows.
|
|
144
|
-
|
|
143
|
+
matching the default rows.
|
|
144
|
+
|
|
145
|
+
**Between groups, a `MenuSeparator`**: the line, the width of the rows it
|
|
146
|
+
separates, with its own 4px above and below. Not a `Divider`, whose room is
|
|
147
|
+
its caller's.
|
|
145
148
|
|
|
146
149
|
## Props
|
|
147
150
|
|
package/src/Menu.stories.tsx
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
2
|
import { IconCopy, IconDots, IconPencil, IconSquareRounded, IconTrash } from '@tabler/icons-react'
|
|
3
3
|
import { Button } from './Button'
|
|
4
|
-
import { Divider } from './Divider'
|
|
5
4
|
import { IconButton } from './IconButton'
|
|
6
|
-
import { Menu, MenuItem, MenuPanel, MenuRow, MenuSection, MenuSub } from './Menu'
|
|
5
|
+
import { Menu, MenuItem, MenuPanel, MenuRow, MenuSection, MenuSeparator, MenuSub } from './Menu'
|
|
7
6
|
import { SectionLabel } from './SectionLabel'
|
|
8
7
|
|
|
9
8
|
/**
|
|
@@ -41,7 +40,7 @@ export const Items: Story = {
|
|
|
41
40
|
<MenuPanel className="min-w-[180px]">
|
|
42
41
|
<MenuItem label="Rename" leading={icon(IconPencil)} onClick={() => {}} />
|
|
43
42
|
<MenuItem label="Duplicate" leading={icon(IconCopy)} shortcut="Ctrl+D" onClick={() => {}} />
|
|
44
|
-
<
|
|
43
|
+
<MenuSeparator />
|
|
45
44
|
<MenuItem label="Delete" destructive leading={<IconTrash size={16} stroke={1.5} className="text-error-default" />} onClick={() => {}} />
|
|
46
45
|
</MenuPanel>
|
|
47
46
|
),
|
|
@@ -57,7 +56,7 @@ export const Sections: Story = {
|
|
|
57
56
|
<MenuItem label="Item one" selected onClick={() => {}} />
|
|
58
57
|
<MenuItem label="Item two" onClick={() => {}} />
|
|
59
58
|
</MenuSection>
|
|
60
|
-
<
|
|
59
|
+
<MenuSeparator />
|
|
61
60
|
<MenuSection label="Another section">
|
|
62
61
|
<MenuItem label="Option one" onClick={() => {}} />
|
|
63
62
|
<MenuItem label="Option two" onClick={() => {}} />
|
|
@@ -75,7 +74,7 @@ export const WithARow: Story = {
|
|
|
75
74
|
<span className="min-w-0 flex-1 truncate text-body-2 text-text-primary">Item one</span>
|
|
76
75
|
<SectionLabel tone="secondary">Label</SectionLabel>
|
|
77
76
|
</MenuRow>
|
|
78
|
-
<
|
|
77
|
+
<MenuSeparator />
|
|
79
78
|
<MenuItem label="An action" onClick={() => {}} />
|
|
80
79
|
</MenuPanel>
|
|
81
80
|
),
|
|
@@ -99,7 +98,7 @@ export const FromATrigger: Story = {
|
|
|
99
98
|
<MenuItem label="Item two" onClick={() => {}} />
|
|
100
99
|
<MenuItem label="Item three" onClick={() => {}} />
|
|
101
100
|
</MenuSub>
|
|
102
|
-
<
|
|
101
|
+
<MenuSeparator />
|
|
103
102
|
<MenuItem label="Delete" destructive leading={<IconTrash size={16} stroke={1.5} className="text-error-default" />} onClick={() => {}} />
|
|
104
103
|
</Menu>
|
|
105
104
|
),
|
|
@@ -149,7 +148,7 @@ export const OnACard: Story = {
|
|
|
149
148
|
<MenuItem label="Item two" onClick={() => {}} />
|
|
150
149
|
</MenuSub>
|
|
151
150
|
</MenuSection>
|
|
152
|
-
<
|
|
151
|
+
<MenuSeparator />
|
|
153
152
|
<MenuItem label="Delete" destructive leading={<IconTrash size={16} stroke={1.5} className="text-error-default" />} onClick={() => {}} />
|
|
154
153
|
</Menu>
|
|
155
154
|
</div>
|
package/src/Menu.test.tsx
CHANGED
|
@@ -11,7 +11,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
|
11
11
|
import { cleanup, render, screen, waitFor } from '@testing-library/react'
|
|
12
12
|
import userEvent from '@testing-library/user-event'
|
|
13
13
|
import { Button } from './Button'
|
|
14
|
-
import { Menu, MenuItem, MenuPanel, MenuSection, MenuSub } from './Menu'
|
|
14
|
+
import { Menu, MenuItem, MenuPanel, MenuSection, MenuSeparator, MenuSub } from './Menu'
|
|
15
15
|
|
|
16
16
|
afterEach(cleanup)
|
|
17
17
|
|
|
@@ -269,6 +269,24 @@ describe('Menu', () => {
|
|
|
269
269
|
* plugin already owns the keyboard — so `MenuItem` and `MenuSection` have to
|
|
270
270
|
* work with no `Menu` above them.
|
|
271
271
|
*/
|
|
272
|
+
describe('MenuSeparator', () => {
|
|
273
|
+
// UIG-14: the line between groups brings its own 4px above and below; a plain
|
|
274
|
+
// Divider left that to each caller, and Peek's main menus had none.
|
|
275
|
+
it('is a separator with its own room, the width of the rows', async () => {
|
|
276
|
+
render(
|
|
277
|
+
<Menu open trigger={<Button>More</Button>}>
|
|
278
|
+
<MenuItem label="One" />
|
|
279
|
+
<MenuSeparator />
|
|
280
|
+
<MenuItem label="Two" />
|
|
281
|
+
</Menu>,
|
|
282
|
+
)
|
|
283
|
+
const line = await screen.findByRole('separator')
|
|
284
|
+
const classes = (line.getAttribute('class') ?? '').split(' ')
|
|
285
|
+
expect(classes).toContain('my-1')
|
|
286
|
+
expect(classes).toContain('mx-0')
|
|
287
|
+
})
|
|
288
|
+
})
|
|
289
|
+
|
|
272
290
|
describe('rows on a bare MenuPanel', () => {
|
|
273
291
|
/**
|
|
274
292
|
* It is a button, and it does NOT claim to be a menu item. ARIA requires a
|
package/src/Menu.tsx
CHANGED
|
@@ -2,7 +2,7 @@ import { createContext, useContext, type ComponentPropsWithRef, type ReactElemen
|
|
|
2
2
|
import { IconChevronRight } from '@tabler/icons-react'
|
|
3
3
|
import { Menu as BaseMenu } from '@base-ui/react/menu'
|
|
4
4
|
import { cn } from './cn'
|
|
5
|
-
import { DividerInPanel } from './Divider'
|
|
5
|
+
import { Divider, DividerInPanel } from './Divider'
|
|
6
6
|
import { triggerDisabled } from './triggerDisabled'
|
|
7
7
|
import { ScrollArea } from './ScrollArea'
|
|
8
8
|
import { Kbd } from './Kbd'
|
|
@@ -590,6 +590,16 @@ export function MenuSection({ label, children, className }: { label: string; chi
|
|
|
590
590
|
)
|
|
591
591
|
}
|
|
592
592
|
|
|
593
|
+
/**
|
|
594
|
+
* The line between two groups of a menu, with its own 4px above and below
|
|
595
|
+
* (UIG-14, Katerina 19 September). A plain `Divider` left that room to each
|
|
596
|
+
* caller, and Peek's menus drew it three ways — none, 4px, 4px and inset — so
|
|
597
|
+
* the lines in its main menus touched the rows.
|
|
598
|
+
*/
|
|
599
|
+
export function MenuSeparator() {
|
|
600
|
+
return <Divider className="my-1" />
|
|
601
|
+
}
|
|
602
|
+
|
|
593
603
|
/** A non-interactive row inside a menu, at the item's own geometry. */
|
|
594
604
|
export function MenuRow({ children, className }: { children: ReactNode; className?: string }) {
|
|
595
605
|
return <div className={cn('flex items-center gap-2 px-2 py-1.5', className)}>{children}</div>
|
package/src/SectionHeader.mdx
CHANGED
|
@@ -16,6 +16,9 @@ title the toggle — that is where a section folds.
|
|
|
16
16
|
|
|
17
17
|
- A sidebar or panel section that carries its own actions — add, sort,
|
|
18
18
|
filter.
|
|
19
|
+
- **A heading over a group of rows, even with nothing to act on**: no
|
|
20
|
+
`actions`, and `hover="none"` so the row stays still under the pointer.
|
|
21
|
+
Never a row drawn by hand with a SectionLabel in it.
|
|
19
22
|
- `showActions="always"` keeps the actions visible — for touch, or for a
|
|
20
23
|
section whose actions are the point.
|
|
21
24
|
|
|
@@ -25,7 +28,8 @@ title the toggle — that is where a section folds.
|
|
|
25
28
|
|
|
26
29
|
- A section that opens and closes → **CollapsibleSection**: the header,
|
|
27
30
|
the rows, the slide, and a `storageKey` that remembers.
|
|
28
|
-
- A
|
|
31
|
+
- A label inside a row of another shape — a dialog's section, a panel's
|
|
32
|
+
title, a card's line → **SectionLabel**.
|
|
29
33
|
- A heading inside a menu → **MenuSection**.
|
|
30
34
|
- Page-level headings → the `h*` type tokens.
|
|
31
35
|
|
package/src/SectionLabel.mdx
CHANGED
|
@@ -15,13 +15,15 @@ how).
|
|
|
15
15
|
## When
|
|
16
16
|
|
|
17
17
|
- **It is a bare label**: the row it sits in, and the room around it, are yours.
|
|
18
|
-
-
|
|
18
|
+
- A label inside a row of another shape: a dialog's section, a panel's
|
|
19
|
+
title, a card's line.
|
|
19
20
|
- Inside a menu it arrives via **MenuSection** — don't place it there by
|
|
20
21
|
hand.
|
|
21
22
|
|
|
22
23
|
## When not
|
|
23
24
|
|
|
24
|
-
- A heading
|
|
25
|
+
- A heading over a group of rows → **SectionHeader**, even with nothing to
|
|
26
|
+
act on; a group that folds → **CollapsibleSection**.
|
|
25
27
|
- The label above a stacked control → **Property** `stacked` — a field
|
|
26
28
|
label is a different voice, at a different size, on purpose.
|
|
27
29
|
- Page-level headings → the `h*` type tokens.
|
package/src/index.ts
CHANGED
|
@@ -60,10 +60,11 @@ export { DialogShell, type DialogShellProps } from './DialogShell'
|
|
|
60
60
|
export { Divider, type DividerProps } from './Divider'
|
|
61
61
|
export { EditableText, type EditableTextProps } from './EditableText'
|
|
62
62
|
export { EmptyState, type EmptyStateProps } from './EmptyState'
|
|
63
|
+
export { ErrorBoundary, type ErrorBoundaryProps } from './ErrorBoundary'
|
|
63
64
|
export { ProgressBar, type ProgressBarProps, type ProgressBarVariant } from './ProgressBar'
|
|
64
65
|
export { SkeletonBar, SkeletonList, SkeletonRow } from './Skeleton'
|
|
65
66
|
export { Breadcrumb, type BreadcrumbProps, type Crumb } from './Breadcrumb'
|
|
66
|
-
export { EnterHint, Menu, MenuItem, MenuPanel, MenuRow, MenuSection, MenuSub, type MenuItemProps, type MenuPanelProps, type MenuProps, type MenuSubProps } from './Menu'
|
|
67
|
+
export { EnterHint, Menu, MenuItem, MenuPanel, MenuRow, MenuSection, MenuSeparator, MenuSub, type MenuItemProps, type MenuPanelProps, type MenuProps, type MenuSubProps } from './Menu'
|
|
67
68
|
export { NavItem, type NavItemProps } from './NavItem'
|
|
68
69
|
export { Popover, type PopoverProps } from './Popover'
|
|
69
70
|
export { ScrollArea, type ScrollAreaProps } from './ScrollArea'
|
|
@@ -45,17 +45,17 @@ describe('the registry builds', () => {
|
|
|
45
45
|
expect(registry.entries.map((one) => one.name).sort()).toEqual(values.map((one) => one.name).sort())
|
|
46
46
|
})
|
|
47
47
|
|
|
48
|
-
it('counts
|
|
48
|
+
it('counts 86 names over 56 files: 79 components, 6 helpers and 1 hook (ErrorBoundary and MenuSeparator joined in UIG-14)', () => {
|
|
49
49
|
// The reconciliation GATES.md §24 explains. If this changes, that changes.
|
|
50
50
|
const kinds = registry.entries.reduce<Record<string, number>>((all, one) => ({ ...all, [one.kind]: (all[one.kind] ?? 0) + 1 }), {})
|
|
51
|
-
expect(kinds).toEqual({ component:
|
|
52
|
-
expect(new Set(registry.entries.map((one) => one.sourceFile)).size).toBe(
|
|
51
|
+
expect(kinds).toEqual({ component: 79, helper: 6, hook: 1 })
|
|
52
|
+
expect(new Set(registry.entries.map((one) => one.sourceFile)).size).toBe(56)
|
|
53
53
|
})
|
|
54
54
|
|
|
55
55
|
it('gives every entry a purpose, from its own page or from the comment above it', () => {
|
|
56
56
|
expect(registry.entries.filter((one) => one.purpose.trim() === '')).toEqual([])
|
|
57
|
-
expect(registry.entries.filter((one) => one.purposeFrom === 'page').length).toBe(
|
|
58
|
-
expect(registry.entries.filter((one) => one.purposeFrom === 'comment').length).toBe(
|
|
57
|
+
expect(registry.entries.filter((one) => one.purposeFrom === 'page').length).toBe(56)
|
|
58
|
+
expect(registry.entries.filter((one) => one.purposeFrom === 'comment').length).toBe(30)
|
|
59
59
|
})
|
|
60
60
|
|
|
61
61
|
it('takes a name with a page of its own from the page, and a name without one from the code', () => {
|
|
@@ -278,7 +278,7 @@ describe('the ids point at something', () => {
|
|
|
278
278
|
describe('a second parser agrees', async () => {
|
|
279
279
|
const docgen = await import('react-docgen').catch(() => null)
|
|
280
280
|
|
|
281
|
-
it.skipIf(!docgen)('finds the same props on all
|
|
281
|
+
it.skipIf(!docgen)('finds the same props on all 79 components', async () => {
|
|
282
282
|
if (!docgen) return
|
|
283
283
|
const resolver = new docgen.builtinResolvers.FindExportedDefinitionsResolver({ limit: 0 })
|
|
284
284
|
const byName = new Map(registry.entries.map((one) => [one.name, one]))
|