@dextinity/agent-features 2.0.0-canary-20260729062014
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/LICENSE +24 -0
- package/package.json +19 -0
- package/rules/coding-guidelines/api-nestjs.instructions.md +107 -0
- package/rules/coding-guidelines/cdn.instructions.md +24 -0
- package/rules/coding-guidelines/general.instructions.md +30 -0
- package/rules/coding-guidelines/git.instructions.md +37 -0
- package/rules/coding-guidelines/kubernetes.instructions.md +59 -0
- package/rules/coding-guidelines/libraries.instructions.md +34 -0
- package/rules/coding-guidelines/naming.instructions.md +39 -0
- package/rules/coding-guidelines/postgresql.instructions.md +40 -0
- package/rules/coding-guidelines/react.instructions.md +102 -0
- package/rules/coding-guidelines/security.instructions.md +44 -0
- package/rules/coding-guidelines/styling.instructions.md +50 -0
- package/rules/coding-guidelines/typescript.instructions.md +50 -0
- package/skills/.gitkeep +0 -0
- package/skills/comet-admin-ui/SKILL.md +492 -0
- package/skills/comet-block/SKILL.md +252 -0
- package/skills/comet-block/references/admin-patterns.md +192 -0
- package/skills/comet-block/references/api-patterns.md +183 -0
- package/skills/comet-block/references/block-loader.md +368 -0
- package/skills/comet-block/references/block-types.md +210 -0
- package/skills/comet-block/references/custom-block-field.md +266 -0
- package/skills/comet-block/references/fixtures.md +436 -0
- package/skills/comet-block/references/image.md +341 -0
- package/skills/comet-block/references/migration.md +597 -0
- package/skills/comet-block/references/registration.md +167 -0
- package/skills/comet-block/references/response-summary.md +102 -0
- package/skills/comet-block/references/rich-text.md +309 -0
- package/skills/comet-block/references/select.md +176 -0
- package/skills/comet-block/references/site-patterns.md +202 -0
- package/skills/comet-core-admin-component-authoring/SKILL.md +92 -0
- package/skills/comet-mail-react/SKILL.md +621 -0
- package/skills/comet-mail-react/references/components-and-theme.md +431 -0
- package/skills/comet-mail-react/references/layout-patterns.md +315 -0
- package/skills/comet-mail-react/references/styling-and-customization.md +306 -0
- package/skills/comet-major-migration/SKILL.md +161 -0
- package/skills/comet-major-migration/references/migration-smoke-test.md +196 -0
- package/skills/comet-minor-update/SKILL.md +191 -0
- package/skills/dev-pm/SKILL.md +100 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: TypeScript style — options objects, named exports, async/await, enums
|
|
3
|
+
applyTo: "**/*.ts,**/*.tsx"
|
|
4
|
+
paths:
|
|
5
|
+
- "**/*.{ts,tsx}"
|
|
6
|
+
globs:
|
|
7
|
+
- "**/*.{ts,tsx}"
|
|
8
|
+
alwaysApply: false
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# TypeScript Rules
|
|
12
|
+
|
|
13
|
+
## Function signatures
|
|
14
|
+
|
|
15
|
+
- If a function takes **more than 2** parameters, use a single options object. This makes call sites self-documenting and avoids argument-order mistakes.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
// Bad
|
|
19
|
+
getSortedJobs("createdAt", true, 20);
|
|
20
|
+
|
|
21
|
+
// Good
|
|
22
|
+
getSortedJobs({ orderBy: "createdAt", includeSoftDeleted: true, limit: 20 });
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
- Use default argument values (`function f(name = "X")`) instead of `name || "X"` or conditional fallbacks inside the body.
|
|
26
|
+
- Prefer `param?: T` over `param: T | undefined` — the optional form does not require callers to explicitly pass `undefined`.
|
|
27
|
+
|
|
28
|
+
## Imports / exports
|
|
29
|
+
|
|
30
|
+
- Use **named exports only**. Default exports are allowed only when technically required (e.g. a framework demands it).
|
|
31
|
+
- Use relative imports only for **sibling or child** files within the same module. Everything else uses the `@src/…` alias.
|
|
32
|
+
|
|
33
|
+
## Async & iteration
|
|
34
|
+
|
|
35
|
+
- Prefer `async/await` over raw callbacks or `.then()` chains.
|
|
36
|
+
- Prefer `for…of` over `Array.prototype.forEach` — it supports `await`, `break`, `continue`, and all iterables.
|
|
37
|
+
|
|
38
|
+
## Enums
|
|
39
|
+
|
|
40
|
+
- Enum **keys** and **values** use `camelCase`, and keys must equal their values.
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
enum Direction {
|
|
44
|
+
north = "north",
|
|
45
|
+
northEast = "northEast",
|
|
46
|
+
// …
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
- GraphQL-exposed enums follow a different rule — see [api-nestjs.instructions.md](api-nestjs.instructions.md#graphql-enums).
|
package/skills/.gitkeep
ADDED
|
File without changes
|
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: comet-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 Comet'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, Comet 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 Comet'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 Comet 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
|
|
33
|
+
defaults: prefer the Comet component or token, and apply that project-specific difference by
|
|
34
|
+
configuring the theme — not by re-styling individual components. Add custom styling only when
|
|
35
|
+
explicitly instructed, or when no component, prop, or token can produce the result.
|
|
36
|
+
|
|
37
|
+
## Decision framework
|
|
38
|
+
|
|
39
|
+
Before writing any styling or markup, work down this list and stop at the first step that applies:
|
|
40
|
+
|
|
41
|
+
1. **Is there a component for this?** Use it rather than assembling the same thing from
|
|
42
|
+
`Box` + CSS (page structure, cards, toolbars, dialogs, alerts, buttons, …).
|
|
43
|
+
2. **Is there a prop for this?** Props apply the correct theme values with no CSS — e.g.
|
|
44
|
+
`elevation` / `square` on `Paper` and `Card`, `variant` on `Button` and `Typography`,
|
|
45
|
+
`spacing` on `Stack` and `Grid`.
|
|
46
|
+
3. **Is there a theme value for this?** Read spacing, colors, and shadows from the theme
|
|
47
|
+
(`theme.spacing(n)`, `theme.palette.*`, `theme.shadows[n]`) instead of hard-coding pixels,
|
|
48
|
+
hex colors, or shadow strings.
|
|
49
|
+
4. **Is there a helper for this?** User-facing text, numbers, and dates go through the i18n
|
|
50
|
+
helpers (`FormattedMessage`, `FormattedNumber`, `FormattedDate`), never hard-coded.
|
|
51
|
+
5. **Only then, custom-style** — using `styled()` (not `sx` or inline `style`) and reading
|
|
52
|
+
values from the theme.
|
|
53
|
+
|
|
54
|
+
## Styling and theme
|
|
55
|
+
|
|
56
|
+
### Custom styling: `styled()`, not `sx` or inline `style`
|
|
57
|
+
|
|
58
|
+
When you do need custom styling, write it with `styled()` from `@mui/material/styles` and give the
|
|
59
|
+
result a name that says what it is. Styling in `sx` props or inline `style` mixes the look into the
|
|
60
|
+
markup, so layout, styling, and logic blur together and the diff is harder to follow. A named
|
|
61
|
+
styled component keeps the markup declarative and the styling in one place.
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
// Avoid — sx and inline style mixed into the markup
|
|
65
|
+
<Box sx={{ padding: 2, backgroundColor: "#fff", borderRadius: 1 }} style={{ marginTop: 16 }}>
|
|
66
|
+
{children}
|
|
67
|
+
</Box>;
|
|
68
|
+
|
|
69
|
+
// Prefer — a named styled component, styling separated from markup
|
|
70
|
+
const Panel = styled("div")`
|
|
71
|
+
padding: ${({ theme }) => theme.spacing(2)};
|
|
72
|
+
background-color: ${({ theme }) => theme.palette.background.paper};
|
|
73
|
+
`;
|
|
74
|
+
|
|
75
|
+
<Panel>{children}</Panel>;
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Spacing and color: theme tokens, not hard-coded values
|
|
79
|
+
|
|
80
|
+
Read spacing and color from the theme instead of typing pixels and hex codes. The theme is the
|
|
81
|
+
single place those values are defined, so reading from it keeps every screen consistent and lets a
|
|
82
|
+
theme change reach all of them at once. Comet's spacing base is `5px` — `theme.spacing(1)` is `5px`,
|
|
83
|
+
`theme.spacing(2)` is `10px` — and it takes up to four arguments for top, right, bottom, and left.
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
// Avoid — hard-coded pixels and colors
|
|
87
|
+
const Header = styled("header")`
|
|
88
|
+
padding: 16px 24px;
|
|
89
|
+
color: #1a1a1a;
|
|
90
|
+
border-bottom: 1px solid #e0e0e0;
|
|
91
|
+
`;
|
|
92
|
+
|
|
93
|
+
// Prefer — spacing and palette tokens from the theme
|
|
94
|
+
const Header = styled("header")`
|
|
95
|
+
padding: ${({ theme }) => theme.spacing(2, 3)};
|
|
96
|
+
color: ${({ theme }) => theme.palette.text.primary};
|
|
97
|
+
border-bottom: 1px solid ${({ theme }) => theme.palette.divider};
|
|
98
|
+
`;
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Use the palette tokens — `primary`, `secondary`, `error`, `warning`, `info`, `success`,
|
|
102
|
+
`grey[50…900]`, `divider`, `text`, `background`, `action` — rather than naming raw colors.
|
|
103
|
+
|
|
104
|
+
### Elevation and shape: `elevation` and `square`, not manual CSS
|
|
105
|
+
|
|
106
|
+
Shadows and corner radius come from props on `Paper` and `Card`, not hand-written CSS. Comet defines
|
|
107
|
+
four shadow elevations (1–4); higher values are `none`. The `elevation` prop selects one, and the
|
|
108
|
+
`square` prop toggles the rounded corner. Read `theme.shadows[n]` directly only inside a `styled()`
|
|
109
|
+
component that cannot be a `Paper` or `Card`.
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
// Avoid — manual shadow and radius on a plain element
|
|
113
|
+
<div style={{ boxShadow: "0 0 8px rgba(0,0,0,0.1)", borderRadius: 4 }}>{children}</div>
|
|
114
|
+
|
|
115
|
+
// Prefer — a Paper carrying the theme's elevation and shape
|
|
116
|
+
<Paper elevation={2}>{children}</Paper>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Typography: `<Typography variant>`, not manual font CSS
|
|
120
|
+
|
|
121
|
+
Render text through `<Typography>` with a variant rather than setting font size, weight, and line
|
|
122
|
+
height by hand. The variant carries the type scale and its responsive steps, so headings and body
|
|
123
|
+
text stay in proportion across breakpoints. Available variants: `h1`–`h6`, `body1`, `body2`,
|
|
124
|
+
`subtitle1`, `subtitle2`, `caption`, `overline`, `list`, `listItem`, `button`.
|
|
125
|
+
|
|
126
|
+
```tsx
|
|
127
|
+
// Avoid — font properties set by hand
|
|
128
|
+
const Title = styled("h2")`
|
|
129
|
+
font-size: 20px;
|
|
130
|
+
font-weight: 600;
|
|
131
|
+
line-height: 26px;
|
|
132
|
+
`;
|
|
133
|
+
|
|
134
|
+
// Prefer — a Typography variant from the type scale
|
|
135
|
+
<Typography variant="h4">{title}</Typography>;
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Organizing styled components
|
|
139
|
+
|
|
140
|
+
By default, define a component's styled parts at the bottom of its own file, below the component
|
|
141
|
+
that uses them:
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
imports → types → component → styled components
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
When a file grows hard to read, refactor the component itself first — split it into smaller
|
|
148
|
+
components and compose them, each keeping its own styled parts at the bottom. Move styles to a
|
|
149
|
+
separate `*.sc.ts` sibling only when you are asked to, or when the styles grow but the component
|
|
150
|
+
cannot be split logically. A `*.sc.ts` file is private to its equally-named component
|
|
151
|
+
(`FooButton.sc.ts` belongs to `FooButton.tsx`). Don't import one component's `*.sc.ts` from another:
|
|
152
|
+
that couples them through styling neither owns. When styling is shared, give it a single owner — a
|
|
153
|
+
reusable component (below).
|
|
154
|
+
|
|
155
|
+
When the same styled component is used by several components, it is no longer a styled part of any
|
|
156
|
+
one of them. Promote it to its own reusable component: one export per file, named exactly as that
|
|
157
|
+
export so it is easy to find, e.g. `SpecialButton.ts` exporting
|
|
158
|
+
`export const SpecialButton = styled(Button)`. Group several small related ones into a single
|
|
159
|
+
generically-named file only when explicitly instructed.
|
|
160
|
+
|
|
161
|
+
## Internationalization
|
|
162
|
+
|
|
163
|
+
Comet recommends `react-intl` (the default). When a project uses it, its user-facing text,
|
|
164
|
+
numbers, and dates go through the `react-intl` helpers instead of being hard-coded.
|
|
165
|
+
|
|
166
|
+
### Text: `<FormattedMessage>` and `useIntl`, not literals
|
|
167
|
+
|
|
168
|
+
Use `<FormattedMessage>` wherever a ReactNode fits. String attributes (`alt`, `placeholder`,
|
|
169
|
+
`title`, `aria-*`) take a string, not a ReactNode, so translate those with
|
|
170
|
+
`useIntl().formatMessage()`.
|
|
171
|
+
|
|
172
|
+
```tsx
|
|
173
|
+
// Avoid — hard-coded user-facing text
|
|
174
|
+
<Button>Save</Button>;
|
|
175
|
+
<img src={src} alt="Preview" />;
|
|
176
|
+
|
|
177
|
+
// Prefer — translate through react-intl (formatMessage for string attributes)
|
|
178
|
+
import { FormattedMessage, useIntl } from "react-intl";
|
|
179
|
+
|
|
180
|
+
<Button>
|
|
181
|
+
<FormattedMessage id="product.save" defaultMessage="Save" />
|
|
182
|
+
</Button>;
|
|
183
|
+
|
|
184
|
+
const intl = useIntl();
|
|
185
|
+
<img src={src} alt={intl.formatMessage({ id: "product.previewAlt", defaultMessage: "Preview" })} />;
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Interpolate runtime values with `values` rather than concatenating strings, and pluralize with ICU
|
|
189
|
+
syntax rather than by hand:
|
|
190
|
+
|
|
191
|
+
```tsx
|
|
192
|
+
<FormattedMessage id="product.greeting" defaultMessage="Welcome, {name}" values={{ name }} />;
|
|
193
|
+
|
|
194
|
+
// # is the formatted count
|
|
195
|
+
<FormattedMessage id="product.cartCount" defaultMessage="{count, plural, one {# item in cart} other {# items in cart}}" values={{ count }} />;
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Numbers: `<FormattedNumber>` / `intl.formatNumber()`
|
|
199
|
+
|
|
200
|
+
Format plain numbers, currency, and percentages through react-intl so grouping, decimals, and
|
|
201
|
+
symbols follow the active locale rather than a hand-written format.
|
|
202
|
+
|
|
203
|
+
```tsx
|
|
204
|
+
// Avoid — raw or hand-formatted numbers (no locale grouping, decimals, or symbol)
|
|
205
|
+
<span>{count}</span>;
|
|
206
|
+
<span>{`${price.toFixed(2)} €`}</span>;
|
|
207
|
+
<span>{`${Math.round(ratio * 100)}%`}</span>;
|
|
208
|
+
|
|
209
|
+
// Prefer — locale-aware grouping and decimals, currency, and percent
|
|
210
|
+
<FormattedNumber value={count} />;
|
|
211
|
+
<FormattedNumber value={price} style="currency" currency="EUR" />;
|
|
212
|
+
<FormattedNumber value={ratio} style="percent" />;
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Dates and times: `<FormattedDate>` / `<FormattedTime>`
|
|
216
|
+
|
|
217
|
+
Render dates and times through react-intl so they follow the active locale rather than a
|
|
218
|
+
hand-built format.
|
|
219
|
+
|
|
220
|
+
```tsx
|
|
221
|
+
// Avoid — hand-built date and time strings
|
|
222
|
+
<span>{date.toLocaleDateString("en-US")}</span>;
|
|
223
|
+
<span>{date.toLocaleTimeString("en-US")}</span>;
|
|
224
|
+
|
|
225
|
+
// Prefer — locale-aware formatting
|
|
226
|
+
<FormattedDate value={date} year="numeric" month="long" day="numeric" />;
|
|
227
|
+
<FormattedTime value={date} />;
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Layout
|
|
231
|
+
|
|
232
|
+
### Arranging elements: `Stack` and `Grid`, not `Box` with margins
|
|
233
|
+
|
|
234
|
+
To arrange children and the space between them, use MUI's `Stack` (one-dimensional flow with a
|
|
235
|
+
`spacing` prop) and `Grid` (responsive columns with `spacing` and `size`), imported from
|
|
236
|
+
`@mui/material`. Both apply spacing from the theme through props, so you never hand-write the
|
|
237
|
+
gaps. Use `Box` with manual `margin` only when neither fits.
|
|
238
|
+
|
|
239
|
+
Pure layout — arranging children and the gaps between them — is fine inline through these props
|
|
240
|
+
and needs no `styled()`. Anything beyond that (padding inside an element, background, borders,
|
|
241
|
+
and other visual styling) goes through the theme and `styled()`, as in the styling section
|
|
242
|
+
above.
|
|
243
|
+
|
|
244
|
+
```tsx
|
|
245
|
+
// Avoid — Box with hand-written margins between children
|
|
246
|
+
<Box>
|
|
247
|
+
<Widget />
|
|
248
|
+
<Box sx={{ marginTop: 16 }}>
|
|
249
|
+
<Widget />
|
|
250
|
+
</Box>
|
|
251
|
+
<Box sx={{ marginTop: 16 }}>
|
|
252
|
+
<Widget />
|
|
253
|
+
</Box>
|
|
254
|
+
</Box>;
|
|
255
|
+
|
|
256
|
+
// Prefer — Stack with spacing from the theme
|
|
257
|
+
<Stack spacing={4}>
|
|
258
|
+
<Widget />
|
|
259
|
+
<Widget />
|
|
260
|
+
<Widget />
|
|
261
|
+
</Stack>;
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
```tsx
|
|
265
|
+
// Avoid — manual flex and width math for a responsive two-column layout
|
|
266
|
+
<Box sx={{ display: "flex", flexWrap: "wrap" }}>
|
|
267
|
+
<Box sx={{ width: "50%" }}>
|
|
268
|
+
<Widget />
|
|
269
|
+
</Box>
|
|
270
|
+
<Box sx={{ width: "50%" }}>
|
|
271
|
+
<Widget />
|
|
272
|
+
</Box>
|
|
273
|
+
</Box>;
|
|
274
|
+
|
|
275
|
+
// Prefer — Grid with responsive size and spacing from the theme
|
|
276
|
+
<Grid container spacing={4}>
|
|
277
|
+
<Grid size={{ xs: 12, md: 6 }}>
|
|
278
|
+
<Widget />
|
|
279
|
+
</Grid>
|
|
280
|
+
<Grid size={{ xs: 12, md: 6 }}>
|
|
281
|
+
<Widget />
|
|
282
|
+
</Grid>
|
|
283
|
+
</Grid>;
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
For responsive behaviour, pass a per-breakpoint object to these props (`size` on `Grid`,
|
|
287
|
+
`direction` on `Stack`); inside a `styled()` component, use `theme.breakpoints` for media
|
|
288
|
+
queries.
|
|
289
|
+
|
|
290
|
+
Don't use `Grid` or `Stack` to lay out form fields: Comet stacks them vertically at full width,
|
|
291
|
+
grouped with `FieldSet` or `FormSection`.
|
|
292
|
+
|
|
293
|
+
`sx` is fine for an occasional layout property that no `Stack` or `Grid` prop covers, such as
|
|
294
|
+
`flexGrow`. It is not for visual styling — that goes through `styled()`.
|
|
295
|
+
|
|
296
|
+
### Page structure: `MainContent`, `Toolbar`, and their parts
|
|
297
|
+
|
|
298
|
+
Wrap a page's body in `MainContent` rather than a hand-padded `Box` — it applies the standard
|
|
299
|
+
page padding and can fill the height with `fullHeight`. Build the action bar from `Toolbar` and
|
|
300
|
+
its parts instead of assembling one from a flex row:
|
|
301
|
+
|
|
302
|
+
- `ToolbarTitleItem` holds the page title, `ToolbarActions` the action buttons; `ToolbarItem` is
|
|
303
|
+
the generic slot for anything else.
|
|
304
|
+
- `FillSpace` is a flexbox spacer that fills the free space, moving the elements after it to the
|
|
305
|
+
end.
|
|
306
|
+
|
|
307
|
+
```tsx
|
|
308
|
+
// Avoid — hand-built toolbar and padded container
|
|
309
|
+
<Box sx={{ display: "flex", padding: 16 }}>
|
|
310
|
+
<Typography variant="h4">{title}</Typography>
|
|
311
|
+
<Box sx={{ marginLeft: "auto" }}>
|
|
312
|
+
<Button>{addLabel}</Button>
|
|
313
|
+
</Box>
|
|
314
|
+
</Box>;
|
|
315
|
+
|
|
316
|
+
// Prefer — Toolbar parts and MainContent
|
|
317
|
+
<Toolbar>
|
|
318
|
+
<ToolbarTitleItem>{title}</ToolbarTitleItem>
|
|
319
|
+
<FillSpace />
|
|
320
|
+
<ToolbarActions>
|
|
321
|
+
<Button>{addLabel}</Button>
|
|
322
|
+
</ToolbarActions>
|
|
323
|
+
</Toolbar>;
|
|
324
|
+
<MainContent>{children}</MainContent>;
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
When a page is rendered inside a Comet navigation `Stack` (nested master–detail views), use the
|
|
328
|
+
`StackMainContent` and `StackToolbar` variants instead: they render only for the active stack
|
|
329
|
+
level, so nested pages don't show duplicate toolbars.
|
|
330
|
+
|
|
331
|
+
### Full-height content: `fullHeight` and `FullHeightContent`
|
|
332
|
+
|
|
333
|
+
Content that should fill the viewport and scroll inside itself — most often a `DataGrid` — needs
|
|
334
|
+
a height-bounded parent, or it grows the whole page instead of scrolling. Set that height through
|
|
335
|
+
the page structure, not a hand-written `height` that has to track the header and toolbar offset:
|
|
336
|
+
|
|
337
|
+
- When the grid is the page's direct content, add `fullHeight` to `MainContent` or
|
|
338
|
+
`StackMainContent`.
|
|
339
|
+
- When the grid is nested inside `RouterTabs` or other content rather than placed directly in
|
|
340
|
+
`MainContent`, wrap it in `FullHeightContent`, which bounds the height at that level.
|
|
341
|
+
- When the grid holds few rows, give the `DataGrid` the `autoHeight` prop instead and skip
|
|
342
|
+
`fullHeight`.
|
|
343
|
+
|
|
344
|
+
```tsx
|
|
345
|
+
// Avoid — a hand-set height that has to track the header and toolbar offset
|
|
346
|
+
<MainContent>
|
|
347
|
+
<Box sx={{ height: "calc(100vh - 200px)" }}>
|
|
348
|
+
<DataGrid />
|
|
349
|
+
</Box>
|
|
350
|
+
</MainContent>;
|
|
351
|
+
|
|
352
|
+
// Prefer — fullHeight for a grid that is the page's direct content
|
|
353
|
+
<StackMainContent fullHeight>
|
|
354
|
+
<DataGrid />
|
|
355
|
+
</StackMainContent>;
|
|
356
|
+
|
|
357
|
+
// Prefer — FullHeightContent for a grid nested inside tabs
|
|
358
|
+
<MainContent>
|
|
359
|
+
<RouterTabs>
|
|
360
|
+
<RouterTab path="" label={label}>
|
|
361
|
+
<FullHeightContent>
|
|
362
|
+
<DataGrid />
|
|
363
|
+
</FullHeightContent>
|
|
364
|
+
</RouterTab>
|
|
365
|
+
</RouterTabs>
|
|
366
|
+
</MainContent>;
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
## Components
|
|
370
|
+
|
|
371
|
+
### Containers and widgets: `FieldSet`, `FormSection`, and themed `Card`, not `Box`
|
|
372
|
+
|
|
373
|
+
To group related content, use a container component instead of a `Box` with hand-set padding,
|
|
374
|
+
borders, and a title. `FieldSet` (from `@dextinity/admin`) is a collapsible titled panel — wrap a page
|
|
375
|
+
form's fields in it. Inside a dialog or sidebar, group fields with `FormSection` instead, a lighter
|
|
376
|
+
titled section with a divider. For a dashboard widget, if the project uses `@dextinity/cms-admin` (most
|
|
377
|
+
do), use its ready-made `DashboardWidgetRoot` rather than building one by hand; when you compose a
|
|
378
|
+
container yourself, build it from MUI's `Card` (with `CardHeader` and `CardContent`) or `Paper`, with
|
|
379
|
+
`Typography` for text and `Grid` for layout — Comet themes `Card`, `Paper`, and `Typography`, so they
|
|
380
|
+
carry the right elevation, radius, and type scale without custom CSS, while `Grid` takes its spacing
|
|
381
|
+
from the theme. `Card`, `CardHeader`, `CardContent`, `Paper`, `Typography`, and `Grid` come from
|
|
382
|
+
`@mui/material`; `FieldSet` and `FormSection` from `@dextinity/admin`.
|
|
383
|
+
|
|
384
|
+
```tsx
|
|
385
|
+
// Avoid — a Box hand-styled into a titled, bordered panel
|
|
386
|
+
<Box sx={{ border: "1px solid #e0e0e0", borderRadius: 1, padding: 2 }}>
|
|
387
|
+
<Typography variant="h4">{title}</Typography>
|
|
388
|
+
{children}
|
|
389
|
+
</Box>;
|
|
390
|
+
|
|
391
|
+
// Prefer — FieldSet groups content under a title (collapsible by default)
|
|
392
|
+
<FieldSet title={title} supportText={supportText}>
|
|
393
|
+
{children}
|
|
394
|
+
</FieldSet>;
|
|
395
|
+
|
|
396
|
+
// Prefer — a dashboard widget from @dextinity/cms-admin's ready-made container
|
|
397
|
+
<DashboardWidgetRoot header={title}>{children}</DashboardWidgetRoot>;
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### Buttons: `Button` variants and action buttons, not hand-styled buttons
|
|
401
|
+
|
|
402
|
+
Give `Button` a `variant` rather than styling a button by hand or setting MUI's `color` directly.
|
|
403
|
+
The variants are `primary`, `secondary`, `outlined`, `destructive`, `success`, `textLight`, and
|
|
404
|
+
`textDark`. For common actions, prefer the specialized buttons: `SaveButton`, `CancelButton`,
|
|
405
|
+
`DeleteButton`, and `OkayButton` each carry a suitable variant, an icon, and a translated label.
|
|
406
|
+
`SaveButton` also has built-in loading, success, and error feedback (it is a `FeedbackButton`), so
|
|
407
|
+
you don't hand-build that; use `FeedbackButton` for other async actions, and `CopyToClipboardButton`
|
|
408
|
+
to copy text with a confirmation.
|
|
409
|
+
|
|
410
|
+
```tsx
|
|
411
|
+
// Avoid — a hand-styled button, and a Save button rebuilt from a plain Button
|
|
412
|
+
<button style={{ background: "#c00", color: "#fff" }} onClick={onDelete}>
|
|
413
|
+
Delete
|
|
414
|
+
</button>;
|
|
415
|
+
<Button variant="primary" startIcon={<Save />}>
|
|
416
|
+
Save
|
|
417
|
+
</Button>;
|
|
418
|
+
|
|
419
|
+
// Prefer — specialized buttons carry variant, icon, and label (SaveButton adds save feedback)
|
|
420
|
+
<DeleteButton onClick={onDelete} />;
|
|
421
|
+
<SaveButton onClick={onSave} />;
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
### Date and time: pickers from `@dextinity/admin`, not raw inputs
|
|
425
|
+
|
|
426
|
+
Enter dates and times through the picker components rather than a plain text input or an MUI picker
|
|
427
|
+
configured by hand. `@dextinity/admin` exports `DatePicker`, `DateTimePicker`, and `TimePicker` (with
|
|
428
|
+
`DateRangePicker` and `DateTimeRangePicker` for ranges), plus `DatePickerField` and siblings for use
|
|
429
|
+
as Final Form fields. Each picker
|
|
430
|
+
manages its own value format — `DatePicker`, for example, reads and writes an ISO `YYYY-MM-DD`
|
|
431
|
+
string — so you don't parse or format dates by hand. The pickers need MUI X's `LocalizationProvider`
|
|
432
|
+
at the app root, set up once with `AdapterDateFns`; pass `adapterLocale` to localize.
|
|
433
|
+
|
|
434
|
+
```tsx
|
|
435
|
+
// Avoid — a plain text input used as a date field
|
|
436
|
+
<input type="text" value={value} onChange={(event) => onChange(event.target.value)} />;
|
|
437
|
+
|
|
438
|
+
// Prefer — a DatePicker working with ISO date strings
|
|
439
|
+
<DatePicker value={value} onChange={onChange} />;
|
|
440
|
+
|
|
441
|
+
// The pickers need a LocalizationProvider at the app root, set up once
|
|
442
|
+
import { LocalizationProvider } from "@mui/x-date-pickers";
|
|
443
|
+
import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
|
|
444
|
+
|
|
445
|
+
<LocalizationProvider dateAdapter={AdapterDateFns} adapterLocale={locale}>
|
|
446
|
+
{children}
|
|
447
|
+
</LocalizationProvider>;
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### Feedback and overlays: `Alert`, `Loading`, `Dialog`, `Tooltip`, not hand-built ones
|
|
451
|
+
|
|
452
|
+
Show status, loading, dialogs, and tooltips through the components rather than assembling them from
|
|
453
|
+
`div`s and state. `Alert` takes a `severity` (`info`, `warning`, `error`, `success`), a `title`, an
|
|
454
|
+
`action`, and an `onClose`. `Loading` renders the standard spinner; its `behavior` prop (`auto`,
|
|
455
|
+
`fillParent`, `fillParentAbsolute`, `fillPageHeight`) sets whether it renders inline, fills its
|
|
456
|
+
parent, or fills the page. Use `Dialog` and `Tooltip` from `@dextinity/admin` — Comet's own wrappers, not
|
|
457
|
+
MUI's directly. For a transient confirmation, call `showSnackbar()` from `useSnackbarApi()` with a
|
|
458
|
+
snackbar element — Comet's `UndoSnackbar`, or a MUI `Snackbar` wrapping an `Alert` — and mount
|
|
459
|
+
`SnackbarProvider` near the app root.
|
|
460
|
+
|
|
461
|
+
```tsx
|
|
462
|
+
// Avoid — a hand-built alert box and a hand-built spinner
|
|
463
|
+
<div style={{ background: "#fdecea", padding: 12 }}>{errorMessage}</div>;
|
|
464
|
+
{
|
|
465
|
+
loading && <div className="spinner" />;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Prefer — Alert shows severity; Loading renders the standard spinner
|
|
469
|
+
<Alert severity="error">{errorMessage}</Alert>;
|
|
470
|
+
{
|
|
471
|
+
loading && <Loading />;
|
|
472
|
+
}
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
### Icons: `@dextinity/admin-icons`, not ad-hoc SVGs
|
|
476
|
+
|
|
477
|
+
Take icons from `@dextinity/admin-icons` rather than importing SVG files or an arbitrary
|
|
478
|
+
icon from another set, so they match the design system and stay consistent. Each icon is a named
|
|
479
|
+
export built on MUI's `SvgIcon`, so size it with the `fontSize` prop (`small`, `medium`, `large`) and
|
|
480
|
+
color it with the `color` prop — the icons use `currentColor`.
|
|
481
|
+
|
|
482
|
+
```tsx
|
|
483
|
+
// Avoid — an imported SVG file, or an arbitrary icon from another set
|
|
484
|
+
import deleteIcon from "./delete.svg";
|
|
485
|
+
|
|
486
|
+
<img src={deleteIcon} width={16} alt="" />;
|
|
487
|
+
|
|
488
|
+
// Prefer — a named icon from the Comet set, sized and colored through props
|
|
489
|
+
import { Delete } from "@dextinity/admin-icons";
|
|
490
|
+
|
|
491
|
+
<Delete fontSize="small" color="error" />;
|
|
492
|
+
```
|