Repository navigation
Expand file tree
/
Copy pathnotes-react-catalog.tsx
More file actions
89 lines (81 loc) · 2.65 KB
/
Copy pathnotes-react-catalog.tsx
File metadata and controls
89 lines (81 loc) · 2.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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
import {
ActionButtonEntry,
Catalog,
ColumnApi,
CommonSchemas,
createComponentImplementation,
ListApi,
RowApi,
z,
type ReactCatalogComponent,
} from '@saga-a2ui/react-mcp-stack';
import type { ReactNode } from 'react';
import { LINE_KINDS } from './notes-line.js';
import { CATALOG_ID } from './notes-surface.js';
// The application-owned catalog of the React consumer. `@a2ui/react` 0.13.0 ships no basic
// catalog, so the layout components are built the way an application builds its own:
// `createComponentImplementation` over the upstream component contracts, the same ones the Lit
// catalog takes from upstream's basic catalog. `Line` has the roles it has in the Lit catalog
// (notes-line.ts) and renders the same elements; `ActionButton` is SAGA's opt-in control. Nothing
// else is in the catalog.
type ChildReference =
string | { readonly id: string; readonly basePath: string };
// The generated view hands a child list over as tokens, or `{ id, basePath }` for a template item.
function childrenOf(
buildChild: (id: string, basePath?: string) => ReactNode,
value: unknown,
): ReactNode[] {
const list = Array.isArray(value) ? (value as ChildReference[]) : [];
return list.map((child) =>
typeof child === 'string'
? buildChild(child)
: buildChild(child.id, child.basePath),
);
}
const LineApi = {
name: 'Line',
schema: z
.object({ text: CommonSchemas.DynamicString, kind: z.enum(LINE_KINDS) })
.strict(),
};
const Line = createComponentImplementation(LineApi, ({ props }) => {
const text = String(props.text ?? '');
switch (props.kind) {
case 'title':
return <h3 data-testid="line-title">{text}</h3>;
case 'body':
return <p data-testid="line-body">{text}</p>;
case 'status':
// Present before it speaks, so a screen reader announces the first message.
return (
<p role="status" data-testid="line-status">
{text}
</p>
);
}
});
const Column = createComponentImplementation(
ColumnApi,
({ props, buildChild }) => (
<div>{childrenOf(buildChild, props.children)}</div>
),
);
const Row = createComponentImplementation(RowApi, ({ props, buildChild }) => (
<div>{childrenOf(buildChild, props.children)}</div>
));
const List = createComponentImplementation(ListApi, ({ props, buildChild }) => (
<div>{childrenOf(buildChild, props.children)}</div>
));
export const notesReactEntries: readonly ReactCatalogComponent[] = [
Column,
Row,
List,
Line,
ActionButtonEntry,
];
/** The catalog the React stack presents the layout over. */
export const notesReactCatalog = new Catalog<ReactCatalogComponent>(
CATALOG_ID,
'v0.9',
[...notesReactEntries],
);