diff --git a/.ai/agents/frontend-developer.md b/.ai/agents/frontend-developer.md index dd4f7bc..0eef551 100644 --- a/.ai/agents/frontend-developer.md +++ b/.ai/agents/frontend-developer.md @@ -189,24 +189,21 @@ import { Page } from '@cratis/components/Common'; import { AddProject } from './Registration/AddProject'; import { Listing } from './Listing/Listing'; import { DialogResult, useDialog } from '@cratis/arc.react/dialogs'; -import { Menubar } from 'primereact/menubar'; -import { MenuItem } from 'primereact/menuitem'; +import { Button } from 'primereact/button'; import * as mdIcons from 'react-icons/md'; export const Projects = () => { const [AddProjectDialog, showAddProjectDialog] = useDialog(AddProject); - const menuItems: MenuItem[] = [ - { - label: 'Add Project', - icon: mdIcons.MdAdd, - command: async () => { await showAddProjectDialog(); } - } - ]; - + // For a query-backed list page, prefer `DataPage` with `` + // (it owns the action bar). PrimeReact 11 removed the standalone `Menubar`; + // for a custom toolbar, compose `Button`s (content is children in v11). return ( - + diff --git a/.ai/rules/components.md b/.ai/rules/components.md index 135ba4f..a9716e0 100644 --- a/.ai/rules/components.md +++ b/.ai/rules/components.md @@ -21,11 +21,37 @@ Reach PrimeReact almost exclusively through Cratis Components wrappers. Import f | Dropdown | `Dropdown` | `@cratis/components/Dropdown` | | Command dialog | `CommandDialog` / `StepperCommandDialog` | `@cratis/components/CommandDialog` | | Data/confirmation dialog | `Dialog` / `ConfirmationDialog` / `BusyIndicatorDialog` | `@cratis/components/Dialogs` | -| Command form fields | `InputTextField`, … | `@cratis/components/CommandForm` | +| Command form fields | `InputTextField`, `PasswordField`, `ToggleSwitchField`, `RatingField`, … | `@cratis/components/CommandForm` | +| Notifications (toasts) | `Toaster` / `toast` / `toastCommandResult` | `@cratis/components/Notifications` | +| Status & display | `Tag` / `Badge` / `Chip` / `Skeleton` / `Avatar` / `ProgressBar` | `@cratis/components/Display` | | Canvas tool palette | `Toolbar` | `@cratis/components/Toolbar` | Use `Dropdown` from `@cratis/components/Dropdown` (not raw `primereact/dropdown`) — it appends to the document body and stacks correctly above overlays, avoiding the z-index issues raw PrimeReact dropdowns have inside dialogs. +### Notifications — feedback for commands run outside a dialog + +`CommandDialog` handles success/error feedback itself. For a command executed +**programmatically** (`command.execute()` outside a dialog), mount one +`` near the app root and surface the result with `toastCommandResult` +(both from `@cratis/components/Notifications`) — it maps the granular +`ICommandResult` flags to the right toast (success, not-authorized, validation +with per-field messages, exceptions — never stack traces): + +```tsx +const result = await command.execute(); +if (toastCommandResult(result, { successTitle: 'Author registered' })) refresh(); +``` + +For ad-hoc notifications, call the imperative `toast.success/info/warn/error(...)` +— each takes an **options object**, not a bare string: `toast.info({ title: 'Saved', description: '…' })`. + +### Column filtering & display components + +`` supports `filter` (a per-column filter menu with match modes) and +`DataPage` / the data tables show a global search box when `globalFilterFields` +is set. Use the `Display` components (`Tag`, `Badge`, `Skeleton`, …) for status +indicators and loading states in tables and detail views. + ### `DataPage` — query list pages `DataPage` (from `@cratis/components/DataPage`) owns the data table's subscription, paging, selection, action menubar, and details split — **do not pre-fetch rows and pass an `items` array**. Required props: `title`, `query` (`Constructor`; snapshot and observable queries are auto-detected), `emptyMessage`, and `children`. Other props: `queryArguments`, `dataKey` (pass whenever the read model has an identity), `selection` / `onSelectionChange`, `globalFilterFields` / `defaultFilters` / `clientFiltering`, `detailsComponent` (`React.FC>` = `{ item, onRefresh? }`), `onRefresh`, and PrimeReact pass-through `tablePt`/`tableClassName`/`menubarPt`/`menubarClassName`. @@ -33,8 +59,7 @@ Use `Dropdown` from `@cratis/components/Dropdown` (not raw `primereact/dropdown` Columns and toolbar actions are compositional children: ```tsx -import { DataPage, MenuItem } from '@cratis/components/DataPage'; -import { Column } from 'primereact/column'; +import { DataPage, MenuItem, Column } from '@cratis/components/DataPage'; diff --git a/.ai/rules/dialogs.md b/.ai/rules/dialogs.md index bf1bf20..a1cb906 100644 --- a/.ai/rules/dialogs.md +++ b/.ai/rules/dialogs.md @@ -75,7 +75,7 @@ It receives the current command values and **must return them** (mutated or not) ### CommandForm fields -Use built-in `CommandForm` fields (from `@cratis/components/CommandForm`) for every user-input value — a raw PrimeReact control inside a command dialog bypasses `CommandFormFieldWrapper`, so validation never re-runs and the submit button stays **permanently disabled**. Catalog: `InputTextField`, `NumberField`, `DropdownField`, `CheckboxField`, `TextAreaField`, `CalendarField`, `RadioButtonField`, `RadioGroupField`, `ChipsField`, `MultiSelectField`, `ColorPickerField`, `SliderField`. +Use built-in `CommandForm` fields (from `@cratis/components/CommandForm`) for every user-input value — a raw PrimeReact control inside a command dialog bypasses `CommandFormFieldWrapper`, so validation never re-runs and the submit button stays **permanently disabled**. Catalog: `InputTextField`, `PasswordField`, `NumberField`, `DropdownField`, `CheckboxField`, `ToggleSwitchField`, `TextAreaField`, `CalendarField`, `RadioButtonField`, `RadioGroupField`, `ChipsField`, `MultiSelectField`, `ColorPickerField`, `SliderField`, `RatingField`. - The `value={c => c.name}` **accessor lambda doubles as the binding and type-checked field selection** — renaming a command property surfaces a compile error at every binding. - **`RadioGroupField`** renders a whole group from data (`options`/`optionLabel`/`optionValue`, `layout='horizontal'|'vertical'`); **`RadioButtonField`** is one component per option (each takes a `buttonValue`). Both infer the value type from the accessor — no `as string` casts. @@ -223,4 +223,4 @@ Use `buttons={null}` for dialogs that contain their own internal actions (e.g. a | `width` | `string` | Dialog width (e.g. `'50vw'`) — replaces PrimeReact `style={{ width }}` | | `resizable` | `boolean` | Default `false` | -PrimeReact-specific props (`style`, `contentStyle`, `modal`, `dismissableMask`, `draggable`, `footer`, `onHide`) are **not** available — do not use them. +`style`, `contentStyle`, and `dismissable` **are** supported. The other v10 PrimeReact Dialog props (`modal`, `dismissableMask`, `draggable`, `footer`, `onHide`) are **not** available — do not use them. (`resizable` is accepted for compatibility but is a no-op in PrimeReact 11.) diff --git a/.ai/rules/react.md b/.ai/rules/react.md index 9574aa8..915c8d1 100644 --- a/.ai/rules/react.md +++ b/.ai/rules/react.md @@ -153,6 +153,8 @@ if (result.hasExceptions) { toast.error('Something went wrong'); console.error(r // happy path — refresh queries, close, etc. ``` +`toastCommandResult(result, opts)` from `@cratis/components/Notifications` collapses this whole branch into one call (with a `` mounted) — success/not-authorized/validation/exception → the right toast, no stack traces shown. + ### Command helpers - **`useCommandInstance(Command)`** — read the live reactive command instance for dependent fields (read it; never mutate — mutations go through field bindings). diff --git a/.ai/skills/cratis-react-page/SKILL.md b/.ai/skills/cratis-react-page/SKILL.md index aff9163..c26268d 100644 --- a/.ai/skills/cratis-react-page/SKILL.md +++ b/.ai/skills/cratis-react-page/SKILL.md @@ -14,7 +14,7 @@ Import `DataPage` (and its `Column`/`MenuItem` helpers) from the **subpath**, no ```tsx import { DataPage, MenuItem } from '@cratis/components/DataPage'; -import { Column } from 'primereact/column'; +import { Column } from '@cratis/components/DataPage'; import { CommandDialog } from '@cratis/components/CommandDialog'; import { useDialog, DialogProps } from '@cratis/arc.react/dialogs'; ``` @@ -27,7 +27,7 @@ import { useDialog, DialogProps } from '@cratis/arc.react/dialogs'; ```tsx import { DataPage } from '@cratis/components/DataPage'; -import { Column } from 'primereact/column'; +import { Column } from '@cratis/components/DataPage'; import { AllAccounts } from './AllAccounts'; export const AccountsPage = () => ( @@ -68,7 +68,7 @@ export const CreateAccountDialog = ({ closeDialog }: DialogProps) => ( ```tsx import { DataPage, MenuItem } from '@cratis/components/DataPage'; -import { Column } from 'primereact/column'; +import { Column } from '@cratis/components/DataPage'; import { useDialog } from '@cratis/arc.react/dialogs'; import { CreateAccountDialog } from './CreateAccountDialog'; diff --git a/.ai/skills/new-vertical-slice/references/PATTERNS.md b/.ai/skills/new-vertical-slice/references/PATTERNS.md index b8b68bf..c6e132e 100644 --- a/.ai/skills/new-vertical-slice/references/PATTERNS.md +++ b/.ai/skills/new-vertical-slice/references/PATTERNS.md @@ -244,7 +244,7 @@ public record StockDecreased(ISBN Isbn, BookStock StockBeforeDecrease); // Copyright (c) Cratis. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -import { Column } from 'primereact/column'; +import { Column } from '@cratis/components/DataPage'; import { DataTable } from 'primereact/datatable'; import { AllProjects } from './AllProjects'; @@ -318,8 +318,7 @@ export const AddProject = ({ closeDialog }: DialogProps) => { // Licensed under the MIT license. See LICENSE file in the project root for full license information. import { DialogResult, useDialog } from '@cratis/arc.react/dialogs'; -import { Menubar } from 'primereact/menubar'; -import { MenuItem } from 'primereact/menuitem'; +import { Button } from 'primereact/button'; import * as mdIcons from 'react-icons/md'; import { Page } from '@cratis/components/Common'; import { AddProject } from './Registration/AddProject'; @@ -328,17 +327,15 @@ import { Listing } from './Listing/Listing'; export const Projects = () => { const [AddProjectDialog, showAddProjectDialog] = useDialog(AddProject); - const menuItems: MenuItem[] = [ - { - label: 'Add Project', - icon: mdIcons.MdAdd, - command: async () => { await showAddProjectDialog(); } - } - ]; - + // PrimeReact 11 removed the standalone Menubar; for a query-backed list + // page prefer `DataPage` + ``, or compose `Button`s + // (content is children in v11) for a custom toolbar. return ( - + diff --git a/.ai/skills/stepper-command-dialog/SKILL.md b/.ai/skills/stepper-command-dialog/SKILL.md index 5cca013..3c8f1be 100644 --- a/.ai/skills/stepper-command-dialog/SKILL.md +++ b/.ai/skills/stepper-command-dialog/SKILL.md @@ -36,7 +36,7 @@ Run a Release `dotnet build` to generate the `CreateProject` TypeScript proxy be ```tsx import { StepperCommandDialog } from '@cratis/components/CommandDialog'; -import { StepperPanel } from 'primereact/stepperpanel'; +import { StepperPanel } from '@cratis/components/CommandDialog'; import { InputTextField, TextAreaField, NumberField } from '@cratis/components/CommandForm/fields'; import { DialogResult, useDialogContext } from '@cratis/arc.react/dialogs'; import { CreateProject } from '../api/Projects/CreateProject'; diff --git a/Documentation/CommandForm/password-field.md b/Documentation/CommandForm/password-field.md new file mode 100644 index 0000000..1f3913a --- /dev/null +++ b/Documentation/CommandForm/password-field.md @@ -0,0 +1,31 @@ +# PasswordField + +`PasswordField` provides a masked text entry backed by the PrimeReact `InputPassword` component, bound to a `string` property on a command. + +## Usage + +```tsx +import { CommandDialog } from '@cratis/components/CommandDialog'; +import { PasswordField } from '@cratis/components/CommandForm'; + + setVisible(false)}> + value={c => c.password} placeholder="At least 8 characters" /> + +``` + +## Props + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `value` | `(instance: TCommand) => unknown` | — | **Required.** Accessor that returns the bound property from the command instance. Pass the command type as the generic parameter for full type safety. | +| `placeholder` | `string` | — | Placeholder text shown when the field is empty. | +| `className` | `string` | — | Extra CSS class combined with the default `w-full`. | +| `pt` | `InputPasswordProps['pt']` | — | PrimeReact pass-through configuration for the underlying `InputPassword`. | +| `ptOptions` | `InputPasswordProps['ptOptions']` | — | PrimeReact pass-through options. | +| `unstyled` | `boolean` | `false` | Disables every base PrimeReact style on the underlying `InputPassword`. | + +## Behavior + +- Default value is an empty string. +- The input masks its content; the underlying `InputPassword` provides the show/hide affordance. +- Validation state is reflected via the PrimeReact `invalid` flag. diff --git a/Documentation/CommandForm/rating-field.md b/Documentation/CommandForm/rating-field.md new file mode 100644 index 0000000..945d78d --- /dev/null +++ b/Documentation/CommandForm/rating-field.md @@ -0,0 +1,32 @@ +# RatingField + +`RatingField` provides a star-rating input backed by the PrimeReact `Rating` component, bound to a `number` property on a command. + +## Usage + +```tsx +import { CommandDialog } from '@cratis/components/CommandDialog'; +import { RatingField } from '@cratis/components/CommandForm'; + + setVisible(false)}> + value={c => c.rating} stars={5} /> + +``` + +## Props + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `value` | `(instance: TCommand) => unknown` | — | **Required.** Accessor that returns the bound property from the command instance. Pass the command type as the generic parameter for full type safety. | +| `stars` | `number` | `5` | Number of stars to display. | +| `starAriaLabel` | `(starValue: number) => string` | `` n => `${n} star(s)` `` | Builds the accessible name for each star. Override to localize. | +| `className` | `string` | — | Extra CSS class name. | +| `pt` | `RatingRootProps['pt']` | — | PrimeReact pass-through configuration. | +| `ptOptions` | `RatingRootProps['ptOptions']` | — | PrimeReact pass-through options. | +| `unstyled` | `boolean` | `false` | Disables every base PrimeReact style on the underlying `Rating`. | + +## Behavior + +- Default value is `0` (no rating selected). +- The bound value is the selected star count (`1`–`stars`). +- Validation state is reflected via the PrimeReact `invalid` flag. diff --git a/Documentation/CommandForm/toc.yml b/Documentation/CommandForm/toc.yml index 5e7cefb..208bc81 100644 --- a/Documentation/CommandForm/toc.yml +++ b/Documentation/CommandForm/toc.yml @@ -16,11 +16,17 @@ href: multi-select-field.md - name: NumberField href: number-field.md +- name: PasswordField + href: password-field.md - name: RadioButtonField href: radio-button-field.md - name: RadioGroupField href: radio-group-field.md +- name: RatingField + href: rating-field.md - name: SliderField href: slider-field.md - name: TextAreaField href: text-area-field.md +- name: ToggleSwitchField + href: toggle-switch-field.md diff --git a/Documentation/CommandForm/toggle-switch-field.md b/Documentation/CommandForm/toggle-switch-field.md new file mode 100644 index 0000000..c3841b8 --- /dev/null +++ b/Documentation/CommandForm/toggle-switch-field.md @@ -0,0 +1,31 @@ +# ToggleSwitchField + +`ToggleSwitchField` provides an on/off switch backed by the PrimeReact `ToggleSwitch` component, bound to a `boolean` property on a command. It is the switch-styled counterpart of [CheckboxField](./checkbox-field.md). + +## Usage + +```tsx +import { CommandDialog } from '@cratis/components/CommandDialog'; +import { ToggleSwitchField } from '@cratis/components/CommandForm'; + + setVisible(false)}> + value={c => c.notificationsEnabled} label="Enable notifications" /> + +``` + +## Props + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `value` | `(instance: TCommand) => unknown` | — | **Required.** Accessor that returns the bound property from the command instance. Pass the command type as the generic parameter for full type safety. | +| `label` | `string` | — | Text displayed next to the switch. It is the switch's accessible name — override it to localize. | +| `className` | `string` | — | Extra CSS class forwarded to the underlying `ToggleSwitch`. | +| `pt` | `ToggleSwitchRootProps['pt']` | — | PrimeReact pass-through configuration. | +| `ptOptions` | `ToggleSwitchRootProps['ptOptions']` | — | PrimeReact pass-through options. | +| `unstyled` | `boolean` | `false` | Disables every base PrimeReact style on the underlying `ToggleSwitch`. | + +## Behavior + +- Default value is `false`. +- The switch is wrapped in a `