@matteoaliano/forest-ui 0.6.0 → 0.7.0
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/package.json +1 -1
- package/skills/forest-alkemy-plus/SKILL.md +2 -2
- package/skills/forest-alkemy-plus/references/upgrading.md +137 -0
- package/skills/forest-agency/SKILL.md +0 -94
- package/skills/forest-agency/references/components.md +0 -202
- package/skills/forest-agency/references/patterns.md +0 -405
- package/skills/forest-external/SKILL.md +0 -94
- package/skills/forest-external/references/components.md +0 -202
- package/skills/forest-external/references/patterns.md +0 -405
- package/skills/forest-internal/SKILL.md +0 -94
- package/skills/forest-internal/references/components.md +0 -202
- package/skills/forest-internal/references/patterns.md +0 -405
|
@@ -1,405 +0,0 @@
|
|
|
1
|
-
# Forest UI — Code Patterns & Anti-Patterns
|
|
2
|
-
|
|
3
|
-
## Code Patterns
|
|
4
|
-
|
|
5
|
-
### Form Layout
|
|
6
|
-
|
|
7
|
-
```tsx
|
|
8
|
-
import { TextField, Button, Select, MenuItem, Checkbox, Box } from "@matteoaliano/forest-ui";
|
|
9
|
-
|
|
10
|
-
function ContactForm() {
|
|
11
|
-
return (
|
|
12
|
-
<Box sx={{ display: "flex", flexDirection: "column", gap: 3 }}>
|
|
13
|
-
<TextField label="Name" fullWidth />
|
|
14
|
-
<TextField label="Email" type="email" fullWidth />
|
|
15
|
-
<Select label="Subject" fullWidth>
|
|
16
|
-
<MenuItem value="support">Support</MenuItem>
|
|
17
|
-
<MenuItem value="sales">Sales</MenuItem>
|
|
18
|
-
</Select>
|
|
19
|
-
<Button variant="contained" type="submit">
|
|
20
|
-
Send
|
|
21
|
-
</Button>
|
|
22
|
-
</Box>
|
|
23
|
-
);
|
|
24
|
-
}
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
### MultiSelect with Select All
|
|
28
|
-
|
|
29
|
-
```tsx
|
|
30
|
-
import { useState } from "react";
|
|
31
|
-
import { MultiSelect } from "@matteoaliano/forest-ui";
|
|
32
|
-
|
|
33
|
-
const options = [
|
|
34
|
-
{ label: "Bug", value: "bug" },
|
|
35
|
-
{ label: "Enhancement", value: "enhancement" },
|
|
36
|
-
{ label: "New Feature", value: "feature" },
|
|
37
|
-
];
|
|
38
|
-
|
|
39
|
-
function CategoryFilter() {
|
|
40
|
-
const [selected, setSelected] = useState<string[]>([]);
|
|
41
|
-
return (
|
|
42
|
-
<MultiSelect
|
|
43
|
-
label="Category"
|
|
44
|
-
options={options}
|
|
45
|
-
value={selected}
|
|
46
|
-
onChange={setSelected}
|
|
47
|
-
/>
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
### Data Table
|
|
53
|
-
|
|
54
|
-
```tsx
|
|
55
|
-
import {
|
|
56
|
-
Table, TableContainer, TableHead, TableBody, TableRow, TableCell,
|
|
57
|
-
} from "@matteoaliano/forest-ui";
|
|
58
|
-
|
|
59
|
-
function UsersTable({ users }) {
|
|
60
|
-
return (
|
|
61
|
-
<TableContainer>
|
|
62
|
-
<Table>
|
|
63
|
-
<TableHead>
|
|
64
|
-
<TableRow>
|
|
65
|
-
<TableCell>Name</TableCell>
|
|
66
|
-
<TableCell>Email</TableCell>
|
|
67
|
-
</TableRow>
|
|
68
|
-
</TableHead>
|
|
69
|
-
<TableBody>
|
|
70
|
-
{users.map((u) => (
|
|
71
|
-
<TableRow key={u.id}>
|
|
72
|
-
<TableCell>{u.name}</TableCell>
|
|
73
|
-
<TableCell>{u.email}</TableCell>
|
|
74
|
-
</TableRow>
|
|
75
|
-
))}
|
|
76
|
-
</TableBody>
|
|
77
|
-
</Table>
|
|
78
|
-
</TableContainer>
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
### DataGrid
|
|
84
|
-
|
|
85
|
-
```tsx
|
|
86
|
-
import { DataGrid, type GridColDef } from "@matteoaliano/forest-ui";
|
|
87
|
-
|
|
88
|
-
const columns: GridColDef[] = [
|
|
89
|
-
{ field: "id", headerName: "ID", width: 70 },
|
|
90
|
-
{ field: "name", headerName: "Name", flex: 1 },
|
|
91
|
-
{ field: "email", headerName: "Email", flex: 1 },
|
|
92
|
-
];
|
|
93
|
-
|
|
94
|
-
function UsersGrid({ rows }) {
|
|
95
|
-
return <DataGrid rows={rows} columns={columns} />;
|
|
96
|
-
}
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
### Feedback Pattern
|
|
100
|
-
|
|
101
|
-
```tsx
|
|
102
|
-
import { Alert, AlertTitle, Snackbar } from "@matteoaliano/forest-ui";
|
|
103
|
-
|
|
104
|
-
// Inline feedback
|
|
105
|
-
<Alert severity="error">
|
|
106
|
-
<AlertTitle>Error</AlertTitle>
|
|
107
|
-
Something went wrong.
|
|
108
|
-
</Alert>
|
|
109
|
-
|
|
110
|
-
// Toast notification
|
|
111
|
-
<Snackbar open={open} autoHideDuration={4000} onClose={handleClose}>
|
|
112
|
-
<Alert severity="success" variant="filled">Saved!</Alert>
|
|
113
|
-
</Snackbar>
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
### Confirmation Dialog
|
|
117
|
-
|
|
118
|
-
```tsx
|
|
119
|
-
import { Dialog, DialogTitle, DialogContent, DialogActions, DialogContentText, Button } from "@matteoaliano/forest-ui";
|
|
120
|
-
|
|
121
|
-
function ConfirmDialog({ open, onClose, onConfirm }) {
|
|
122
|
-
return (
|
|
123
|
-
<Dialog open={open} onClose={onClose}>
|
|
124
|
-
<DialogTitle>Confirm</DialogTitle>
|
|
125
|
-
<DialogContent>
|
|
126
|
-
<DialogContentText>Are you sure?</DialogContentText>
|
|
127
|
-
</DialogContent>
|
|
128
|
-
<DialogActions>
|
|
129
|
-
<Button variant="outlined" onClick={onClose}>Cancel</Button>
|
|
130
|
-
<Button onClick={onConfirm}>Confirm</Button>
|
|
131
|
-
</DialogActions>
|
|
132
|
-
</Dialog>
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
### App Shell (AppBar + Sidebar Navigation)
|
|
138
|
-
|
|
139
|
-
The standard application layout: a full-width AppBar at the top with a SidebarNav below it. The Toolbar uses three equal `flex: 1` columns so the Search stays visually centered. The sidebar drawer uses `position: relative` to flow inside the layout instead of overlaying as a fixed panel.
|
|
140
|
-
|
|
141
|
-
```tsx
|
|
142
|
-
import { useState } from "react";
|
|
143
|
-
import {
|
|
144
|
-
AppBar, Toolbar, Logo, Search, IconButton, Badge,
|
|
145
|
-
SidebarNav, SidebarItem, List, Typography, Box, Avatar,
|
|
146
|
-
} from "@matteoaliano/forest-ui";
|
|
147
|
-
import NotificationsOutlined from "@mui/icons-material/NotificationsOutlined";
|
|
148
|
-
import SettingsOutlined from "@mui/icons-material/SettingsOutlined";
|
|
149
|
-
import HomeOutlined from "@mui/icons-material/HomeOutlined";
|
|
150
|
-
import BarChartOutlined from "@mui/icons-material/BarChartOutlined";
|
|
151
|
-
import PeopleOutlined from "@mui/icons-material/PeopleOutlined";
|
|
152
|
-
|
|
153
|
-
function AppShell({ children }) {
|
|
154
|
-
const [sidebarOpen, setSidebarOpen] = useState(true);
|
|
155
|
-
|
|
156
|
-
return (
|
|
157
|
-
<Box sx={{ display: "flex", flexDirection: "column", height: "100vh" }}>
|
|
158
|
-
{/* ── App Bar ── */}
|
|
159
|
-
<AppBar
|
|
160
|
-
position="sticky"
|
|
161
|
-
elevation={0}
|
|
162
|
-
sx={{ zIndex: (theme) => theme.zIndex.drawer + 1 }}
|
|
163
|
-
>
|
|
164
|
-
<Toolbar variant="dense" sx={{ px: { xs: "16px", sm: "16px" } }}>
|
|
165
|
-
{/* Left — Logo */}
|
|
166
|
-
<Box sx={{ flex: 1, display: "flex", alignItems: "center" }}>
|
|
167
|
-
<Logo product="wsuite" />
|
|
168
|
-
</Box>
|
|
169
|
-
{/* Center — Search (always visually centered) */}
|
|
170
|
-
<Box sx={{ flex: 1, display: "flex", justifyContent: "center" }}>
|
|
171
|
-
<Search size="small" sx={{ width: "100%", maxWidth: 480 }} />
|
|
172
|
-
</Box>
|
|
173
|
-
{/* Right — Actions */}
|
|
174
|
-
<Box sx={{ flex: 1, display: "flex", alignItems: "center", justifyContent: "flex-end", gap: 1 }}>
|
|
175
|
-
<IconButton size="small" color="inherit">
|
|
176
|
-
<Badge color="error" variant="dot">
|
|
177
|
-
<NotificationsOutlined fontSize="small" />
|
|
178
|
-
</Badge>
|
|
179
|
-
</IconButton>
|
|
180
|
-
<IconButton size="small" color="inherit">
|
|
181
|
-
<SettingsOutlined fontSize="small" />
|
|
182
|
-
</IconButton>
|
|
183
|
-
<Avatar sx={{ width: 28, height: 28, fontSize: 13 }}>MA</Avatar>
|
|
184
|
-
</Box>
|
|
185
|
-
</Toolbar>
|
|
186
|
-
</AppBar>
|
|
187
|
-
|
|
188
|
-
{/* ── Body: Sidebar + Content ── */}
|
|
189
|
-
<Box sx={{ display: "flex", flexGrow: 1, overflow: "hidden" }}>
|
|
190
|
-
<SidebarNav
|
|
191
|
-
open={sidebarOpen}
|
|
192
|
-
onOpenChange={setSidebarOpen}
|
|
193
|
-
sx={{
|
|
194
|
-
height: "100%",
|
|
195
|
-
"& .MuiDrawer-paper": { position: "relative", height: "100%" },
|
|
196
|
-
}}
|
|
197
|
-
>
|
|
198
|
-
<List>
|
|
199
|
-
<SidebarItem icon={<HomeOutlined />} label="Home" selected />
|
|
200
|
-
<SidebarItem icon={<BarChartOutlined />} label="Analytics" />
|
|
201
|
-
<SidebarItem icon={<PeopleOutlined />} label="Users" />
|
|
202
|
-
<SidebarItem icon={<SettingsOutlined />} label="Settings" />
|
|
203
|
-
</List>
|
|
204
|
-
</SidebarNav>
|
|
205
|
-
|
|
206
|
-
<Box
|
|
207
|
-
component="main"
|
|
208
|
-
sx={{ flexGrow: 1, p: 3, overflow: "auto", backgroundColor: "background.paper" }}
|
|
209
|
-
>
|
|
210
|
-
{children}
|
|
211
|
-
</Box>
|
|
212
|
-
</Box>
|
|
213
|
-
</Box>
|
|
214
|
-
);
|
|
215
|
-
}
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
**Key patterns:**
|
|
219
|
-
- `Toolbar variant="dense"` — 48px height instead of 64px
|
|
220
|
-
- Three `flex: 1` columns in Toolbar — keeps Search centered regardless of left/right content width
|
|
221
|
-
- `px: { xs: "16px", sm: "16px" }` on Toolbar — aligns logo with sidebar icons (overrides MUI's responsive 24px default)
|
|
222
|
-
- `position: "relative"` on drawer paper — makes sidebar flow in layout, not overlay
|
|
223
|
-
- `height: "100%"` on SidebarNav — sidebar border extends full height
|
|
224
|
-
- `backgroundColor: "background.paper"` on content — contrasts with sidebar/AppBar background
|
|
225
|
-
|
|
226
|
-
### Detail Drawer
|
|
227
|
-
|
|
228
|
-
Use the `Drawer` component when a user clicks an item (table row, card, list entry) to view or edit its details in a side panel. The Drawer defaults to `anchor="right"` and `hideBackdrop={true}`, so it opens on the right without dimming the page — the user retains full visibility of the content behind it.
|
|
229
|
-
|
|
230
|
-
```tsx
|
|
231
|
-
import { useState } from "react";
|
|
232
|
-
import {
|
|
233
|
-
Drawer, Box, Typography, IconButton, Divider, Chip, Stack,
|
|
234
|
-
DataGrid, type GridColDef,
|
|
235
|
-
} from "@matteoaliano/forest-ui";
|
|
236
|
-
import CloseOutlined from "@mui/icons-material/CloseOutlined";
|
|
237
|
-
|
|
238
|
-
function KeywordTable({ rows, columns }: { rows: any[]; columns: GridColDef[] }) {
|
|
239
|
-
const [selected, setSelected] = useState<any | null>(null);
|
|
240
|
-
|
|
241
|
-
return (
|
|
242
|
-
<Box sx={{ display: "flex", height: "100%" }}>
|
|
243
|
-
<Box sx={{ flex: 1, minWidth: 0 }}>
|
|
244
|
-
<DataGrid
|
|
245
|
-
rows={rows}
|
|
246
|
-
columns={columns}
|
|
247
|
-
onRowClick={(params) => setSelected(params.row)}
|
|
248
|
-
/>
|
|
249
|
-
</Box>
|
|
250
|
-
|
|
251
|
-
<Drawer open={!!selected} onClose={() => setSelected(null)}>
|
|
252
|
-
{selected && (
|
|
253
|
-
<Box sx={{ width: 400, p: 3 }}>
|
|
254
|
-
{/* Header */}
|
|
255
|
-
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center", mb: 2 }}>
|
|
256
|
-
<Typography variant="h6">{selected.name}</Typography>
|
|
257
|
-
<IconButton size="small" onClick={() => setSelected(null)}>
|
|
258
|
-
<CloseOutlined />
|
|
259
|
-
</IconButton>
|
|
260
|
-
</Box>
|
|
261
|
-
<Divider sx={{ mb: 2 }} />
|
|
262
|
-
|
|
263
|
-
{/* Detail content */}
|
|
264
|
-
<Stack direction="row" spacing={1} sx={{ mb: 3 }}>
|
|
265
|
-
<Chip label={`${selected.volume} vol`} size="small" />
|
|
266
|
-
<Chip label={selected.category} size="small" variant="outlined" />
|
|
267
|
-
</Stack>
|
|
268
|
-
<Typography variant="body2" color="text.secondary">
|
|
269
|
-
{selected.description}
|
|
270
|
-
</Typography>
|
|
271
|
-
</Box>
|
|
272
|
-
)}
|
|
273
|
-
</Drawer>
|
|
274
|
-
</Box>
|
|
275
|
-
);
|
|
276
|
-
}
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
**Key points:**
|
|
280
|
-
- `anchor="right"` and `hideBackdrop={true}` are the Forest defaults — no need to set them
|
|
281
|
-
- The Drawer takes full viewport height and overlays the right side of the page
|
|
282
|
-
- Always include a close button in the Drawer header
|
|
283
|
-
- Set a fixed `width` on the Drawer content (e.g. 400px)
|
|
284
|
-
- The content behind the Drawer remains fully visible and interactive
|
|
285
|
-
|
|
286
|
-
### Charts
|
|
287
|
-
|
|
288
|
-
Colors are automatically assigned from the theme's 12-series palette. Override with the `colors` prop if needed.
|
|
289
|
-
|
|
290
|
-
```tsx
|
|
291
|
-
import { BarChart, LineChart, PieChart, useChartColors } from "@matteoaliano/forest-ui";
|
|
292
|
-
|
|
293
|
-
// Bar chart with striped variant
|
|
294
|
-
<BarChart
|
|
295
|
-
series={[
|
|
296
|
-
{ data: [10, 20, 30], label: "Current", variant: "solid" },
|
|
297
|
-
{ data: [8, 15, 25], label: "Previous", variant: "striped" },
|
|
298
|
-
]}
|
|
299
|
-
xAxis={[{ data: ["Jan", "Feb", "Mar"], scaleType: "band" }]}
|
|
300
|
-
height={300}
|
|
301
|
-
/>
|
|
302
|
-
|
|
303
|
-
// Line chart
|
|
304
|
-
<LineChart
|
|
305
|
-
series={[{ data: [10, 20, 30], label: "Revenue" }]}
|
|
306
|
-
xAxis={[{ data: ["Jan", "Feb", "Mar"], scaleType: "band" }]}
|
|
307
|
-
height={300}
|
|
308
|
-
/>
|
|
309
|
-
|
|
310
|
-
// Pie chart
|
|
311
|
-
<PieChart
|
|
312
|
-
series={[{ data: [
|
|
313
|
-
{ id: 0, value: 40, label: "Desktop" },
|
|
314
|
-
{ id: 1, value: 30, label: "Mobile" },
|
|
315
|
-
{ id: 2, value: 30, label: "Tablet" },
|
|
316
|
-
]}]}
|
|
317
|
-
height={300}
|
|
318
|
-
/>
|
|
319
|
-
|
|
320
|
-
// Access chart colors programmatically
|
|
321
|
-
const colors = useChartColors(3);
|
|
322
|
-
```
|
|
323
|
-
|
|
324
|
-
## Storybook Controls
|
|
325
|
-
|
|
326
|
-
When adding argTypes to stories:
|
|
327
|
-
|
|
328
|
-
```tsx
|
|
329
|
-
const meta: Meta<typeof Button> = {
|
|
330
|
-
component: Button,
|
|
331
|
-
argTypes: {
|
|
332
|
-
variant: {
|
|
333
|
-
control: "select",
|
|
334
|
-
options: ["contained", "outlined", "text"],
|
|
335
|
-
},
|
|
336
|
-
color: {
|
|
337
|
-
control: "select",
|
|
338
|
-
options: ["primary", "secondary", "error", "warning", "success", "info"],
|
|
339
|
-
},
|
|
340
|
-
disabled: { control: "boolean" },
|
|
341
|
-
},
|
|
342
|
-
};
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
**Control types:** `"select"` with `options`, `"boolean"`, `"number"`, `"text"`.
|
|
346
|
-
|
|
347
|
-
## Anti-Patterns
|
|
348
|
-
|
|
349
|
-
```tsx
|
|
350
|
-
// WRONG: importing from @mui/material
|
|
351
|
-
import Button from "@mui/material/Button";
|
|
352
|
-
|
|
353
|
-
// WRONG: hardcoded colors
|
|
354
|
-
<Box sx={{ backgroundColor: "#7f56d9" }} />
|
|
355
|
-
|
|
356
|
-
// WRONG: hardcoded spacing
|
|
357
|
-
<Box sx={{ padding: "16px" }} />
|
|
358
|
-
|
|
359
|
-
// WRONG: forgetting ForestProvider
|
|
360
|
-
ReactDOM.render(<App />, root); // theme won't apply
|
|
361
|
-
|
|
362
|
-
// WRONG: using filled (default) icons
|
|
363
|
-
import CloseIcon from "@mui/icons-material/Close";
|
|
364
|
-
|
|
365
|
-
// CORRECT: always use Outlined variant
|
|
366
|
-
import CloseIcon from "@mui/icons-material/CloseOutlined";
|
|
367
|
-
|
|
368
|
-
// CORRECT: use theme tokens
|
|
369
|
-
<Box sx={{ backgroundColor: "primary.main", p: 4 }} />
|
|
370
|
-
|
|
371
|
-
// WRONG: manually building multi-select with Select + Checkbox
|
|
372
|
-
// CORRECT: use the MultiSelect component
|
|
373
|
-
<MultiSelect options={options} value={value} onChange={setValue} />
|
|
374
|
-
|
|
375
|
-
// WRONG: inline detail panel — a flex Box that sits inside the content area
|
|
376
|
-
// This is not a design system pattern and creates inconsistent layouts
|
|
377
|
-
<Box sx={{ display: "flex" }}>
|
|
378
|
-
<Box sx={{ flex: 1 }}><DataGrid ... /></Box>
|
|
379
|
-
{selected && (
|
|
380
|
-
<Box sx={{ width: 400, borderLeft: 1, borderColor: "divider", p: 2 }}>
|
|
381
|
-
{/* detail content sitting next to the table */}
|
|
382
|
-
</Box>
|
|
383
|
-
)}
|
|
384
|
-
</Box>
|
|
385
|
-
|
|
386
|
-
// CORRECT: use the Drawer component for detail panels
|
|
387
|
-
// It opens full viewport height on the right, without a backdrop
|
|
388
|
-
<Drawer open={!!selected} onClose={() => setSelected(null)}>
|
|
389
|
-
<Box sx={{ width: 400, p: 3 }}>
|
|
390
|
-
{/* detail content */}
|
|
391
|
-
</Box>
|
|
392
|
-
</Drawer>
|
|
393
|
-
```
|
|
394
|
-
|
|
395
|
-
## TypeScript Props
|
|
396
|
-
|
|
397
|
-
Every component exports its props type:
|
|
398
|
-
|
|
399
|
-
```tsx
|
|
400
|
-
import { Button, type ButtonProps } from "@matteoaliano/forest-ui";
|
|
401
|
-
|
|
402
|
-
interface MyButtonProps extends ButtonProps {
|
|
403
|
-
analyticsId: string;
|
|
404
|
-
}
|
|
405
|
-
```
|