@estiva-app/ui 0.23.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.
- 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/ListColumn.d.ts +41 -0
- package/dist/ListColumn.d.ts.map +1 -0
- 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 +12 -3
- package/dist/Select.d.ts.map +1 -1
- package/dist/Toolbar.d.ts +18 -0
- package/dist/Toolbar.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.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +189 -143
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
- package/registry.json +214 -11
- package/src/AppShell.mdx +18 -1
- package/src/AppShell.test.tsx +16 -0
- package/src/AppShell.tsx +4 -1
- package/src/AttachmentCard.mdx +12 -1
- package/src/AttachmentCard.test.tsx +10 -0
- package/src/AttachmentCard.tsx +8 -5
- package/src/Avatar.mdx +20 -5
- package/src/AvatarGroup.mdx +4 -0
- package/src/Banner.mdx +17 -8
- package/src/Breadcrumb.mdx +12 -0
- package/src/Button.mdx +17 -5
- package/src/Button.tsx +2 -2
- package/src/Card.mdx +14 -1
- package/src/Checkbox.mdx +11 -0
- package/src/Chip.mdx +4 -0
- package/src/ChipInput.mdx +13 -1
- package/src/CollapsibleSection.mdx +12 -3
- package/src/CommandPalette.mdx +14 -2
- package/src/ConfirmDialog.mdx +13 -2
- package/src/ContainerHeader.mdx +15 -2
- package/src/DialogShell.mdx +15 -1
- package/src/Divider.mdx +12 -1
- package/src/EditableText.mdx +17 -1
- package/src/EmptyState.mdx +17 -6
- package/src/Field.mdx +13 -5
- package/src/FieldLine.mdx +9 -1
- package/src/FilePicker.mdx +8 -0
- package/src/Form.mdx +11 -1
- package/src/Form.test.tsx +26 -0
- package/src/Form.tsx +7 -0
- package/src/IconButton.mdx +17 -3
- package/src/IconButton.stories.tsx +3 -0
- package/src/IconButton.test.tsx +25 -0
- package/src/IconButton.tsx +61 -46
- package/src/IdentityMenu.mdx +10 -1
- package/src/InlineChip.mdx +11 -0
- package/src/Kbd.mdx +6 -2
- package/src/Link.mdx +13 -1
- package/src/ListColumn.mdx +87 -0
- package/src/ListColumn.stories.tsx +136 -0
- package/src/ListColumn.test.tsx +50 -0
- package/src/ListColumn.tsx +63 -0
- package/src/Menu.mdx +18 -1
- package/src/MenuItem.mdx +15 -2
- package/src/NavItem.mdx +12 -0
- package/src/Person.mdx +7 -0
- package/src/PersonTrigger.mdx +11 -1
- package/src/PersonTrigger.tsx +1 -1
- package/src/Popover.mdx +23 -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 +12 -1
- package/src/RailItem.mdx +12 -0
- package/src/RailItem.tsx +1 -1
- package/src/Reaction.mdx +10 -0
- package/src/Reaction.tsx +1 -1
- package/src/ReactionPicker.mdx +8 -0
- package/src/ScrollArea.mdx +16 -2
- package/src/SearchInput.mdx +9 -0
- package/src/SectionHeader.mdx +18 -1
- package/src/SectionHeader.stories.tsx +9 -0
- package/src/SectionHeader.test.tsx +9 -0
- package/src/SectionHeader.tsx +9 -3
- package/src/SectionLabel.mdx +10 -2
- package/src/Select.mdx +24 -4
- package/src/Select.stories.tsx +4 -1
- package/src/Select.test.tsx +29 -0
- package/src/Select.tsx +36 -14
- package/src/Sidebar.mdx +8 -0
- package/src/Skeleton.mdx +8 -0
- package/src/Skeleton.tsx +1 -1
- package/src/Tabs.mdx +12 -1
- package/src/TextInput.mdx +9 -0
- package/src/TextInput.tsx +1 -1
- package/src/Textarea.mdx +8 -0
- package/src/Toast.mdx +12 -1
- package/src/Toolbar.mdx +20 -2
- package/src/Toolbar.stories.tsx +19 -2
- package/src/Toolbar.test.tsx +24 -1
- package/src/Toolbar.tsx +22 -0
- package/src/Tooltip.mdx +27 -2
- package/src/Tooltip.stories.tsx +26 -0
- package/src/Tooltip.test.tsx +51 -0
- package/src/Tooltip.tsx +17 -4
- package/src/TopBar.mdx +11 -0
- package/src/eslint/no-rebuilt-behaviour.ts +1 -1
- package/src/eslint/no-restyled-part.ts +1 -0
- package/src/heights.test.tsx +78 -0
- package/src/index.ts +2 -1
- package/src/pages.test.ts +142 -0
- package/src/registry/registry.test.ts +16 -8
- package/tailwind-preset.js +22 -1
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
/**
|
|
3
|
+
* A part with a set height keeps it in a scrolling column (UIG-15; UIG-14 cards
|
|
4
|
+
* T1, M4, M6). A scrolling flex column squashes any child that may shrink:
|
|
5
|
+
* measured in Chrome, a 32px SectionHeader went to 24px, a Skeleton row to 18px,
|
|
6
|
+
* a Button to 18px. jsdom draws nothing, so this pins the class that prevents it;
|
|
7
|
+
* the measurement is in the record.
|
|
8
|
+
*
|
|
9
|
+
* Rows that span their column refuse to shrink (`shrink-0`). Controls, which
|
|
10
|
+
* mostly sit in rows, keep a minimum height instead: `shrink-0` would also stop
|
|
11
|
+
* a row from narrowing them.
|
|
12
|
+
*/
|
|
13
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
14
|
+
import { cleanup, render, screen } from '@testing-library/react'
|
|
15
|
+
import { Button } from './Button'
|
|
16
|
+
import { PersonTrigger } from './PersonTrigger'
|
|
17
|
+
import { RailItem } from './RailItem'
|
|
18
|
+
import { Reaction } from './Reaction'
|
|
19
|
+
import { SectionHeader } from './SectionHeader'
|
|
20
|
+
import { Select } from './Select'
|
|
21
|
+
import { SkeletonRow } from './Skeleton'
|
|
22
|
+
import { TextInput } from './TextInput'
|
|
23
|
+
|
|
24
|
+
afterEach(cleanup)
|
|
25
|
+
|
|
26
|
+
const classes = (el: Element | null) => (el?.getAttribute('class') ?? '').split(' ')
|
|
27
|
+
|
|
28
|
+
describe('a part with a set height keeps it in a scrolling column', () => {
|
|
29
|
+
it('SectionHeader: shrink-0', () => {
|
|
30
|
+
const { container } = render(<SectionHeader title="Group" />)
|
|
31
|
+
expect(classes(container.querySelector('[class*="h-[32px]"]'))).toContain('shrink-0')
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('SkeletonRow: shrink-0', () => {
|
|
35
|
+
const { container } = render(<SkeletonRow />)
|
|
36
|
+
expect(classes(container.firstElementChild)).toContain('shrink-0')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('RailItem: a set 48px, and shrink-0', () => {
|
|
40
|
+
render(<RailItem href="#" label="Item" icon={<span />} />)
|
|
41
|
+
const tile = classes(screen.getByRole('link'))
|
|
42
|
+
expect(tile).toContain('h-12')
|
|
43
|
+
expect(tile).toContain('shrink-0')
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('Button: a minimum height at each size', () => {
|
|
47
|
+
render(
|
|
48
|
+
<>
|
|
49
|
+
<Button>Default</Button>
|
|
50
|
+
<Button size="small">Small</Button>
|
|
51
|
+
</>,
|
|
52
|
+
)
|
|
53
|
+
expect(classes(screen.getByRole('button', { name: 'Default' }))).toContain('min-h-8')
|
|
54
|
+
expect(classes(screen.getByRole('button', { name: 'Small' }))).toContain('min-h-6')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('Select and TextInput, small: a minimum height', () => {
|
|
58
|
+
render(
|
|
59
|
+
<>
|
|
60
|
+
<Select size="small" value="a" onChange={() => {}} options={[{ value: 'a', label: 'A' }]} ariaLabel="Pick" />
|
|
61
|
+
<TextInput size="small" aria-label="Type" />
|
|
62
|
+
</>,
|
|
63
|
+
)
|
|
64
|
+
expect(classes(screen.getByRole('combobox', { name: 'Pick' }))).toContain('min-h-6')
|
|
65
|
+
expect(classes(screen.getByRole('textbox', { name: 'Type' }))).toContain('min-h-6')
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('PersonTrigger and Reaction: a minimum height', () => {
|
|
69
|
+
render(
|
|
70
|
+
<>
|
|
71
|
+
<PersonTrigger name="Ana Duarte" />
|
|
72
|
+
<Reaction emoji="👍" count={2} aria-label="Thumbs up" />
|
|
73
|
+
</>,
|
|
74
|
+
)
|
|
75
|
+
expect(classes(screen.getByRole('button', { name: /Ana Duarte/ }))).toContain('min-h-8')
|
|
76
|
+
expect(classes(screen.getByRole('button', { name: 'Thumbs up' }))).toContain('min-h-6')
|
|
77
|
+
})
|
|
78
|
+
})
|
package/src/index.ts
CHANGED
|
@@ -68,7 +68,7 @@ export { NavItem, type NavItemProps } from './NavItem'
|
|
|
68
68
|
export { Popover, type PopoverProps } from './Popover'
|
|
69
69
|
export { ScrollArea, type ScrollAreaProps } from './ScrollArea'
|
|
70
70
|
export { ReactionPicker, type ReactionOption, type ReactionPickerProps } from './ReactionPicker'
|
|
71
|
-
export { Toolbar, ToolbarButton, ToolbarInput, ToolbarSeparator, type ToolbarProps, type ToolbarButtonProps, type ToolbarInputProps } from './Toolbar'
|
|
71
|
+
export { Toolbar, ToolbarButton, ToolbarInput, ToolbarLink, ToolbarSeparator, type ToolbarProps, type ToolbarButtonProps, type ToolbarInputProps, type ToolbarLinkProps } from './Toolbar'
|
|
72
72
|
export { PreviewCard, type PreviewCardProps } from './PreviewCard'
|
|
73
73
|
export { Person, type PersonProps } from './Person'
|
|
74
74
|
export { Rail, type RailProps } from './Rail'
|
|
@@ -81,6 +81,7 @@ export { Reaction, type ReactionProps } from './Reaction'
|
|
|
81
81
|
export { SearchInput, type SearchInputProps } from './SearchInput'
|
|
82
82
|
export { SectionHeader, type SectionAction, type SectionHeaderProps } from './SectionHeader'
|
|
83
83
|
export { ContainerHeader, type ContainerHeaderProps } from './ContainerHeader'
|
|
84
|
+
export { ListColumn, type ListColumnProps } from './ListColumn'
|
|
84
85
|
export { CollapsibleSection, type CollapsibleSectionProps } from './CollapsibleSection'
|
|
85
86
|
export { SectionLabel } from './SectionLabel'
|
|
86
87
|
export { Tabs, type TabDef, type TabsProps } from './Tabs'
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The contract every component page keeps (UIG-14, with UIG-15 and UIG-16 folded in).
|
|
3
|
+
*
|
|
4
|
+
* A page opens with a line saying what the part is, then has `When`, `When not`,
|
|
5
|
+
* `How` (with code) and `What it owns`, in that order, before `Keys` and `Props`.
|
|
6
|
+
*
|
|
7
|
+
* `What it owns` reads the checker's own list back. A row ticked ✓ in its third
|
|
8
|
+
* column is a behaviour from `OWNED_BEHAVIOURS` that one of the page's parts owns,
|
|
9
|
+
* worded as `ROW` below; every behaviour the page's parts own has one; and a row
|
|
10
|
+
* worded as a behaviour the parts do not own fails. So the page and the checker
|
|
11
|
+
* cannot say different things. A part that owns nothing says so in one line.
|
|
12
|
+
*
|
|
13
|
+
* A `When not` line names only parts that exist: a bold name is a value export of
|
|
14
|
+
* `index.ts`, or a page.
|
|
15
|
+
*/
|
|
16
|
+
import { readdirSync, readFileSync } from 'node:fs'
|
|
17
|
+
import { describe, expect, it } from 'vitest'
|
|
18
|
+
import { OWNED_BEHAVIOURS } from './eslint/no-rebuilt-behaviour'
|
|
19
|
+
|
|
20
|
+
/** How a page words each behaviour. `base-ui` is how a part is built, not what it does for a caller. */
|
|
21
|
+
const ROW: Record<string, RegExp> = {
|
|
22
|
+
portal: /^Floats on top of the page\b/,
|
|
23
|
+
'press-outside': /^Closes on a press outside\b/,
|
|
24
|
+
'page-keys': /^Takes its keys by itself\b/,
|
|
25
|
+
focus: /^Holds focus inside while open\b/,
|
|
26
|
+
'scroll-lock': /^Stops the page behind it scrolling\b/,
|
|
27
|
+
follow: /^Stays attached to its anchor\b/,
|
|
28
|
+
walking: /^Moves through its items with the arrow keys\b/,
|
|
29
|
+
role: /^Says what it is to assistive technology\b/,
|
|
30
|
+
'tab-stop': /^Is reachable with Tab\b/,
|
|
31
|
+
scroll: /^Scrolls with our scrollbar\b/,
|
|
32
|
+
}
|
|
33
|
+
const NOT_A_ROW = ['base-ui']
|
|
34
|
+
|
|
35
|
+
/** A page whose part another ticket is changing, while it is; empty when none is. */
|
|
36
|
+
const WAITING: string[] = []
|
|
37
|
+
|
|
38
|
+
const NOTHING = 'Nothing. It only draws. Clicks and keys are yours.'
|
|
39
|
+
|
|
40
|
+
const dir = new URL('.', import.meta.url)
|
|
41
|
+
const read = (file: string) => readFileSync(new URL(file, dir), 'utf8').replace(/\r\n/g, '\n')
|
|
42
|
+
const pages = readdirSync(dir)
|
|
43
|
+
.filter((f) => f.endsWith('.mdx'))
|
|
44
|
+
.map((f) => f.slice(0, -4))
|
|
45
|
+
|
|
46
|
+
const index = read('index.ts')
|
|
47
|
+
/** Value exports of index.ts, by the module they come from. */
|
|
48
|
+
const byModule = new Map<string, string[]>()
|
|
49
|
+
for (const m of index.matchAll(/export\s*\{([^}]*)\}\s*from\s*'\.\/([\w/]+)'/g)) {
|
|
50
|
+
const names = m[1]
|
|
51
|
+
.split(',')
|
|
52
|
+
.map((s) => s.trim())
|
|
53
|
+
.filter((s) => s && !s.startsWith('type '))
|
|
54
|
+
.map((s) => s.split(/\s+as\s+/).pop()!)
|
|
55
|
+
byModule.set(m[2], [...(byModule.get(m[2]) ?? []), ...names])
|
|
56
|
+
}
|
|
57
|
+
const exported = new Set([...byModule.values()].flat())
|
|
58
|
+
|
|
59
|
+
/** The parts a page documents: its own name, and its module's exports that have no page of their own. */
|
|
60
|
+
const partsOf = (page: string) => [page, ...(byModule.get(page) ?? []).filter((n) => n !== page && !pages.includes(n))]
|
|
61
|
+
|
|
62
|
+
const section = (t: string, name: string) => {
|
|
63
|
+
const head = `\n## ${name}\n`
|
|
64
|
+
const at = t.indexOf(head)
|
|
65
|
+
if (at < 0) return null
|
|
66
|
+
const rest = t.slice(at + head.length)
|
|
67
|
+
const end = rest.search(/^## /m)
|
|
68
|
+
return end < 0 ? rest : rest.slice(0, end)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const owns = (t: string) =>
|
|
72
|
+
(section(t, 'What it owns') ?? '')
|
|
73
|
+
.split('\n')
|
|
74
|
+
.filter((l) => l.startsWith('| ') && !l.startsWith('| It does |'))
|
|
75
|
+
.map((l) => l.split('|').map((c) => c.trim()))
|
|
76
|
+
.map(([, does, never, ticked]) => ({ does, never, ticked: ticked === '✓' }))
|
|
77
|
+
|
|
78
|
+
describe('the page contract', () => {
|
|
79
|
+
it('words every behaviour the checker knows', () => {
|
|
80
|
+
const ids = OWNED_BEHAVIOURS.map((b) => b.id).filter((id) => !NOT_A_ROW.includes(id))
|
|
81
|
+
expect(Object.keys(ROW).sort()).toEqual(ids.sort())
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('finds the pages', () => {
|
|
85
|
+
expect(pages.length).toBeGreaterThanOrEqual(54)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
for (const page of pages) {
|
|
89
|
+
describe(page, () => {
|
|
90
|
+
const t = read(`${page}.mdx`)
|
|
91
|
+
const headings = [...t.matchAll(/^## (.+)$/gm)].map((m) => m[1])
|
|
92
|
+
|
|
93
|
+
it('opens with a line saying what it is', () => {
|
|
94
|
+
const opening = t.split(/^# .*$/m)[1]?.split(/\n\s*\n/).map((s) => s.trim()).find((s) => s && !s.startsWith('<'))
|
|
95
|
+
expect(opening, 'no opening line under the title').toBeTruthy()
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('has each section once', () => {
|
|
99
|
+
expect(headings.filter((h, i) => headings.indexOf(h) !== i)).toEqual([])
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('has its sections in order', () => {
|
|
103
|
+
const want = WAITING.includes(page) ? ['When', 'When not', 'How'] : ['When', 'When not', 'How', 'What it owns']
|
|
104
|
+
const at = want.map((h) => headings.indexOf(h))
|
|
105
|
+
expect(at.filter((i) => i < 0).length, `missing: ${want.filter((_, i) => at[i] < 0).join(', ')}`).toBe(0)
|
|
106
|
+
expect([...at].sort((a, b) => a - b)).toEqual(at)
|
|
107
|
+
for (const after of ['Keys', 'Props']) {
|
|
108
|
+
const i = headings.indexOf(after)
|
|
109
|
+
if (i >= 0) expect(at[at.length - 1], `${want[want.length - 1]} comes before ${after}`).toBeLessThan(i)
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('shows code under How', () => {
|
|
114
|
+
expect(section(t, 'How')).toMatch(/```tsx?\n/)
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('names only parts that exist under When not', () => {
|
|
118
|
+
const names = [...(section(t, 'When not') ?? '').matchAll(/\*\*`?([A-Z][A-Za-z0-9]*)`?\*\*/g)].map((m) => m[1])
|
|
119
|
+
expect(names.filter((n) => !exported.has(n) && !pages.includes(n))).toEqual([])
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
if (WAITING.includes(page)) return
|
|
123
|
+
|
|
124
|
+
it('says what it owns, as the checker does', () => {
|
|
125
|
+
const parts = partsOf(page)
|
|
126
|
+
const owned = OWNED_BEHAVIOURS.filter((b) => !NOT_A_ROW.includes(b.id) && b.owners.some((o) => parts.includes(o))).map((b) => b.id)
|
|
127
|
+
const rows = owns(t)
|
|
128
|
+
const body = (section(t, 'What it owns') ?? '').trim()
|
|
129
|
+
if (!rows.length) {
|
|
130
|
+
expect(owned, `${page} owns ${owned.join(', ')}; the page says nothing`).toEqual([])
|
|
131
|
+
expect(body).toBe(NOTHING)
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
const said = rows.flatMap((r) => Object.entries(ROW).filter(([, re]) => re.test(r.does)).map(([id]) => ({ id, ticked: r.ticked, does: r.does })))
|
|
135
|
+
expect(said.filter((s) => !s.ticked).map((s) => s.does), 'a checker behaviour, not ticked').toEqual([])
|
|
136
|
+
expect(rows.filter((r) => r.ticked && !Object.values(ROW).some((re) => re.test(r.does))).map((r) => r.does), 'ticked, but not worded as a checker behaviour').toEqual([])
|
|
137
|
+
expect(said.map((s) => s.id).sort()).toEqual([...owned].sort())
|
|
138
|
+
expect(rows.every((r) => r.does && r.never), 'every row says what it does and what you never write').toBe(true)
|
|
139
|
+
})
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
})
|
|
@@ -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 84 names over 55 files: 77 components, 6 helpers and 1 hook (ListColumn and ToolbarLink 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: 77, helper: 6, hook: 1 })
|
|
52
|
+
expect(new Set(registry.entries.map((one) => one.sourceFile)).size).toBe(55)
|
|
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(55)
|
|
58
|
+
expect(registry.entries.filter((one) => one.purposeFrom === 'comment').length).toBe(29)
|
|
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 77 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]))
|
|
@@ -321,12 +321,14 @@ describe('a second parser agrees', async () => {
|
|
|
321
321
|
const INHERITED_WITH_A_DEFAULT = new Set(['Button.type', 'IconButton.type', 'TextInput.type', 'SearchInput.placeholder'])
|
|
322
322
|
|
|
323
323
|
/**
|
|
324
|
-
* The
|
|
324
|
+
* The eighteen where **this registry is right and `react-docgen` is not**.
|
|
325
325
|
*
|
|
326
326
|
* `IdentityPanelProps extends Omit<IdentityMenuProps, 'compact'>` and
|
|
327
327
|
* `PersonTriggerProps extends Omit<PersonProps, 'className'>`: docgen does not
|
|
328
328
|
* follow a heritage clause wrapped in `Omit`, so it reports neither `me` nor
|
|
329
|
-
* `signedIn`, which `IdentityPanel` *requires*.
|
|
329
|
+
* `signedIn`, which `IdentityPanel` *requires*. `ToolbarLinkProps extends
|
|
330
|
+
* Omit<IconButtonProps, …>` (UIG-14, F3) is the same: docgen reports none of
|
|
331
|
+
* the IconButton props it keeps. Checked against the source —
|
|
330
332
|
* `IdentityMenuProps` declares both, and `IdentityPanel` spreads `...rest`
|
|
331
333
|
* straight into `IdentityRows`, which destructures them.
|
|
332
334
|
*
|
|
@@ -346,6 +348,12 @@ const DOCGEN_STOPS_AT_OMIT = new Set([
|
|
|
346
348
|
'PersonTrigger.picture',
|
|
347
349
|
'PersonTrigger.fallback',
|
|
348
350
|
'PersonTrigger.size',
|
|
351
|
+
'ToolbarLink.variant',
|
|
352
|
+
'ToolbarLink.glow',
|
|
353
|
+
'ToolbarLink.tooltip',
|
|
354
|
+
'ToolbarLink.tooltipShortcut',
|
|
355
|
+
'ToolbarLink.tooltipPlacement',
|
|
356
|
+
'ToolbarLink.children',
|
|
349
357
|
])
|
|
350
358
|
|
|
351
359
|
describe('ui:find', () => {
|
package/tailwind-preset.js
CHANGED
|
@@ -67,9 +67,30 @@ export default {
|
|
|
67
67
|
// see tokens.css). Both exist so a treatment can be given to the two apps
|
|
68
68
|
// without changing the plain light/dark themes the docs render in.
|
|
69
69
|
plugins: [
|
|
70
|
-
plugin(({ addVariant }) => {
|
|
70
|
+
plugin(({ addVariant, addComponents }) => {
|
|
71
71
|
addVariant('signal', '.signal &')
|
|
72
72
|
addVariant('ship', "[data-theme='ship'] &")
|
|
73
|
+
// The Signal canvas: the control-room dot grid on the app's dark ground,
|
|
74
|
+
// in the Signal theme only. AppShell's floating frame carries
|
|
75
|
+
// `signal-canvas`. Peek's, verbatim (UIG-14, Katerina, 19 September: the
|
|
76
|
+
// canvas is the theme's, so it moved with the frame). A component here,
|
|
77
|
+
// not base.css, so it reaches every app on the preset and the class lint
|
|
78
|
+
// knows the name. `isolation` and `z-index: -1` keep the grid above
|
|
79
|
+
// the ground and below every child: never over the card, a dialog, a menu.
|
|
80
|
+
addComponents({
|
|
81
|
+
'.signal .signal-canvas': { isolation: 'isolate' },
|
|
82
|
+
'.signal .signal-canvas::before': {
|
|
83
|
+
content: "''",
|
|
84
|
+
position: 'absolute',
|
|
85
|
+
inset: '0',
|
|
86
|
+
zIndex: '-1',
|
|
87
|
+
pointerEvents: 'none',
|
|
88
|
+
backgroundImage: 'radial-gradient(circle, rgba(255, 255, 255, 0.045) 1px, transparent 1px)',
|
|
89
|
+
backgroundSize: '26px 26px',
|
|
90
|
+
maskImage: 'radial-gradient(1100px 700px at 50% 45%, transparent 25%, #000 100%)',
|
|
91
|
+
WebkitMaskImage: 'radial-gradient(1100px 700px at 50% 45%, transparent 25%, #000 100%)',
|
|
92
|
+
},
|
|
93
|
+
})
|
|
73
94
|
}),
|
|
74
95
|
],
|
|
75
96
|
theme: {
|