@dextinity/agent-features 10.0.1-canary-20260814073232 → 10.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.
Files changed (38) hide show
  1. package/package.json +6 -1
  2. package/rules/coding-guidelines/api-nestjs.instructions.md +107 -0
  3. package/rules/coding-guidelines/cdn.instructions.md +24 -0
  4. package/rules/coding-guidelines/general.instructions.md +30 -0
  5. package/rules/coding-guidelines/git.instructions.md +37 -0
  6. package/rules/coding-guidelines/kubernetes.instructions.md +59 -0
  7. package/rules/coding-guidelines/libraries.instructions.md +34 -0
  8. package/rules/coding-guidelines/naming.instructions.md +39 -0
  9. package/rules/coding-guidelines/postgresql.instructions.md +40 -0
  10. package/rules/coding-guidelines/react.instructions.md +102 -0
  11. package/rules/coding-guidelines/security.instructions.md +44 -0
  12. package/rules/coding-guidelines/styling.instructions.md +50 -0
  13. package/rules/coding-guidelines/typescript.instructions.md +50 -0
  14. package/skills/.gitkeep +0 -0
  15. package/skills/dev-pm/SKILL.md +100 -0
  16. package/skills/dextinity-admin-ui/SKILL.md +544 -0
  17. package/skills/dextinity-block/SKILL.md +252 -0
  18. package/skills/dextinity-block/references/admin-patterns.md +192 -0
  19. package/skills/dextinity-block/references/api-patterns.md +183 -0
  20. package/skills/dextinity-block/references/block-loader.md +368 -0
  21. package/skills/dextinity-block/references/block-types.md +210 -0
  22. package/skills/dextinity-block/references/custom-block-field.md +266 -0
  23. package/skills/dextinity-block/references/fixtures.md +436 -0
  24. package/skills/dextinity-block/references/image.md +341 -0
  25. package/skills/dextinity-block/references/migration.md +597 -0
  26. package/skills/dextinity-block/references/registration.md +167 -0
  27. package/skills/dextinity-block/references/response-summary.md +102 -0
  28. package/skills/dextinity-block/references/rich-text.md +309 -0
  29. package/skills/dextinity-block/references/select.md +176 -0
  30. package/skills/dextinity-block/references/site-patterns.md +202 -0
  31. package/skills/dextinity-core-admin-component-authoring/SKILL.md +92 -0
  32. package/skills/dextinity-mail-react/SKILL.md +647 -0
  33. package/skills/dextinity-mail-react/references/components-and-theme.md +448 -0
  34. package/skills/dextinity-mail-react/references/layout-patterns.md +315 -0
  35. package/skills/dextinity-mail-react/references/styling-and-customization.md +306 -0
  36. package/skills/dextinity-major-migration/SKILL.md +161 -0
  37. package/skills/dextinity-major-migration/references/migration-smoke-test.md +196 -0
  38. package/skills/dextinity-minor-update/SKILL.md +191 -0
