@cratis/pi 0.0.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/README.md +43 -0
- package/package.json +24 -0
- package/skills/cratis-application-react-specifications/LICENSE +2 -0
- package/skills/cratis-application-react-specifications/SKILL.md +192 -0
- package/skills/cratis-application-slice-specifications/LICENSE +2 -0
- package/skills/cratis-application-slice-specifications/SKILL.md +222 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/LICENSE +2 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/SKILL.md +233 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/references/authentication.md +140 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/references/authorization.md +112 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/references/frontend.md +102 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/references/local-development.md +85 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/references/tenancy.md +87 -0
- package/skills/cratis-arc-command/LICENSE +2 -0
- package/skills/cratis-arc-command/SKILL.md +286 -0
- package/skills/cratis-arc-command/references/command-result.md +111 -0
- package/skills/cratis-arc-command/references/handler-shapes.md +135 -0
- package/skills/cratis-arc-command/references/proxy-generation.md +87 -0
- package/skills/cratis-arc-command/references/read-model-injection.md +97 -0
- package/skills/cratis-arc-command-execution/LICENSE +2 -0
- package/skills/cratis-arc-command-execution/SKILL.md +148 -0
- package/skills/cratis-arc-command-validation/LICENSE +2 -0
- package/skills/cratis-arc-command-validation/SKILL.md +230 -0
- package/skills/cratis-arc-ef-core-migration/LICENSE +2 -0
- package/skills/cratis-arc-ef-core-migration/SKILL.md +231 -0
- package/skills/cratis-arc-observable-query-http/LICENSE +2 -0
- package/skills/cratis-arc-observable-query-http/SKILL.md +157 -0
- package/skills/cratis-arc-query-paging/LICENSE +2 -0
- package/skills/cratis-arc-query-paging/SKILL.md +173 -0
- package/skills/cratis-arc-react-page/LICENSE +2 -0
- package/skills/cratis-arc-react-page/SKILL.md +291 -0
- package/skills/cratis-arc-react-page/references/data-page.md +125 -0
- package/skills/cratis-arc-react-page/references/data-tables.md +136 -0
- package/skills/cratis-arc-react-page/references/dialogs.md +238 -0
- package/skills/cratis-arc-react-page/references/mvvm.md +150 -0
- package/skills/cratis-arc-react-page/references/queries-and-commands.md +200 -0
- package/skills/cratis-chronicle-cli-operations/LICENSE +2 -0
- package/skills/cratis-chronicle-cli-operations/SKILL.md +199 -0
- package/skills/cratis-chronicle-client-dotnet/LICENSE +2 -0
- package/skills/cratis-chronicle-client-dotnet/SKILL.md +433 -0
- package/skills/cratis-chronicle-client-elixir/LICENSE +2 -0
- package/skills/cratis-chronicle-client-elixir/SKILL.md +429 -0
- package/skills/cratis-chronicle-client-kotlin/LICENSE +2 -0
- package/skills/cratis-chronicle-client-kotlin/SKILL.md +498 -0
- package/skills/cratis-chronicle-client-typescript/LICENSE +2 -0
- package/skills/cratis-chronicle-client-typescript/SKILL.md +396 -0
- package/skills/cratis-chronicle-compliance/LICENSE +2 -0
- package/skills/cratis-chronicle-compliance/SKILL.md +246 -0
- package/skills/cratis-chronicle-event-constraints/LICENSE +2 -0
- package/skills/cratis-chronicle-event-constraints/SKILL.md +227 -0
- package/skills/cratis-chronicle-event-modeling/LICENSE +2 -0
- package/skills/cratis-chronicle-event-modeling/SKILL.md +157 -0
- package/skills/cratis-chronicle-event-specifications/LICENSE +2 -0
- package/skills/cratis-chronicle-event-specifications/SKILL.md +200 -0
- package/skills/cratis-chronicle-event-type-migration/LICENSE +2 -0
- package/skills/cratis-chronicle-event-type-migration/SKILL.md +235 -0
- package/skills/cratis-chronicle-mcp-inspection/LICENSE +2 -0
- package/skills/cratis-chronicle-mcp-inspection/SKILL.md +79 -0
- package/skills/cratis-chronicle-mcp-inspection/references/blocked-tools.md +12 -0
- package/skills/cratis-chronicle-mcp-inspection/references/observational-tools.md +8 -0
- package/skills/cratis-chronicle-multi-tenancy/LICENSE +2 -0
- package/skills/cratis-chronicle-multi-tenancy/SKILL.md +198 -0
- package/skills/cratis-chronicle-projection/LICENSE +2 -0
- package/skills/cratis-chronicle-projection/SKILL.md +162 -0
- package/skills/cratis-chronicle-projection/references/fluent-builder.md +126 -0
- package/skills/cratis-chronicle-projection/references/model-bound-attributes.md +118 -0
- package/skills/cratis-chronicle-reactor/LICENSE +2 -0
- package/skills/cratis-chronicle-reactor/SKILL.md +344 -0
- package/skills/cratis-chronicle-read-model/LICENSE +2 -0
- package/skills/cratis-chronicle-read-model/SKILL.md +193 -0
- package/skills/cratis-chronicle-read-model/references/queries.md +142 -0
- package/skills/cratis-chronicle-read-model-specifications/LICENSE +2 -0
- package/skills/cratis-chronicle-read-model-specifications/SKILL.md +176 -0
- package/skills/cratis-chronicle-reducer/LICENSE +2 -0
- package/skills/cratis-chronicle-reducer/SKILL.md +173 -0
- package/skills/cratis-chronicle-web-workbench/LICENSE +2 -0
- package/skills/cratis-chronicle-web-workbench/SKILL.md +177 -0
- package/skills/cratis-cli-terminal-workbench/LICENSE +2 -0
- package/skills/cratis-cli-terminal-workbench/SKILL.md +156 -0
- package/skills/cratis-code-review/LICENSE +2 -0
- package/skills/cratis-code-review/SKILL.md +177 -0
- package/skills/cratis-components-accessibility/LICENSE +2 -0
- package/skills/cratis-components-accessibility/SKILL.md +176 -0
- package/skills/cratis-components-schema-editor/LICENSE +2 -0
- package/skills/cratis-components-schema-editor/SKILL.md +238 -0
- package/skills/cratis-components-stepper-command-dialog/LICENSE +2 -0
- package/skills/cratis-components-stepper-command-dialog/SKILL.md +209 -0
- package/skills/cratis-components-styling/LICENSE +2 -0
- package/skills/cratis-components-styling/SKILL.md +221 -0
- package/skills/cratis-components-toolbar/LICENSE +2 -0
- package/skills/cratis-components-toolbar/SKILL.md +259 -0
- package/skills/cratis-documentation-writing/LICENSE +2 -0
- package/skills/cratis-documentation-writing/SKILL.md +121 -0
- package/skills/cratis-event-model-diagram/LICENSE +2 -0
- package/skills/cratis-event-model-diagram/SKILL.md +142 -0
- package/skills/cratis-fundamentals-concept/LICENSE +2 -0
- package/skills/cratis-fundamentals-concept/SKILL.md +198 -0
- package/skills/cratis-fundamentals-type-discovery/LICENSE +2 -0
- package/skills/cratis-fundamentals-type-discovery/SKILL.md +152 -0
- package/skills/cratis-governed-release-methodology/LICENSE +2 -0
- package/skills/cratis-governed-release-methodology/SKILL.md +297 -0
- package/skills/cratis-lens-browser-extension/LICENSE +2 -0
- package/skills/cratis-lens-browser-extension/SKILL.md +239 -0
- package/skills/cratis-performance-review/LICENSE +2 -0
- package/skills/cratis-performance-review/SKILL.md +139 -0
- package/skills/cratis-screenplay-model-authoring/LICENSE +2 -0
- package/skills/cratis-screenplay-model-authoring/SKILL.md +231 -0
- package/skills/cratis-security-review/LICENSE +2 -0
- package/skills/cratis-security-review/SKILL.md +141 -0
- package/skills/cratis-specification-by-example/LICENSE +2 -0
- package/skills/cratis-specification-by-example/SKILL.md +188 -0
- package/skills/cratis-specifications-csharp/LICENSE +2 -0
- package/skills/cratis-specifications-csharp/SKILL.md +230 -0
- package/skills/cratis-specifications-csharp/references/application-scenarios.md +191 -0
- package/skills/cratis-specifications-csharp/references/csharp-patterns.md +142 -0
- package/skills/cratis-specifications-csharp/references/integration-specs.md +126 -0
- package/skills/cratis-specifications-typescript/LICENSE +2 -0
- package/skills/cratis-specifications-typescript/SKILL.md +202 -0
- package/skills/cratis-specifications-typescript/references/typescript-patterns.md +168 -0
- package/skills/cratis-stage-rendering-and-sandbox/LICENSE +2 -0
- package/skills/cratis-stage-rendering-and-sandbox/SKILL.md +230 -0
- package/skills/cratis-studio-mcp-safety-guidance/LICENSE +2 -0
- package/skills/cratis-studio-mcp-safety-guidance/SKILL.md +69 -0
- package/skills/cratis-studio-mcp-safety-guidance/references/blocked-tools.md +12 -0
- package/skills/cratis-studio-mcp-safety-guidance/references/observational-tools.md +8 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cratis-arc-react-page
|
|
3
|
+
description: Build a React page in a Cratis Arc application with Cratis Components — DataPage lists, columns, toolbar menu items, command dialogs, confirmation and busy-indicator dialogs, row selection, details panes, snapshot and observable queries, paging, and MVVM view models. Use when building or changing a page that lists or displays data, wiring Add/Edit/Delete actions, connecting a component to a generated Arc query or command proxy, or asking the user to confirm something. Do not use for a multi-step wizard dialog alone, for a canvas tool palette, or for backend command and read-model work.
|
|
4
|
+
license: MIT
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Cratis Arc React pages
|
|
8
|
+
|
|
9
|
+
Build the page from the generated Arc proxies and the Cratis Components
|
|
10
|
+
wrappers. Do not hand-roll a table, a dialog, or a fetch.
|
|
11
|
+
|
|
12
|
+
## Verified product sources
|
|
13
|
+
|
|
14
|
+
This skill is verified against these exact package contracts:
|
|
15
|
+
|
|
16
|
+
| Package | Version | Verified from |
|
|
17
|
+
| --- | --- | --- |
|
|
18
|
+
| `@cratis/components` | `3.0.0` | its package manifest and component sources |
|
|
19
|
+
| `@cratis/arc` | `>=20.3.1 <23` | peer range declared by `@cratis/components@3.0.0` |
|
|
20
|
+
| `@cratis/arc.react` | `>=20.3.1 <23` | peer range declared by `@cratis/components@3.0.0` |
|
|
21
|
+
| `primereact` | `^11.0.0` | peer of `@cratis/components@3.0.0` |
|
|
22
|
+
| `primeicons` | `^8.0.0` | peer of `@cratis/components@3.0.0` |
|
|
23
|
+
| `react` | `^19.0.0` | peer of `@cratis/components@3.0.0` |
|
|
24
|
+
|
|
25
|
+
`@cratis/arc.react.mvvm` ships with `@cratis/arc.react` and is an explicit
|
|
26
|
+
application dependency. Reverify before claiming another version.
|
|
27
|
+
|
|
28
|
+
## Import from subpaths, never the root barrel
|
|
29
|
+
|
|
30
|
+
The root `@cratis/components` entry exports **namespaces**, not components —
|
|
31
|
+
`import { DataPage } from '@cratis/components'` yields a namespace object whose
|
|
32
|
+
component is `DataPage.DataPage`. Always import from the subpath:
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
import { DataPage, MenuItem, Column } from '@cratis/components/DataPage';
|
|
36
|
+
import { CommandDialog } from '@cratis/components/CommandDialog';
|
|
37
|
+
import { InputTextField } from '@cratis/components/CommandForm';
|
|
38
|
+
import { Dialog } from '@cratis/components/Dialogs';
|
|
39
|
+
import { DialogProps, DialogResult, useDialog } from '@cratis/arc.react/dialogs';
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Step 1 — Prerequisites
|
|
43
|
+
|
|
44
|
+
- The backend query and command must already exist, and a Debug build must have
|
|
45
|
+
regenerated the TypeScript proxies. Generated proxies carry a
|
|
46
|
+
`**DO NOT EDIT** - This file is an automatically generated file.` header —
|
|
47
|
+
fix the C# source and rebuild instead of editing one.
|
|
48
|
+
- The app must mount `CratisComponentsProvider` (from
|
|
49
|
+
`@cratis/components/Common`) above every Cratis component. PrimeReact 11
|
|
50
|
+
resolves its configuration from a provider, so components fail without one.
|
|
51
|
+
- The app must import the stylesheets explicitly; components no longer import
|
|
52
|
+
their own CSS:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import '@cratis/components/tokens'; // the --cratis-* token layer
|
|
56
|
+
import '@cratis/components/styles'; // every component stylesheet
|
|
57
|
+
import '@cratis/components/theme'; // optional license-free baseline look
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
See the **cratis-components-styling** skill for the full theming contract.
|
|
61
|
+
|
|
62
|
+
## Step 2 — The DataPage shell
|
|
63
|
+
|
|
64
|
+
`DataPage` owns the query subscription, paging, selection, action menubar, and
|
|
65
|
+
the optional details split. Do not pre-fetch rows and pass an array.
|
|
66
|
+
|
|
67
|
+
Required props: `title`, `query`, `emptyMessage`, `children`. Columns and
|
|
68
|
+
toolbar actions are compositional children.
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
import { Column, DataPage } from '@cratis/components/DataPage';
|
|
72
|
+
import { AllAccounts } from './AllAccounts';
|
|
73
|
+
|
|
74
|
+
export const AccountsPage = () => (
|
|
75
|
+
<DataPage title='Accounts' query={AllAccounts} emptyMessage='No accounts yet.' dataKey='id'>
|
|
76
|
+
<DataPage.Columns>
|
|
77
|
+
<Column field='name' header='Name' sortable />
|
|
78
|
+
<Column field='balance' header='Balance' />
|
|
79
|
+
</DataPage.Columns>
|
|
80
|
+
</DataPage>
|
|
81
|
+
);
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Only `DataPage.Columns` and `DataPage.MenuItems` exist as compound members.
|
|
85
|
+
`MenuItem` and `Column` are **named exports** — there is no `DataPage.MenuItem`
|
|
86
|
+
and no `DataPage.Column`. Pass `dataKey` whenever the read model has an
|
|
87
|
+
identity.
|
|
88
|
+
|
|
89
|
+
See [data-page.md](references/data-page.md) for every prop, and
|
|
90
|
+
[data-tables.md](references/data-tables.md) when you need a table without the
|
|
91
|
+
page chrome.
|
|
92
|
+
|
|
93
|
+
## Step 3 — Toolbar actions
|
|
94
|
+
|
|
95
|
+
Menu items go in `<DataPage.MenuItems>`. `MenuItem` takes `command`, not
|
|
96
|
+
`onClick`, and its `icon` is a **React component type**, not an icon class
|
|
97
|
+
string. `disableOnUnselected` greys the item out until a row is selected.
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
import { DataPage, MenuItem } from '@cratis/components/DataPage';
|
|
101
|
+
import { useDialog } from '@cratis/arc.react/dialogs';
|
|
102
|
+
import { CreateAccountDialog } from './CreateAccountDialog';
|
|
103
|
+
|
|
104
|
+
const [CreateAccountWrapper, showCreateAccount] = useDialog(CreateAccountDialog);
|
|
105
|
+
|
|
106
|
+
<DataPage title='Accounts' query={AllAccounts} emptyMessage='No accounts yet.'>
|
|
107
|
+
<DataPage.MenuItems>
|
|
108
|
+
<MenuItem label='Add account' icon={() => <i className='pi pi-plus' />} command={() => showCreateAccount()} />
|
|
109
|
+
<MenuItem label='Edit account' icon={() => <i className='pi pi-pencil' />} command={() => showEditAccount()} disableOnUnselected />
|
|
110
|
+
</DataPage.MenuItems>
|
|
111
|
+
<DataPage.Columns>
|
|
112
|
+
<Column field='name' header='Name' />
|
|
113
|
+
</DataPage.Columns>
|
|
114
|
+
</DataPage>
|
|
115
|
+
<CreateAccountWrapper />
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Render the wrapper returned by `useDialog` once in the tree. See
|
|
119
|
+
[dialogs.md](references/dialogs.md) for the full dialog contract.
|
|
120
|
+
|
|
121
|
+
## Step 4 — Command dialogs
|
|
122
|
+
|
|
123
|
+
A dialog that runs a command is its own component built on `CommandDialog`.
|
|
124
|
+
Bind each input with a `CommandForm` field whose `value` accessor selects the
|
|
125
|
+
command property; the label prop is `title`.
|
|
126
|
+
|
|
127
|
+
```tsx
|
|
128
|
+
import { CommandDialog } from '@cratis/components/CommandDialog';
|
|
129
|
+
import { InputTextField } from '@cratis/components/CommandForm';
|
|
130
|
+
import { DialogResult, useDialogContext } from '@cratis/arc.react/dialogs';
|
|
131
|
+
import { CreateAccount } from './CreateAccount';
|
|
132
|
+
|
|
133
|
+
export const CreateAccountDialog = () => {
|
|
134
|
+
const { closeDialog } = useDialogContext();
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<CommandDialog<CreateAccount>
|
|
138
|
+
command={CreateAccount}
|
|
139
|
+
title='Create account'
|
|
140
|
+
okLabel='Create'
|
|
141
|
+
onSuccess={() => closeDialog(DialogResult.Ok)}
|
|
142
|
+
onCancel={() => closeDialog(DialogResult.Cancelled)}>
|
|
143
|
+
<InputTextField<CreateAccount> value={c => c.name} title='Account name' />
|
|
144
|
+
</CommandDialog>
|
|
145
|
+
);
|
|
146
|
+
};
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Never put a raw PrimeReact control inside a command dialog for a command value —
|
|
150
|
+
it bypasses the field wrapper, so validation never re-runs and the submit button
|
|
151
|
+
stays disabled. Seed values that must be present for validity with
|
|
152
|
+
`initialValues`, not `onBeforeExecute`.
|
|
153
|
+
|
|
154
|
+
## Step 5 — Confirming, and showing that work is in progress
|
|
155
|
+
|
|
156
|
+
Do not build a confirmation or busy dialog into a page. `ConfirmationDialog` and
|
|
157
|
+
`BusyIndicatorDialog` are registered once at the app root through
|
|
158
|
+
`DialogComponents` and raised by hook, which is what keeps every one of them
|
|
159
|
+
identical:
|
|
160
|
+
|
|
161
|
+
```tsx
|
|
162
|
+
import { DialogComponents } from '@cratis/arc.react/dialogs';
|
|
163
|
+
import { BusyIndicatorDialog, ConfirmationDialog } from '@cratis/components/Dialogs';
|
|
164
|
+
|
|
165
|
+
<DialogComponents confirmation={ConfirmationDialog} busyIndicator={BusyIndicatorDialog}>
|
|
166
|
+
<YourApp />
|
|
167
|
+
</DialogComponents>
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
```tsx
|
|
171
|
+
import { DialogButtons, DialogResult, useBusyIndicator, useConfirmationDialog } from '@cratis/arc.react/dialogs';
|
|
172
|
+
|
|
173
|
+
const [confirm] = useConfirmationDialog();
|
|
174
|
+
if (await confirm('Delete this account?', `"${account.name}" disappears permanently.`, DialogButtons.YesNo) !== DialogResult.Yes) return;
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
A busy indicator is modal and deliberately non-dismissible, so reserve it for
|
|
178
|
+
work the user genuinely cannot proceed past — a multi-step import, a migration.
|
|
179
|
+
For an ordinary command behind a button, the in-flight disabled button is the
|
|
180
|
+
right control; a modal that flashes for 200 ms tells the user nothing. When you
|
|
181
|
+
do use one, pair it with the close call in a `finally`:
|
|
182
|
+
|
|
183
|
+
```tsx
|
|
184
|
+
const [showBusy, closeBusy] = useBusyIndicator('Importing', 'This takes a moment.');
|
|
185
|
+
showBusy();
|
|
186
|
+
try { await importEverything(); } finally { closeBusy(); }
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Step 6 — Row selection and a details pane
|
|
190
|
+
|
|
191
|
+
`selection` and `onSelectionChange` are controlled. The change event carries
|
|
192
|
+
`value`, which is `null` when the selection is cleared.
|
|
193
|
+
|
|
194
|
+
```tsx
|
|
195
|
+
import type { DataTableSelectionChangeEvent } from '@cratis/components/DataTables';
|
|
196
|
+
|
|
197
|
+
const [selected, setSelected] = useState<AccountSummary | null>(null);
|
|
198
|
+
|
|
199
|
+
<DataPage
|
|
200
|
+
title='Accounts'
|
|
201
|
+
query={AllAccounts}
|
|
202
|
+
emptyMessage='No accounts yet.'
|
|
203
|
+
dataKey='id'
|
|
204
|
+
selection={selected}
|
|
205
|
+
onSelectionChange={(event: DataTableSelectionChangeEvent<AccountSummary>) => setSelected(event.value)}
|
|
206
|
+
detailsComponent={AccountDetails}>
|
|
207
|
+
<DataPage.Columns>
|
|
208
|
+
<Column field='name' header='Name' />
|
|
209
|
+
</DataPage.Columns>
|
|
210
|
+
</DataPage>
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
`detailsComponent` receives `IDetailsComponentProps<T>` — `{ item, onRefresh? }`
|
|
214
|
+
— and renders beside the table for the selected row.
|
|
215
|
+
|
|
216
|
+
## Step 7 — Snapshot or observable query
|
|
217
|
+
|
|
218
|
+
The **same `query` prop** takes a snapshot query or an observable query. There
|
|
219
|
+
is no separate `observableQuery` prop: `DataPage` inspects the query prototype
|
|
220
|
+
and picks the snapshot or observable table itself. Pass an observable query and
|
|
221
|
+
the page stays live; pass a snapshot query and call `onRefresh` after a command
|
|
222
|
+
succeeds.
|
|
223
|
+
|
|
224
|
+
Read [queries-and-commands.md](references/queries-and-commands.md) for the
|
|
225
|
+
generated proxy hooks, their exact return tuples, paging, and how to read a
|
|
226
|
+
command result.
|
|
227
|
+
|
|
228
|
+
## Step 8 — MVVM for pages with real logic
|
|
229
|
+
|
|
230
|
+
Extract a view model as soon as the component has three or more `useState`
|
|
231
|
+
calls, a state-synchronizing `useEffect`, or derived values. Keep the component
|
|
232
|
+
declarative and read `viewModel.x` **inside** JSX so MobX tracks it.
|
|
233
|
+
|
|
234
|
+
```tsx
|
|
235
|
+
import { withViewModel } from '@cratis/arc.react.mvvm';
|
|
236
|
+
import { injectable } from 'tsyringe';
|
|
237
|
+
|
|
238
|
+
@injectable()
|
|
239
|
+
export class AccountsViewModel {
|
|
240
|
+
selected: AccountSummary | null = null;
|
|
241
|
+
select(account: AccountSummary | null) { this.selected = account; }
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export const AccountsPage = withViewModel(AccountsViewModel, ({ viewModel }) => (
|
|
245
|
+
<DataPage title='Accounts' query={AllAccounts} emptyMessage='No accounts yet.' dataKey='id'
|
|
246
|
+
selection={viewModel.selected}
|
|
247
|
+
onSelectionChange={event => viewModel.select(event.value)}>
|
|
248
|
+
<DataPage.Columns>
|
|
249
|
+
<Column field='name' header='Name' />
|
|
250
|
+
</DataPage.Columns>
|
|
251
|
+
</DataPage>
|
|
252
|
+
));
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
`withViewModel` applies `makeAutoObservable` itself — do not call it in the
|
|
256
|
+
constructor. See [mvvm.md](references/mvvm.md) for injection, route parameters,
|
|
257
|
+
props handling, and teardown.
|
|
258
|
+
|
|
259
|
+
## Quick decision guide
|
|
260
|
+
|
|
261
|
+
| Need | Use |
|
|
262
|
+
| --- | --- |
|
|
263
|
+
| Read-only list page | `DataPage` with a snapshot query |
|
|
264
|
+
| Live-updating list page | `DataPage` with an observable query on the same `query` prop |
|
|
265
|
+
| Add or create action | `<DataPage.MenuItems>` + `MenuItem` + `CommandDialog` + `useDialog` |
|
|
266
|
+
| Edit the selected row | `selection` / `onSelectionChange` + `CommandDialog` + `initialValues` / `currentValues` |
|
|
267
|
+
| Detail for the selected row | `detailsComponent` |
|
|
268
|
+
| Table without page chrome | `DataTableForQuery` / `DataTableForObservableQuery` |
|
|
269
|
+
| Ask the user to confirm | `useConfirmationDialog` — never `window.confirm`, never a hand-rolled Yes/No dialog |
|
|
270
|
+
| Multi-step wizard dialog | the **cratis-components-stepper-command-dialog** skill |
|
|
271
|
+
| Canvas tool palette | the **cratis-components-toolbar** skill |
|
|
272
|
+
| Complex page state | `withViewModel` |
|
|
273
|
+
|
|
274
|
+
## Verify
|
|
275
|
+
|
|
276
|
+
- Every Cratis Components import uses a subpath, not the root barrel.
|
|
277
|
+
- `CratisComponentsProvider` wraps the tree and the three stylesheets are
|
|
278
|
+
imported once at the app entry point.
|
|
279
|
+
- `DataPage` receives `title`, `query`, `emptyMessage`, and children, and
|
|
280
|
+
`dataKey` when the read model has an identity.
|
|
281
|
+
- Columns sit in `<DataPage.Columns>`; menu items sit in
|
|
282
|
+
`<DataPage.MenuItems>` and use `command`, not `onClick`.
|
|
283
|
+
- No component pre-fetches rows and passes them to `DataPage` as an array.
|
|
284
|
+
- Every command value in a dialog is bound through a `CommandForm` field.
|
|
285
|
+
- Values required for validity come from `initialValues`, not
|
|
286
|
+
`onBeforeExecute`.
|
|
287
|
+
- Confirmations and busy indicators are raised by hook, registered once at the
|
|
288
|
+
app root.
|
|
289
|
+
- Every `showBusy()` has a matching close in a `finally`.
|
|
290
|
+
- No generated proxy file was edited.
|
|
291
|
+
- Lint, the frontend test gate, and the TypeScript build all pass.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# DataPage reference
|
|
2
|
+
|
|
3
|
+
`DataPage` is the standard list page: an action menubar, a data table, and an
|
|
4
|
+
optional details pane in one component. Import from
|
|
5
|
+
`@cratis/components/DataPage`.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { Column, DataPage, MenuItem } from '@cratis/components/DataPage';
|
|
9
|
+
import type { IDetailsComponentProps } from '@cratis/components/DataPage';
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Compound members
|
|
13
|
+
|
|
14
|
+
Exactly two static members exist:
|
|
15
|
+
|
|
16
|
+
- `DataPage.Columns` — wraps the `Column` elements.
|
|
17
|
+
- `DataPage.MenuItems` — wraps the `MenuItem` elements.
|
|
18
|
+
|
|
19
|
+
`Column` and `MenuItem` are named exports of the same subpath. There is no
|
|
20
|
+
`DataPage.Column` and no `DataPage.MenuItem`.
|
|
21
|
+
|
|
22
|
+
## `DataPageProps`
|
|
23
|
+
|
|
24
|
+
| Prop | Type | Notes |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| `title` | `string` | required — menubar title |
|
|
27
|
+
| `query` | `Constructor<TQuery>` | required — a snapshot **or** observable query proxy |
|
|
28
|
+
| `emptyMessage` | `string` | required — shown when the query returns no rows |
|
|
29
|
+
| `children` | `ReactNode` | required — `DataPage.Columns` and optionally `DataPage.MenuItems` |
|
|
30
|
+
| `queryArguments` | `TArguments` | forwarded to the query |
|
|
31
|
+
| `dataKey` | `string \| undefined` | row identity; pass whenever the read model has one |
|
|
32
|
+
| `selection` | `any \| undefined \| null` | controlled selection |
|
|
33
|
+
| `onSelectionChange` | `(event: DataTableSelectionChangeEvent<any>) => void` | `event.value` is the row or `null` |
|
|
34
|
+
| `detailsComponent` | `React.FC<IDetailsComponentProps<any>>` | rendered beside the table for the selected row |
|
|
35
|
+
| `globalFilterFields` | `string[] \| undefined` | enables the global search box over these fields |
|
|
36
|
+
| `defaultFilters` | `DataTableFilterMeta` | initial per-column filter state |
|
|
37
|
+
| `onRefresh` | `() => void` | invoked to re-fetch a snapshot query |
|
|
38
|
+
| `actionsAriaLabel` | `string` | accessible name of the actions menubar, default `'Actions'` |
|
|
39
|
+
| `tableClassName` | `string` | class on the inner table |
|
|
40
|
+
| `tablePt` / `tablePtOptions` / `tableUnstyled` | PrimeReact pass-through | target the inner data table |
|
|
41
|
+
| `menubarClassName` | `string` | class on the action menubar |
|
|
42
|
+
| `menubarPt` / `menubarPtOptions` / `menubarUnstyled` | PrimeReact pass-through | target the action buttons |
|
|
43
|
+
| `clientFiltering` | `boolean` | **deprecated and a no-op** — do not use in new code |
|
|
44
|
+
|
|
45
|
+
## Snapshot versus observable
|
|
46
|
+
|
|
47
|
+
There is no `observableQuery` prop. `DataPage` inspects the query prototype at
|
|
48
|
+
render time: a snapshot query renders the snapshot table, anything else renders
|
|
49
|
+
the observable table. Pass an observable query proxy and the page pushes
|
|
50
|
+
updates automatically; pass a snapshot query and re-fetch through `onRefresh`
|
|
51
|
+
after a command succeeds.
|
|
52
|
+
|
|
53
|
+
## `MenuItemProps`
|
|
54
|
+
|
|
55
|
+
| Prop | Type | Notes |
|
|
56
|
+
| --- | --- | --- |
|
|
57
|
+
| `icon` | `React.ComponentType<{ className?: string }>` | a **component**, not an icon class string |
|
|
58
|
+
| `label` | `string` | menu label |
|
|
59
|
+
| `command` | `() => void` | the action — **not** `onClick` |
|
|
60
|
+
| `disabled` | `boolean` | unconditionally disabled |
|
|
61
|
+
| `disableOnUnselected` | `boolean` | disabled until a row is selected |
|
|
62
|
+
|
|
63
|
+
`MenuItem` renders nothing on its own; it is a marker that `DataPage.MenuItems`
|
|
64
|
+
reads. Icons are usually a small inline component:
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
<MenuItem label='Add account' icon={() => <i className='pi pi-plus' />} command={() => showCreate()} />
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## `IDetailsComponentProps<TDataType>`
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
interface IDetailsComponentProps<TDataType> {
|
|
74
|
+
item: TDataType;
|
|
75
|
+
onRefresh?: () => void;
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
const AccountDetails = ({ item }: IDetailsComponentProps<AccountSummary>) => <div>{item.name}</div>;
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## `Column`
|
|
84
|
+
|
|
85
|
+
`Column` is re-exported from `@cratis/components/DataPage` and defined in
|
|
86
|
+
`@cratis/components/DataTables`. It renders nothing itself — it is a
|
|
87
|
+
declaration the table reads.
|
|
88
|
+
|
|
89
|
+
| Prop | Type |
|
|
90
|
+
| --- | --- |
|
|
91
|
+
| `field` | `string` |
|
|
92
|
+
| `header` | `React.ReactNode` |
|
|
93
|
+
| `body` | `(rowData: TData) => React.ReactNode` |
|
|
94
|
+
| `sortable` | `boolean` |
|
|
95
|
+
| `filter` | `boolean` |
|
|
96
|
+
| `filterField` | `string` |
|
|
97
|
+
| `filterPlaceholder` | `string` |
|
|
98
|
+
| `dataType` | `'text' \| 'numeric' \| 'date' \| 'boolean'` |
|
|
99
|
+
| `showFilterMatchModes` | `boolean` |
|
|
100
|
+
| `filterElement` | `(options: ColumnFilterElementOptions) => ReactNode` |
|
|
101
|
+
| `filterLabels` | `Partial<ColumnFilterMenuLabels>` |
|
|
102
|
+
| `selectionMode` | `'single' \| 'multiple'` |
|
|
103
|
+
| `style` / `className` | `React.CSSProperties` / `string` |
|
|
104
|
+
| `headerStyle` / `headerClassName` | `React.CSSProperties` / `string` |
|
|
105
|
+
| `bodyStyle` / `bodyClassName` | `React.CSSProperties` / `string` |
|
|
106
|
+
|
|
107
|
+
Use `body` for a computed or formatted cell:
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
<Column field='balance' header='Balance' body={row => `$${row.balance.toFixed(2)}`} />
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Type name caution
|
|
114
|
+
|
|
115
|
+
`@cratis/components/DataPage` exports two different things called
|
|
116
|
+
`ColumnProps` — the props of the `DataPage.Columns` wrapper and the re-exported
|
|
117
|
+
column declaration props. Import `ColumnProps` from
|
|
118
|
+
`@cratis/components/DataTables` when you need the column type, so the reference
|
|
119
|
+
is unambiguous.
|
|
120
|
+
|
|
121
|
+
## Layout
|
|
122
|
+
|
|
123
|
+
The details pane renders in a resizable split. Give the page a bounded height —
|
|
124
|
+
the stories wrap `DataPage` in a container with an explicit height — or the
|
|
125
|
+
split has nothing to size against.
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Data tables reference
|
|
2
|
+
|
|
3
|
+
Use a standalone table when you need query-backed rows **without** the
|
|
4
|
+
`DataPage` chrome — embedded in a panel, a card, or a dialog. Import from
|
|
5
|
+
`@cratis/components/DataTables`.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import {
|
|
9
|
+
Column,
|
|
10
|
+
DataTableForObservableQuery,
|
|
11
|
+
DataTableForQuery,
|
|
12
|
+
} from '@cratis/components/DataTables';
|
|
13
|
+
import type { DataTableSelectionChangeEvent } from '@cratis/components/DataTables';
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Pick the component by query kind
|
|
17
|
+
|
|
18
|
+
| Situation | Component |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| Full list page with menubar and details | `DataPage` |
|
|
21
|
+
| Embedded table, snapshot query | `DataTableForQuery` |
|
|
22
|
+
| Embedded table, observable query | `DataTableForObservableQuery` |
|
|
23
|
+
|
|
24
|
+
Unlike `DataPage`, these do **not** auto-detect — pass a snapshot query to
|
|
25
|
+
`DataTableForQuery` and an observable query to `DataTableForObservableQuery`.
|
|
26
|
+
|
|
27
|
+
## Shared props
|
|
28
|
+
|
|
29
|
+
Both components take the same prop names:
|
|
30
|
+
|
|
31
|
+
| Prop | Type | Notes |
|
|
32
|
+
| --- | --- | --- |
|
|
33
|
+
| `query` | `Constructor<TQuery>` | required |
|
|
34
|
+
| `emptyMessage` | `string` | required |
|
|
35
|
+
| `children` | `ReactNode` | the `Column` declarations |
|
|
36
|
+
| `queryArguments` | `TArguments` | forwarded to the query |
|
|
37
|
+
| `dataKey` | `string \| undefined` | row identity |
|
|
38
|
+
| `selection` | `TDataType \| undefined \| null` | controlled selection |
|
|
39
|
+
| `onSelectionChange` | `(event: DataTableSelectionChangeEvent<TDataType>) => void` | `event.value` is the row or `null` |
|
|
40
|
+
| `globalFilterFields` | `string[] \| undefined` | enables the search box |
|
|
41
|
+
| `defaultFilters` | `DataTableFilterMeta` | initial filter state |
|
|
42
|
+
| `className` | `string` | |
|
|
43
|
+
| `pt` / `ptOptions` / `unstyled` | PrimeReact pass-through | |
|
|
44
|
+
| `paginatorClassName` | `string` | |
|
|
45
|
+
| `paginatorAriaLabels` | paginator label overrides | localize the paginator |
|
|
46
|
+
| `clientFiltering` | `boolean` | **deprecated and a no-op** |
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
<DataTableForQuery<AllAccounts, AccountSummary, object>
|
|
50
|
+
query={AllAccounts}
|
|
51
|
+
emptyMessage='No accounts found'
|
|
52
|
+
dataKey='id'
|
|
53
|
+
globalFilterFields={['name', 'ownerName']}
|
|
54
|
+
selection={selected}
|
|
55
|
+
onSelectionChange={event => setSelected(event.value)}>
|
|
56
|
+
<Column field='name' header='Name' sortable filter filterPlaceholder='Filter by name' />
|
|
57
|
+
<Column field='ownerName' header='Owner' />
|
|
58
|
+
</DataTableForQuery>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Paging
|
|
62
|
+
|
|
63
|
+
Both tables page server-side through the Arc paging hooks with a fixed page
|
|
64
|
+
size of 20 rows starting at page 0. The paginator renders only when the result
|
|
65
|
+
reports more than one page. There is no page-size prop on these components —
|
|
66
|
+
when you need caller-controlled paging, call the query's paging hook yourself
|
|
67
|
+
(see [queries-and-commands.md](queries-and-commands.md)) and render the rows
|
|
68
|
+
with your own layout.
|
|
69
|
+
|
|
70
|
+
## Selection event
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
interface DataTableSelectionChangeEvent<TData> {
|
|
74
|
+
value: TData | null;
|
|
75
|
+
originalEvent?: SyntheticEvent;
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Add a selection column with `<Column selectionMode='single' headerStyle={{ width: '3rem' }} />`
|
|
80
|
+
when you want an explicit selector rather than row click selection.
|
|
81
|
+
|
|
82
|
+
## Column filtering
|
|
83
|
+
|
|
84
|
+
Set `filter` on a column to get a per-column filter menu. `dataType` picks the
|
|
85
|
+
match modes (`'text'`, `'numeric'`, `'date'`, `'boolean'`; default `'text'`),
|
|
86
|
+
`showFilterMatchModes={false}` hides the mode picker, and `filterElement`
|
|
87
|
+
replaces the editor entirely:
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
<Column
|
|
91
|
+
field='rating'
|
|
92
|
+
header='Rating'
|
|
93
|
+
filter
|
|
94
|
+
filterElement={options => (
|
|
95
|
+
<RatingEditor value={options.value} onChange={value => options.onChange(value)} onApply={options.onApply} />
|
|
96
|
+
)} />
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`ColumnFilterElementOptions` gives `field`, `value`, `matchMode`, `onChange`,
|
|
100
|
+
`onApply`, and `onClear`.
|
|
101
|
+
|
|
102
|
+
Filter menu strings are localizable per column with `filterLabels`
|
|
103
|
+
(`filterTriggerAriaLabel`, `clear`, `apply`, `true`, `false`) — never ship a
|
|
104
|
+
hard-coded English accessible name.
|
|
105
|
+
|
|
106
|
+
## Filter state shape
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
type DataTableFilterConstraint = { value: unknown; matchMode?: DataTableFilterMatchMode };
|
|
110
|
+
type DataTableFilterMeta = Record<string, DataTableFilterConstraint>;
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`DataTableFilterMatchMode` is a const object, not an enum:
|
|
114
|
+
`StartsWith`, `Contains`, `NotContains`, `EndsWith`, `Equals`, `NotEquals`,
|
|
115
|
+
`In`, `Between`, `LessThan`, `LessThanOrEqual`, `GreaterThan`,
|
|
116
|
+
`GreaterThanOrEqual`, `DateIs`, `DateIsNot`, `DateBefore`, `DateAfter`.
|
|
117
|
+
|
|
118
|
+
## Custom match modes
|
|
119
|
+
|
|
120
|
+
Register a matcher to add a domain-specific mode, and unregister it when the
|
|
121
|
+
owner unmounts:
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import { registerDataTableFilterMatcher } from '@cratis/components/DataTables';
|
|
125
|
+
|
|
126
|
+
const registration = registerDataTableFilterMatcher(
|
|
127
|
+
'startsWithAccountPrefix',
|
|
128
|
+
(value, filter) => String(value).startsWith(String(filter)));
|
|
129
|
+
|
|
130
|
+
// later
|
|
131
|
+
registration.unregister();
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Registration rejects names that collide with a built-in mode, names that are
|
|
135
|
+
not identifier-like, and prototype-polluting names. Re-registering the same
|
|
136
|
+
name with a *different* function throws — register once per matcher.
|