@estiva-app/ui 0.42.0 → 0.43.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/CommandPalette.d.ts +16 -0
- package/dist/CommandPalette.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -0
- package/dist/index.js.map +2 -2
- package/package.json +1 -1
- package/registry.json +41 -3
- package/src/CommandPalette.mdx +6 -0
- package/src/CommandPalette.stories.tsx +26 -1
- package/src/CommandPalette.test.tsx +14 -0
- package/src/CommandPalette.tsx +29 -0
- package/src/index.ts +2 -0
- package/src/registry/registry.test.ts +3 -3
package/package.json
CHANGED
package/registry.json
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
"kind": "package",
|
|
5
5
|
"repo": "estiva-ui",
|
|
6
6
|
"package": "@estiva-app/ui",
|
|
7
|
-
"packageVersion": "0.
|
|
8
|
-
"exports":
|
|
9
|
-
"typeExports":
|
|
7
|
+
"packageVersion": "0.43.0",
|
|
8
|
+
"exports": 91,
|
|
9
|
+
"typeExports": 102,
|
|
10
10
|
"files": null
|
|
11
11
|
},
|
|
12
12
|
"storybook": {
|
|
@@ -1280,6 +1280,44 @@
|
|
|
1280
1280
|
"app": null,
|
|
1281
1281
|
"looks": []
|
|
1282
1282
|
},
|
|
1283
|
+
{
|
|
1284
|
+
"name": "CommandPaletteProposal",
|
|
1285
|
+
"repo": "estiva-ui",
|
|
1286
|
+
"kind": "component",
|
|
1287
|
+
"importPath": "@estiva-app/ui",
|
|
1288
|
+
"sourceFile": "src/CommandPalette.tsx",
|
|
1289
|
+
"purpose": "Something the palette offers to make, before anything is made: a card with where it would be made on top, then what it would be.",
|
|
1290
|
+
"purposeFrom": "comment",
|
|
1291
|
+
"props": [
|
|
1292
|
+
{
|
|
1293
|
+
"name": "source",
|
|
1294
|
+
"takes": "text",
|
|
1295
|
+
"required": true,
|
|
1296
|
+
"note": "Where it would be made: the app and the action — \"App › New item\"."
|
|
1297
|
+
},
|
|
1298
|
+
{
|
|
1299
|
+
"name": "mark",
|
|
1300
|
+
"takes": "anything",
|
|
1301
|
+
"required": false,
|
|
1302
|
+
"note": "The app's mark before the source, as small as the line (about 10px)."
|
|
1303
|
+
},
|
|
1304
|
+
{
|
|
1305
|
+
"name": "children",
|
|
1306
|
+
"takes": "text",
|
|
1307
|
+
"required": true,
|
|
1308
|
+
"note": "What would be made, in a sentence."
|
|
1309
|
+
}
|
|
1310
|
+
],
|
|
1311
|
+
"variants": [],
|
|
1312
|
+
"ownsBehaviours": [],
|
|
1313
|
+
"status": "stable",
|
|
1314
|
+
"migrationStage": null,
|
|
1315
|
+
"docsId": "overlays-commandpalette--docs",
|
|
1316
|
+
"storyId": "overlays-commandpalette--first-level",
|
|
1317
|
+
"docPage": null,
|
|
1318
|
+
"app": null,
|
|
1319
|
+
"looks": []
|
|
1320
|
+
},
|
|
1283
1321
|
{
|
|
1284
1322
|
"name": "CommandPaletteQuote",
|
|
1285
1323
|
"repo": "estiva-ui",
|
package/src/CommandPalette.mdx
CHANGED
|
@@ -23,6 +23,12 @@ form — and the app keeps the levels.
|
|
|
23
23
|
above the rows, for a result that is written rather than listed: a line while
|
|
24
24
|
it is on its way, an answer with marks pointing at the rows it came from, and
|
|
25
25
|
text written to be used somewhere else.
|
|
26
|
+
- **`CommandPaletteProposal`** — above the rows, for something the palette
|
|
27
|
+
offers to make before anything is made: a card with where it would be made
|
|
28
|
+
(`source`, with the app's `mark`), then what it would be. The rows under it
|
|
29
|
+
say what happens next.
|
|
30
|
+
|
|
31
|
+
<Canvas of={CommandPaletteStories.Proposal} />
|
|
26
32
|
|
|
27
33
|
<Canvas of={CommandPaletteStories.Answer} />
|
|
28
34
|
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
CommandPalette,
|
|
8
8
|
CommandPaletteAnswer,
|
|
9
9
|
CommandPaletteForm,
|
|
10
|
+
CommandPaletteProposal,
|
|
10
11
|
CommandPaletteQuote,
|
|
11
12
|
CommandPaletteSearch,
|
|
12
13
|
CommandPaletteWorking,
|
|
@@ -54,6 +55,7 @@ type Level =
|
|
|
54
55
|
| { kind: 'place'; name: string }
|
|
55
56
|
| { kind: 'ask' }
|
|
56
57
|
| { kind: 'draft' }
|
|
58
|
+
| { kind: 'make' }
|
|
57
59
|
| { kind: 'form' }
|
|
58
60
|
| { kind: 'one-list' }
|
|
59
61
|
|
|
@@ -62,7 +64,7 @@ interface Frame {
|
|
|
62
64
|
query: string
|
|
63
65
|
}
|
|
64
66
|
|
|
65
|
-
type Start = 'first' | 'arriving' | 'nothing' | 'place' | 'working' | 'answer' | 'draft' | 'form' | 'refused' | 'one-list'
|
|
67
|
+
type Start = 'first' | 'arriving' | 'nothing' | 'place' | 'working' | 'answer' | 'draft' | 'make' | 'form' | 'refused' | 'one-list'
|
|
66
68
|
|
|
67
69
|
const PLACES = ['Place one', 'Place two']
|
|
68
70
|
const ACTIONS = [
|
|
@@ -81,6 +83,7 @@ const GROUPS = [
|
|
|
81
83
|
]
|
|
82
84
|
const ANSWER = 'Item one moved on Monday [1], and item two is waiting on a reply [2].\n\nNothing else changed this week.'
|
|
83
85
|
const DRAFT = 'Thanks, both. I will look at item one today\nand reply on item two tomorrow.'
|
|
86
|
+
const PROPOSAL = 'An item for the reply on item two, due tomorrow.'
|
|
84
87
|
|
|
85
88
|
function startStack(start: Start): Frame[] {
|
|
86
89
|
const first: Frame = { level: { kind: 'first' }, query: '' }
|
|
@@ -96,6 +99,8 @@ function startStack(start: Start): Frame[] {
|
|
|
96
99
|
return [first, { level: { kind: 'ask' }, query: 'What changed this week?' }]
|
|
97
100
|
case 'draft':
|
|
98
101
|
return [first, { level: { kind: 'draft' }, query: 'Say I will look today' }]
|
|
102
|
+
case 'make':
|
|
103
|
+
return [first, { level: { kind: 'make' }, query: '' }]
|
|
99
104
|
case 'form':
|
|
100
105
|
case 'refused':
|
|
101
106
|
return [first, { level: { kind: 'place', name: 'Place one' }, query: '' }, { level: { kind: 'form' }, query: '' }]
|
|
@@ -180,6 +185,17 @@ function Demo({ start, where, modKey, label }: { start: Start; where?: string; m
|
|
|
180
185
|
},
|
|
181
186
|
]
|
|
182
187
|
}
|
|
188
|
+
if (level.kind === 'make') {
|
|
189
|
+
return [
|
|
190
|
+
{
|
|
191
|
+
label: 'Next',
|
|
192
|
+
rows: [
|
|
193
|
+
{ id: 'make-open', label: 'Open the form', description: 'Place one › Action one · nothing is made until you submit', icon, onSelect: () => push({ kind: 'form' }) },
|
|
194
|
+
{ id: 'make-again', label: 'Suggest something else', icon, onSelect: () => close('Suggest something else') },
|
|
195
|
+
],
|
|
196
|
+
},
|
|
197
|
+
]
|
|
198
|
+
}
|
|
183
199
|
if (level.kind === 'ask' || level.kind === 'draft') {
|
|
184
200
|
if (!asked?.done) return []
|
|
185
201
|
const changed = frame.query.trim() !== asked.question.trim()
|
|
@@ -236,6 +252,7 @@ function Demo({ start, where, modKey, label }: { start: Start; where?: string; m
|
|
|
236
252
|
rows: [
|
|
237
253
|
{ id: 'ask', label: 'Ask about this page', icon, onSelect: () => push({ kind: 'ask' }) },
|
|
238
254
|
{ id: 'draft', label: 'Write a draft', icon, onSelect: () => push({ kind: 'draft' }) },
|
|
255
|
+
{ id: 'make', label: 'Make something from this page', icon, onSelect: () => push({ kind: 'make' }) },
|
|
239
256
|
{ id: 'one-list', label: 'Change kind', description: 'Item one', icon, onSelect: () => push({ kind: 'one-list' }) },
|
|
240
257
|
],
|
|
241
258
|
},
|
|
@@ -289,6 +306,7 @@ function Demo({ start, where, modKey, label }: { start: Start; where?: string; m
|
|
|
289
306
|
}
|
|
290
307
|
|
|
291
308
|
const body = (() => {
|
|
309
|
+
if (level.kind === 'make') return <CommandPaletteProposal source="Place one › Action one" mark={<IconSquareRounded size={10} stroke={1.5} />}>{PROPOSAL}</CommandPaletteProposal>
|
|
292
310
|
if (level.kind !== 'ask' && level.kind !== 'draft') return undefined
|
|
293
311
|
if (!asked || !asked.done) return <CommandPaletteWorking>Reading 12 notes…</CommandPaletteWorking>
|
|
294
312
|
const changed = frame.query.trim() !== asked.question.trim()
|
|
@@ -355,6 +373,8 @@ function Demo({ start, where, modKey, label }: { start: Start; where?: string; m
|
|
|
355
373
|
? 'Ask anything about this page'
|
|
356
374
|
: level.kind === 'draft'
|
|
357
375
|
? 'What should it say?'
|
|
376
|
+
: level.kind === 'make'
|
|
377
|
+
? 'Looking at this page'
|
|
358
378
|
: 'Search, or choose a place'
|
|
359
379
|
}
|
|
360
380
|
groups={groups}
|
|
@@ -365,6 +385,8 @@ function Demo({ start, where, modKey, label }: { start: Start; where?: string; m
|
|
|
365
385
|
? { label: 'Ask', onBack: back }
|
|
366
386
|
: level.kind === 'draft'
|
|
367
387
|
? { label: 'Draft', onBack: back }
|
|
388
|
+
: level.kind === 'make'
|
|
389
|
+
? { label: 'Make', onBack: back }
|
|
368
390
|
: undefined
|
|
369
391
|
}
|
|
370
392
|
pending={level.kind === 'first' && searching ? 'Searching…' : undefined}
|
|
@@ -412,6 +434,9 @@ export const Answer: Story = story('answer')
|
|
|
412
434
|
export const Quote: Story = story('draft')
|
|
413
435
|
|
|
414
436
|
/** A form. Ctrl+Enter creates; a missing field says so and takes focus; Backspace in an empty field goes back and keeps what was typed. */
|
|
437
|
+
/** Something it offers to make: where, then what. Nothing is made until the form is sent. */
|
|
438
|
+
export const Proposal: Story = story('make')
|
|
439
|
+
|
|
415
440
|
export const Form: Story = story('form')
|
|
416
441
|
|
|
417
442
|
/** Creating is refused: the fields unlock, the reason sits beside the button, and focus is back where it was. */
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
CommandPalette,
|
|
18
18
|
CommandPaletteAnswer,
|
|
19
19
|
CommandPaletteForm,
|
|
20
|
+
CommandPaletteProposal,
|
|
20
21
|
CommandPaletteQuote,
|
|
21
22
|
CommandPaletteSearch,
|
|
22
23
|
CommandPaletteWorking,
|
|
@@ -421,6 +422,19 @@ describe('what sits above the rows', () => {
|
|
|
421
422
|
expect(screen.getByText(/Line one/).textContent).toBe('Line one\nLine two')
|
|
422
423
|
})
|
|
423
424
|
|
|
425
|
+
it('draws a proposal as a card: where it would be made, with its mark, then what', () => {
|
|
426
|
+
const { container } = render(
|
|
427
|
+
<CommandPaletteProposal source="Place one › Action one" mark={<svg data-testid="mark" />}>
|
|
428
|
+
An item for tomorrow.
|
|
429
|
+
</CommandPaletteProposal>,
|
|
430
|
+
)
|
|
431
|
+
const lines = container.firstElementChild?.children
|
|
432
|
+
expect(lines).toHaveLength(2)
|
|
433
|
+
expect(lines?.[0].textContent).toBe('Place one › Action one')
|
|
434
|
+
expect(lines?.[0].contains(screen.getByTestId('mark'))).toBe(true)
|
|
435
|
+
expect(lines?.[1].textContent).toBe('An item for tomorrow.')
|
|
436
|
+
})
|
|
437
|
+
|
|
424
438
|
it('announces what is being worked on, with its grey bars', () => {
|
|
425
439
|
const { container } = render(<CommandPaletteWorking bars={3}>Reading…</CommandPaletteWorking>)
|
|
426
440
|
expect(screen.getByRole('status').textContent).toContain('Reading…')
|
package/src/CommandPalette.tsx
CHANGED
|
@@ -16,6 +16,7 @@ import type { BaseUIEvent } from '@base-ui/react/types'
|
|
|
16
16
|
import { IconLoader2, IconSearch } from '@tabler/icons-react'
|
|
17
17
|
import { cn } from './cn'
|
|
18
18
|
import { Button } from './Button'
|
|
19
|
+
import { Card } from './Card'
|
|
19
20
|
import { InputChip } from './ChipInput'
|
|
20
21
|
import { EmptyState } from './EmptyState'
|
|
21
22
|
import { FieldLine } from './Field'
|
|
@@ -661,3 +662,31 @@ export interface CommandPaletteQuoteProps {
|
|
|
661
662
|
export function CommandPaletteQuote({ children }: CommandPaletteQuoteProps) {
|
|
662
663
|
return <p className="whitespace-pre-wrap border-l-2 border-border-default pl-3 text-body-2 text-text-primary">{children}</p>
|
|
663
664
|
}
|
|
665
|
+
|
|
666
|
+
export interface CommandPaletteProposalProps {
|
|
667
|
+
/** Where it would be made: the app and the action — "App › New item". */
|
|
668
|
+
source: string
|
|
669
|
+
/** The app's mark before the source, as small as the line (about 10px). */
|
|
670
|
+
mark?: ReactNode
|
|
671
|
+
/** What would be made, in a sentence. */
|
|
672
|
+
children: string
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Something the palette offers to make, before anything is made: a card with
|
|
677
|
+
* where it would be made on top, then what it would be. A card because it is a
|
|
678
|
+
* thing about to exist, not text to read or use; the source first because two
|
|
679
|
+
* apps may offer something alike (UIG-40, Katerina 28 September: the look Peek's
|
|
680
|
+
* launcher already had, now drawn by the palette, ruling P4).
|
|
681
|
+
*/
|
|
682
|
+
export function CommandPaletteProposal({ source, mark, children }: CommandPaletteProposalProps) {
|
|
683
|
+
return (
|
|
684
|
+
<Card fill="surface" className="flex flex-col gap-1 p-3">
|
|
685
|
+
<span className="flex items-center gap-2 text-caption text-text-secondary">
|
|
686
|
+
{mark}
|
|
687
|
+
{source}
|
|
688
|
+
</span>
|
|
689
|
+
<span className="text-body-2 text-text-primary">{children}</span>
|
|
690
|
+
</Card>
|
|
691
|
+
)
|
|
692
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -22,6 +22,7 @@ export {
|
|
|
22
22
|
CommandPalette,
|
|
23
23
|
CommandPaletteAnswer,
|
|
24
24
|
CommandPaletteForm,
|
|
25
|
+
CommandPaletteProposal,
|
|
25
26
|
CommandPaletteQuote,
|
|
26
27
|
CommandPaletteSearch,
|
|
27
28
|
CommandPaletteWorking,
|
|
@@ -30,6 +31,7 @@ export {
|
|
|
30
31
|
type CommandPaletteFormProps,
|
|
31
32
|
type CommandPaletteGroup,
|
|
32
33
|
type CommandPaletteProps,
|
|
34
|
+
type CommandPaletteProposalProps,
|
|
33
35
|
type CommandPaletteQuoteProps,
|
|
34
36
|
type CommandPaletteRow,
|
|
35
37
|
type CommandPaletteSearchProps,
|
|
@@ -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 91 names over 59 files: 83 components, 7 helpers and 1 hook (CommandPaletteProposal joined on 28 September)', () => {
|
|
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:
|
|
51
|
+
expect(kinds).toEqual({ component: 83, helper: 7, hook: 1 })
|
|
52
52
|
expect(new Set(registry.entries.map((one) => one.sourceFile)).size).toBe(59)
|
|
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
57
|
expect(registry.entries.filter((one) => one.purposeFrom === 'page').length).toBe(59)
|
|
58
|
-
expect(registry.entries.filter((one) => one.purposeFrom === 'comment').length).toBe(
|
|
58
|
+
expect(registry.entries.filter((one) => one.purposeFrom === 'comment').length).toBe(32)
|
|
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', () => {
|