@@ -0,0 +1,100 @@
1
+ ---
2
+ name: dev-pm
3
+ description: Run, restart, stop, and inspect logs/status of long-running development processes via dev-pm (dev-process-manager). Use when starting/stopping/restarting services, tailing logs of those processes, or checking which services are running. Do not use for one-off scripts (`npm run X` is fine for those) — dev-pm is for processes that stay alive.
4
+ ---
5
+
6
+ # dev-pm Skill
7
+
8
+ `dev-pm` (`dev-process-manager`) supervises long-running dev processes (servers, watchers, codegen, storybook, docker, …). Available scripts and groups are defined in `dev-pm.config.ts` at the repo root — read it to discover what can be started.
9
+
10
+ ## Critical rules
11
+
12
+ 1. **Always invoke through the package manager:** `npm exec -- dev-pm <command>`. Never call `dev-pm` directly. If `pnpm` is the active package manager for the project (e.g. a `pnpm-lock.yaml` is present), use `pnpm exec -- dev-pm <command>` instead.
13
+ 2. **Never use streaming flags from a tool call — they hang.** `logs` requires `-n` / `--lines <N>`; `status` must not get `--interval`; `start` / `restart` must not get `--follow`.
14
+ 3. **Services may already be running.** dev-pm runs as a daemon across sessions — check `status` before starting things again. Same applies to build watchers ("the build watcher is running" means dev-pm is supervising a `build:watch` script); if absent, build the affected package manually.
15
+ 4. **dev-pm owns _all_ long-running tasks — including docker.** If a project uses dev-pm, assume every long-lived process (servers, watchers, codegen, **and** `docker compose up`) is wired into `dev-pm.config.ts`. Don't reach for `docker compose up` / `docker compose down` directly — start/stop the corresponding dev-pm script (commonly named `docker`). Exceptions exist; only treat something as outside scope after confirming it's not in `dev-pm.config.ts`.
16
+
17
+ ## Commands
18
+
19
+ Script/group names below are placeholders; the authoritative list is `dev-pm.config.ts`.
20
+
21
+ ### `start [patterns...]`
22
+
23
+ ```bash
24
+ npm exec -- dev-pm start <script> # one script
25
+ npm exec -- dev-pm start @<group> # a group
26
+ npm exec -- dev-pm start <a> <b> # multiple patterns
27
+ ```
28
+
29
+ - `@`-prefix → group name (from `group: [...]` in the config). Bare → script `name`. Globs (minimatch) work too, e.g. `api-*`.
30
+ - Already-running scripts are a no-op — safe to call again. To force a restart, use `restart`.
31
+
32
+ ### `status` / `list`
33
+
34
+ ```bash
35
+ npm exec -- dev-pm status # all scripts
36
+ npm exec -- dev-pm status <pattern> # filter
37
+ ```
38
+
39
+ First stop when troubleshooting "is X running?".
40
+
41
+ **Reading the output:**
42
+
43
+ - **Status:**
44
+ - `Running` — script is up.
45
+ - `Waiting` — `waitOn` condition unmet (e.g. api waiting for the database, site waiting for the api). Will start when the dependency is ready; if it stays here, the dependency failed — check its logs.
46
+ - `Backoff` — process crashed; dev-pm is sleeping before respawn. Wait grows as `min(1.3 ^ restartCount, 10)` seconds, capped at 10s. Flapping `Backoff` means broken — read logs and fix the cause; restarting won't help.
47
+ - `Stopped` — not running (never started or manually stopped).
48
+ - **Restarts:** healthy scripts sit at `0`. Anything `> 0` means dev-pm respawned a crash — pull logs (`logs --lines 300 <name>`).
49
+
50
+ ### `logs` / `log`
51
+
52
+ ```bash
53
+ npm exec -- dev-pm logs --lines 200 <script>
54
+ npm exec -- dev-pm log -n 500 <pattern>
55
+ ```
56
+
57
+ Pick a line count for the question — 100–200 for a quick check, 500+ for startup/error history.
58
+
59
+ ### `restart [patterns...]`
60
+
61
+ ```bash
62
+ npm exec -- dev-pm restart <script>
63
+ npm exec -- dev-pm restart @<group>
64
+ ```
65
+
66
+ Use after rebuilding a package whose consumers cache the old build, or after editing config the running process loaded at startup.
67
+
68
+ ### `stop [patterns...]`
69
+
70
+ ```bash
71
+ npm exec -- dev-pm stop <script>
72
+ npm exec -- dev-pm stop @<group>
73
+ ```
74
+
75
+ Stops the matched scripts; the daemon stays alive.
76
+
77
+ ### `shutdown` / `halt`
78
+
79
+ ```bash
80
+ npm exec -- dev-pm shutdown
81
+ ```
82
+
83
+ Stops everything and shuts down the daemon. Only when the user explicitly asks to "stop everything" / "kill dev-pm". Don't use as a "reset state" hammer — prefer `restart <pattern>`.
84
+
85
+ ## Discovering scripts and groups
86
+
87
+ `dev-pm.config.ts` exports a `scripts` array. Each entry has:
88
+
89
+ - `name` — identifier for start/stop/logs.
90
+ - `group` — group names usable with the `@` prefix.
91
+ - `script` — underlying shell command (informational; don't run directly).
92
+ - `waitOn` — files / TCP ports the script waits for (explains startup ordering).
93
+
94
+ When the user names a service vaguely ("the api"), grep `dev-pm.config.ts` for the matching `name` rather than guessing.
95
+
96
+ ## Common workflows
97
+
98
+ - **"Why isn't service X responding?"** → `status` → `logs --lines 200 <name>` → `restart <name>` after fixing the cause.
99
+ - **"Edited a package, running service isn't picking it up"** → confirm the package's build watcher is in `status`; if not, build the package or start its watcher; then `restart` the consumer.
100
+ - **"Verify service X still boots"** → `start <name>`, poll `logs --lines N` until the ready line or an error appears.
@@ -0,0 +1,544 @@
1
+ ---
2
+ name: dextinity-admin-ui
3
+ description: Building or editing admin UI in a project that uses @dextinity/admin and its sibling packages — pages, dashboards, dialogs, widgets, layouts, or component styling. Use even for small UI changes, to build with Dextinity's theme, components, and helpers instead of custom sx/styled CSS, hard-coded values, or Box layouts.
4
+ ---
5
+
6
+ # Building admin UIs with @dextinity/admin
7
+
8
+ `@dextinity/admin` and its sibling packages ship a design system: a theme (spacing,
9
+ colors, shadows, typography, breakpoints) and a library of ready-made components. For
10
+ internationalization, Dextinity recommends `react-intl` (the default) to translate text, numbers, and
11
+ dates. The components and types are available in the consuming project through the installed
12
+ packages — import them directly (e.g. `import { Button, MainContent } from "@dextinity/admin"`).
13
+
14
+ Build admin UI by composing what the design system already provides. Add custom styling only
15
+ after the system genuinely can't express what you need.
16
+
17
+ ## Core principle
18
+
19
+ **Prefer Dextinity's theme values, components, and helpers over custom styling.** Three reasons:
20
+
21
+ 1. **Reviewability.** When styling lives in the theme and in components, the markup stays
22
+ declarative and diffs stay small. A component that mixes `sx`, inline `style`, and `styled()`
23
+ is hard to read and hard to review — the layout, the styling, and the logic blur together.
24
+ 2. **Automatic upgrades.** A project's visual design is often built against a _future_ version of
25
+ the design system, so it won't fully match what the currently installed components and theme
26
+ produce. That gap is expected — it is not a reason to add custom styling to force the match.
27
+ Use the current Dextinity components and tokens as they are; a later library upgrade closes the gap
28
+ on its own, with no hand-written CSS to find and rework.
29
+ 3. **Consistency.** Every screen built from the same components and tokens looks and behaves the
30
+ same way.
31
+
32
+ This holds **even when a project's design deliberately differs** from the current library defaults —
33
+ a permanent design decision, not a gap a later library upgrade will close. Prefer the Dextinity component
34
+ or token, and apply that project-specific difference by configuring the theme — not by re-styling
35
+ individual components. Add custom styling only when explicitly instructed, or when no component, prop,
36
+ or token can produce the result.
37
+
38
+ ## Decision framework
39
+
40
+ Before writing any styling or markup, work down this list and stop at the first step that applies:
41
+
42
+ 1. **Is there a component for this?** Use it rather than assembling the same thing from
43
+ `Box` + CSS (page structure, cards, toolbars, dialogs, alerts, buttons, …).
44
+ 2. **Is there a prop for this?** Props apply the correct theme values with no CSS — e.g.
45
+ `elevation` / `square` on `Paper` and `Card`, `variant` on `Button` and `Typography`,
46
+ `spacing` on `Stack` and `Grid`.
47
+ 3. **Is there a theme value for this?** Read spacing, colors, and shadows from the theme
48
+ (`theme.spacing(n)`, `theme.palette.*`, `theme.shadows[n]`) instead of hard-coding pixels,
49
+ hex colors, or shadow strings.
50
+ 4. **Is there a helper for this?** User-facing text, numbers, and dates go through the i18n
51
+ helpers (`FormattedMessage`, `FormattedNumber`, `FormattedDate`), never hard-coded.
52
+ 5. **Only then, custom-style** — using `styled()` (not `sx` or inline `style`) and reading
53
+ values from the theme.
54
+
55
+ ## Styling and theme
56
+
57
+ ### Custom styling: `styled()`, not `sx` or inline `style`
58
+
59
+ When you do need custom styling, write it with `styled()` from `@mui/material/styles` and give the
60
+ result a name that says what it is. Styling in `sx` props or inline `style` mixes the look into the
61
+ markup, so layout, styling, and logic blur together and the diff is harder to follow. A named
62
+ styled component keeps the markup declarative and the styling in one place.
63
+
64
+ ```tsx
65
+ // Avoid — sx and inline style mixed into the markup
66
+ <Box sx={{ padding: 2, backgroundColor: "#fff", borderRadius: 1 }} style={{ marginTop: 16 }}>
67
+ {children}
68
+ </Box>;
69
+
70
+ // Prefer — a named styled component, styling separated from markup
71
+ const Panel = styled("div")`
72
+ padding: ${({ theme }) => theme.spacing(2)};
73
+ background-color: ${({ theme }) => theme.palette.background.paper};
74
+ `;
75
+
76
+ <Panel>{children}</Panel>;
77
+ ```
78
+
79
+ ### Spacing and color: theme tokens, not hard-coded values
80
+
81
+ Read spacing and color from the theme instead of typing pixels and hex codes. The theme is the
82
+ single place those values are defined, so reading from it keeps every screen consistent and lets a
83
+ theme change reach all of them at once. Dextinity's spacing base is `5px` — `theme.spacing(1)` is `5px`,
84
+ `theme.spacing(2)` is `10px` — and it takes up to four arguments for top, right, bottom, and left.
85
+
86
+ ```tsx
87
+ // Avoid — hard-coded pixels and colors
88
+ const Header = styled("header")`
89
+ padding: 16px 24px;
90
+ color: #1a1a1a;
91
+ border-bottom: 1px solid #e0e0e0;
92
+ `;
93
+
94
+ // Prefer — spacing and palette tokens from the theme
95
+ const Header = styled("header")`
96
+ padding: ${({ theme }) => theme.spacing(2, 3)};
97
+ color: ${({ theme }) => theme.palette.text.primary};
98
+ border-bottom: 1px solid ${({ theme }) => theme.palette.divider};
99
+ `;
100
+ ```
101
+
102
+ Use the palette tokens — `primary`, `secondary`, `error`, `warning`, `info`, `success`,
103
+ `grey[50…900]`, `divider`, `text`, `background`, `action` — rather than naming raw colors.
104
+
105
+ ### Elevation and shape: `elevation` and `square`, not manual CSS
106
+
107
+ Shadows and corner radius come from props on `Paper` and `Card`, not hand-written CSS. Dextinity defines
108
+ four shadow elevations (1–4); higher values are `none`. The `elevation` prop selects one, and the
109
+ `square` prop toggles the rounded corner. Read `theme.shadows[n]` directly only inside a `styled()`
110
+ component that cannot be a `Paper` or `Card`.
111
+
112
+ ```tsx
113
+ // Avoid — manual shadow and radius on a plain element
114
+ <div style={{ boxShadow: "0 0 8px rgba(0,0,0,0.1)", borderRadius: 4 }}>{children}</div>
115
+
116
+ // Prefer — a Paper carrying the theme's elevation and shape
117
+ <Paper elevation={2}>{children}</Paper>
118
+ ```
119
+
120
+ ### Typography: `<Typography variant>`, not manual font CSS
121
+
122
+ Render text through `<Typography>` with a variant rather than setting font size, weight, and line
123
+ height by hand. The variant carries the type scale and its responsive steps, so headings and body
124
+ text stay in proportion across breakpoints. Available variants: `h1`–`h6`, `body1`, `body2`,
125
+ `subtitle1`, `subtitle2`, `caption`, `overline`, `list`, `listItem`, `button`.
126
+
127
+ ```tsx
128
+ // Avoid — font properties set by hand
129
+ const Title = styled("h2")`
130
+ font-size: 20px;
131
+ font-weight: 600;
132
+ line-height: 26px;
133
+ `;
134
+
135
+ // Prefer — a Typography variant from the type scale
136
+ <Typography variant="h4">{title}</Typography>;
137
+ ```
138
+
139
+ ## Organizing styled components
140
+
141
+ By default, define a component's styled parts at the bottom of its own file, below the component
142
+ that uses them:
143
+
144
+ ```
145
+ imports → types → component → styled components
146
+ ```
147
+
148
+ When a file grows hard to read, refactor the component itself first — split it into smaller
149
+ components and compose them, each keeping its own styled parts at the bottom. Move styles to a
150
+ separate `*.sc.ts` sibling only when you are asked to, or when the styles grow but the component
151
+ cannot be split logically. A `*.sc.ts` file is private to its equally-named component
152
+ (`FooButton.sc.ts` belongs to `FooButton.tsx`). Don't import one component's `*.sc.ts` from another:
153
+ that couples them through styling neither owns. When styling is shared, give it a single owner — a
154
+ reusable component (below).
155
+
156
+ When the same styled component is used by several components, it is no longer a styled part of any
157
+ one of them. Promote it to its own reusable component: one export per file, named exactly as that
158
+ export so it is easy to find, e.g. `SpecialButton.ts` exporting
159
+ `export const SpecialButton = styled(Button)`. Group several small related ones into a single
160
+ generically-named file only when explicitly instructed.
161
+
162
+ ## Internationalization
163
+
164
+ Dextinity recommends `react-intl` (the default). When a project uses it, its user-facing text,
165
+ numbers, and dates go through the `react-intl` helpers instead of being hard-coded.
166
+
167
+ ### Text: `<FormattedMessage>` and `useIntl`, not literals
168
+
169
+ Use `<FormattedMessage>` wherever a ReactNode fits. String attributes (`alt`, `placeholder`,
170
+ `title`, `aria-*`) take a string, not a ReactNode, so translate those with
171
+ `useIntl().formatMessage()`.
172
+
173
+ ```tsx
174
+ // Avoid — hard-coded user-facing text
175
+ <Button>Save</Button>;
176
+ <img src={src} alt="Preview" />;
177
+
178
+ // Prefer — translate through react-intl (formatMessage for string attributes)
179
+ import { FormattedMessage, useIntl } from "react-intl";
180
+
181
+ <Button>
182
+ <FormattedMessage id="product.save" defaultMessage="Save" />
183
+ </Button>;
184
+
185
+ const intl = useIntl();
186
+ <img src={src} alt={intl.formatMessage({ id: "product.previewAlt", defaultMessage: "Preview" })} />;
187
+ ```
188
+
189
+ Interpolate runtime values with `values` rather than concatenating strings, and pluralize with ICU
190
+ syntax rather than by hand:
191
+
192
+ ```tsx
193
+ <FormattedMessage id="product.greeting" defaultMessage="Welcome, {name}" values={{ name }} />;
194
+
195
+ // # is the formatted count
196
+ <FormattedMessage id="product.cartCount" defaultMessage="{count, plural, one {# item in cart} other {# items in cart}}" values={{ count }} />;
197
+ ```
198
+
199
+ ### Numbers: `<FormattedNumber>` / `intl.formatNumber()`
200
+
201
+ Format plain numbers, currency, and percentages through react-intl so grouping, decimals, and
202
+ symbols follow the active locale rather than a hand-written format.
203
+
204
+ ```tsx
205
+ // Avoid — raw or hand-formatted numbers (no locale grouping, decimals, or symbol)
206
+ <span>{count}</span>;
207
+ <span>{`${price.toFixed(2)} €`}</span>;
208
+ <span>{`${Math.round(ratio * 100)}%`}</span>;
209
+
210
+ // Prefer — locale-aware grouping and decimals, currency, and percent
211
+ <FormattedNumber value={count} />;
212
+ <FormattedNumber value={price} style="currency" currency="EUR" />;
213
+ <FormattedNumber value={ratio} style="percent" />;
214
+ ```
215
+
216
+ ### Dates and times: `<FormattedDate>` / `<FormattedTime>`
217
+
218
+ Render dates and times through react-intl so they follow the active locale rather than a
219
+ hand-built format.
220
+
221
+ ```tsx
222
+ // Avoid — hand-built date and time strings
223
+ <span>{date.toLocaleDateString("en-US")}</span>;
224
+ <span>{date.toLocaleTimeString("en-US")}</span>;
225
+
226
+ // Prefer — locale-aware formatting
227
+ <FormattedDate value={date} year="numeric" month="long" day="numeric" />;
228
+ <FormattedTime value={date} />;
229
+ ```
230
+
231
+ ## Layout
232
+
233
+ ### Arranging elements: `Stack` and `Grid`, not `Box` with margins
234
+
235
+ To arrange children and the space between them, use MUI's `Stack` (one-dimensional flow with a
236
+ `spacing` prop) and `Grid` (responsive columns with `spacing` and `size`), imported from
237
+ `@mui/material`. Both apply spacing from the theme through props, so you never hand-write the
238
+ gaps. Use `Box` with manual `margin` only when neither fits.
239
+
240
+ Pure layout — arranging children and the gaps between them — is fine inline through these props
241
+ and needs no `styled()`. Anything beyond that (padding inside an element, background, borders,
242
+ and other visual styling) goes through the theme and `styled()`, as in the styling section
243
+ above.
244
+
245
+ ```tsx
246
+ // Avoid — Box with hand-written margins between children
247
+ <Box>
248
+ <Widget />
249
+ <Box sx={{ marginTop: 16 }}>
250
+ <Widget />
251
+ </Box>
252
+ <Box sx={{ marginTop: 16 }}>
253
+ <Widget />
254
+ </Box>
255
+ </Box>;
256
+
257
+ // Prefer — Stack with spacing from the theme
258
+ <Stack spacing={4}>
259
+ <Widget />
260
+ <Widget />
261
+ <Widget />
262
+ </Stack>;
263
+ ```
264
+
265
+ ```tsx
266
+ // Avoid — manual flex and width math for a responsive two-column layout
267
+ <Box sx={{ display: "flex", flexWrap: "wrap" }}>
268
+ <Box sx={{ width: "50%" }}>
269
+ <Widget />
270
+ </Box>
271
+ <Box sx={{ width: "50%" }}>
272
+ <Widget />
273
+ </Box>
274
+ </Box>;
275
+
276
+ // Prefer — Grid with responsive size and spacing from the theme
277
+ <Grid container spacing={4}>
278
+ <Grid size={{ xs: 12, md: 6 }}>
279
+ <Widget />
280
+ </Grid>
281
+ <Grid size={{ xs: 12, md: 6 }}>
282
+ <Widget />
283
+ </Grid>
284
+ </Grid>;
285
+ ```
286
+
287
+ For responsive behaviour, pass a per-breakpoint object to these props (`size` on `Grid`,
288
+ `direction` on `Stack`); inside a `styled()` component, use `theme.breakpoints` for media
289
+ queries.
290
+
291
+ Don't use `Grid` or `Stack` to lay out form fields: Dextinity stacks them vertically at full width,
292
+ grouped with `FieldSet` or `FormSection`.
293
+
294
+ `sx` is fine for an occasional layout property that no `Stack` or `Grid` prop covers, such as
295
+ `flexGrow`. On your own markup, it is not for visual styling — that goes through `styled()`.
296
+
297
+ ### Page structure: `MainContent`, `Toolbar`, and their parts
298
+
299
+ Wrap a page's body in `MainContent` rather than a hand-padded `Box` — it applies the standard
300
+ page padding and can fill the height with `fullHeight`. Build the action bar from `Toolbar` and
301
+ its parts instead of assembling one from a flex row:
302
+
303
+ - `ToolbarTitleItem` holds the page title, `ToolbarActions` the action buttons; `ToolbarItem` is
304
+ the generic slot for anything else.
305
+ - `FillSpace` is a flexbox spacer that fills the free space, moving the elements after it to the
306
+ end.
307
+
308
+ ```tsx
309
+ // Avoid — hand-built toolbar and padded container
310
+ <Box sx={{ display: "flex", padding: 16 }}>
311
+ <Typography variant="h4">{title}</Typography>
312
+ <Box sx={{ marginLeft: "auto" }}>
313
+ <Button>{addLabel}</Button>
314
+ </Box>
315
+ </Box>;
316
+
317
+ // Prefer — Toolbar parts and MainContent
318
+ <Toolbar>
319
+ <ToolbarTitleItem>{title}</ToolbarTitleItem>
320
+ <FillSpace />
321
+ <ToolbarActions>
322
+ <Button>{addLabel}</Button>
323
+ </ToolbarActions>
324
+ </Toolbar>;
325
+ <MainContent>{children}</MainContent>;
326
+ ```
327
+
328
+ When a page is rendered inside a Dextinity navigation `Stack` (nested master–detail views), use the
329
+ `StackMainContent` and `StackToolbar` variants instead: they render only for the active stack
330
+ level, so nested pages don't show duplicate toolbars.
331
+
332
+ ### Full-height content: `fullHeight` and `FullHeightContent`
333
+
334
+ Content that should fill the viewport and scroll inside itself — most often a `DataGrid` — needs
335
+ a height-bounded parent, or it grows the whole page instead of scrolling. Set that height through
336
+ the page structure, not a hand-written `height` that has to track the header and toolbar offset:
337
+
338
+ - When the grid is the page's direct content, add `fullHeight` to `MainContent` or
339
+ `StackMainContent`.
340
+ - When the grid is nested inside `RouterTabs` or other content rather than placed directly in
341
+ `MainContent`, wrap it in `FullHeightContent`, which bounds the height at that level.
342
+ - When the grid holds few rows, give the `DataGrid` the `autoHeight` prop instead and skip
343
+ `fullHeight`.
344
+
345
+ ```tsx
346
+ // Avoid — a hand-set height that has to track the header and toolbar offset
347
+ <MainContent>
348
+ <Box sx={{ height: "calc(100vh - 200px)" }}>
349
+ <DataGrid />
350
+ </Box>
351
+ </MainContent>;
352
+
353
+ // Prefer — fullHeight for a grid that is the page's direct content
354
+ <StackMainContent fullHeight>
355
+ <DataGrid />
356
+ </StackMainContent>;
357
+
358
+ // Prefer — FullHeightContent for a grid nested inside tabs
359
+ <MainContent>
360
+ <RouterTabs>
361
+ <RouterTab path="" label={label}>
362
+ <FullHeightContent>
363
+ <DataGrid />
364
+ </FullHeightContent>
365
+ </RouterTab>
366
+ </RouterTabs>
367
+ </MainContent>;
368
+ ```
369
+
370
+ ## Components
371
+
372
+ ### Containers and widgets: `FieldSet`, `FormSection`, and themed `Card`, not `Box`
373
+
374
+ To group related content, use a container component instead of a `Box` with hand-set padding,
375
+ borders, and a title. `FieldSet` (from `@dextinity/admin`) is a collapsible titled panel — wrap a page
376
+ form's fields in it. Inside a dialog or sidebar, group fields with `FormSection` instead, a lighter
377
+ titled section with a divider. For a dashboard widget, if the project uses `@dextinity/cms-admin` (most
378
+ do), use its ready-made `DashboardWidgetRoot` rather than building one by hand; when you compose a
379
+ container yourself, build it from MUI's `Card` (with `CardHeader` and `CardContent`) or `Paper`, with
380
+ `Typography` for text and `Grid` for layout — Dextinity themes `Card`, `Paper`, and `Typography`, so they
381
+ carry the right elevation, radius, and type scale without custom CSS, while `Grid` takes its spacing
382
+ from the theme. `Card`, `CardHeader`, `CardContent`, `Paper`, `Typography`, and `Grid` come from
383
+ `@mui/material`; `FieldSet` and `FormSection` from `@dextinity/admin`.
384
+
385
+ ```tsx
386
+ // Avoid — a Box hand-styled into a titled, bordered panel
387
+ <Box sx={{ border: "1px solid #e0e0e0", borderRadius: 1, padding: 2 }}>
388
+ <Typography variant="h4">{title}</Typography>
389
+ {children}
390
+ </Box>;
391
+
392
+ // Prefer — FieldSet groups content under a title (collapsible by default)
393
+ <FieldSet title={title} supportText={supportText}>
394
+ {children}
395
+ </FieldSet>;
396
+
397
+ // Prefer — a dashboard widget from @dextinity/cms-admin's ready-made container
398
+ <DashboardWidgetRoot header={title}>{children}</DashboardWidgetRoot>;
399
+ ```
400
+
401
+ ### Buttons: `Button` variants and action buttons, not hand-styled buttons
402
+
403
+ Give `Button` a `variant` rather than styling a button by hand or setting MUI's `color` directly.
404
+ The variants are `primary`, `secondary`, `outlined`, `destructive`, `success`, `textLight`, and
405
+ `textDark`. For common actions, prefer the specialized buttons: `SaveButton`, `CancelButton`,
406
+ `DeleteButton`, and `OkayButton` each carry a suitable variant, an icon, and a translated label.
407
+ `SaveButton` also has built-in loading, success, and error feedback (it is a `FeedbackButton`), so
408
+ you don't hand-build that; use `FeedbackButton` for other async actions, and `CopyToClipboardButton`
409
+ to copy text with a confirmation.
410
+
411
+ ```tsx
412
+ // Avoid — a hand-styled button, and a Save button rebuilt from a plain Button
413
+ <button style={{ background: "#c00", color: "#fff" }} onClick={onDelete}>
414
+ Delete
415
+ </button>;
416
+ <Button variant="primary" startIcon={<Save />}>
417
+ Save
418
+ </Button>;
419
+
420
+ // Prefer — specialized buttons carry variant, icon, and label (SaveButton adds save feedback)
421
+ <DeleteButton onClick={onDelete} />;
422
+ <SaveButton onClick={onSave} />;
423
+ ```
424
+
425
+ ### Date and time: pickers from `@dextinity/admin`, not raw inputs
426
+
427
+ Enter dates and times through the picker components rather than a plain text input or an MUI picker
428
+ configured by hand. `@dextinity/admin` exports `DatePicker`, `DateTimePicker`, and `TimePicker` (with
429
+ `DateRangePicker` and `DateTimeRangePicker` for ranges), plus `DatePickerField` and siblings for use
430
+ as Final Form fields. Each picker
431
+ manages its own value format — `DatePicker`, for example, reads and writes an ISO `YYYY-MM-DD`
432
+ string — so you don't parse or format dates by hand. The pickers need MUI X's `LocalizationProvider`
433
+ at the app root, set up once with `AdapterDateFns`; pass `adapterLocale` to localize.
434
+
435
+ ```tsx
436
+ // Avoid — a plain text input used as a date field
437
+ <input type="text" value={value} onChange={(event) => onChange(event.target.value)} />;
438
+
439
+ // Prefer — a DatePicker working with ISO date strings
440
+ <DatePicker value={value} onChange={onChange} />;
441
+
442
+ // The pickers need a LocalizationProvider at the app root, set up once
443
+ import { LocalizationProvider } from "@mui/x-date-pickers";
444
+ import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
445
+
446
+ <LocalizationProvider dateAdapter={AdapterDateFns} adapterLocale={locale}>
447
+ {children}
448
+ </LocalizationProvider>;
449
+ ```
450
+
451
+ ### Feedback and overlays: `Alert`, `Loading`, `Dialog`, `Tooltip`, not hand-built ones
452
+
453
+ Show status, loading, dialogs, and tooltips through the components rather than assembling them from
454
+ `div`s and state. `Alert` takes a `severity` (`info`, `warning`, `error`, `success`), a `title`, an
455
+ `action`, and an `onClose`. `Loading` renders the standard spinner; its `behavior` prop (`auto`,
456
+ `fillParent`, `fillParentAbsolute`, `fillPageHeight`) sets whether it renders inline, fills its
457
+ parent, or fills the page. Use `Dialog` and `Tooltip` from `@dextinity/admin` — Dextinity's own wrappers, not
458
+ MUI's directly. For a transient confirmation, call `showSnackbar()` from `useSnackbarApi()` with a
459
+ snackbar element — Dextinity's `UndoSnackbar`, or a MUI `Snackbar` wrapping an `Alert` — and mount
460
+ `SnackbarProvider` near the app root.
461
+
462
+ ```tsx
463
+ // Avoid — a hand-built alert box and a hand-built spinner
464
+ <div style={{ background: "#fdecea", padding: 12 }}>{errorMessage}</div>;
465
+ {
466
+ loading && <div className="spinner" />;
467
+ }
468
+
469
+ // Prefer — Alert shows severity; Loading renders the standard spinner
470
+ <Alert severity="error">{errorMessage}</Alert>;
471
+ {
472
+ loading && <Loading />;
473
+ }
474
+ ```
475
+
476
+ ### Icons: `@dextinity/admin-icons`, not ad-hoc SVGs
477
+
478
+ Take icons from `@dextinity/admin-icons` rather than importing SVG files or an arbitrary
479
+ icon from another set, so they match the design system and stay consistent. Each icon is a named
480
+ export built on MUI's `SvgIcon`, so size it with the `fontSize` prop (`small`, `medium`, `large`) and
481
+ color it with the `color` prop — the icons use `currentColor`.
482
+
483
+ ```tsx
484
+ // Avoid — an imported SVG file, or an arbitrary icon from another set
485
+ import deleteIcon from "./delete.svg";
486
+
487
+ <img src={deleteIcon} width={16} alt="" />;
488
+
489
+ // Prefer — a named icon from the Dextinity set, sized and colored through props
490
+ import { Delete } from "@dextinity/admin-icons";
491
+
492
+ <Delete fontSize="small" color="error" />;
493
+ ```
494
+
495
+ ## Customizing an existing Dextinity Admin component
496
+
497
+ When a Dextinity Admin component needs to look or behave differently than its defaults, customize it
498
+ through the mechanisms it already supports: the component's own props, a per-slot `slotProps` prop,
499
+ and theme-level `styleOverrides` and `defaultProps`. A _slot_ is a named inner element of a component;
500
+ only `root` is universal — a component lists its own slot names on its props type. Which mechanism you
501
+ pick depends on whether you're changing behavior or appearance.
502
+
503
+ ### Behavior
504
+
505
+ Start with the component's own props. To reach an inner element, use `slotProps`: it forwards props
506
+ to a named slot, letting you override that slot's props. This configures the component rather than
507
+ restyling it.
508
+
509
+ ```tsx
510
+ // Override an inner slot's props — here, disable the button behind the content
511
+ <ContentOverflow slotProps={{ clickableContent: { disabled: true } }}>{children}</ContentOverflow>
512
+ ```
513
+
514
+ ### Appearance
515
+
516
+ To change how a Dextinity component looks, configure the theme rather than the component itself — the core
517
+ principle's rule for a project-specific design difference. `createDextinityTheme`'s `components` map takes
518
+ a `DextinityAdmin*` key (MUI components use their `Mui*` key); `styleOverrides` restyles a component's
519
+ slots and `defaultProps` sets default prop values, such as swapping an icon through `iconMapping`.
520
+ Both apply to every instance, so the deviation stays consistent and is defined in one reviewable place.
521
+
522
+ Styling a single instance directly is the rare exception — for a change genuinely specific to that
523
+ instance, or when you're explicitly told to. Which tool you use is then the same as anywhere else:
524
+ `sx` (or `slotProps.<slot>.sx` for a slot) for small layout, `styled()` for other custom styling, per
525
+ the styling section above. Use this only for a true single-instance change; a difference that recurs
526
+ belongs in the theme, not repeated on each instance.
527
+
528
+ ```tsx
529
+ // Avoid — a styled() wrapper for a design difference that belongs in the theme
530
+ const HighlightedContentOverflow = styled(ContentOverflow)`
531
+ background-color: ${({ theme }) => theme.palette.grey[50]};
532
+ `;
533
+
534
+ // Prefer — configure the theme, applied to every instance
535
+ const theme = createDextinityTheme({
536
+ components: {
537
+ DextinityAdminContentOverflow: {
538
+ styleOverrides: {
539
+ root: ({ theme }) => ({ backgroundColor: theme.palette.grey[50] }),
540
+ },
541
+ },
542
+ },
543
+ });
544
+ ```