@matteoaliano/forest-ui 0.8.8 → 1.0.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/README.md +56 -12
- package/dist/{chunk-I3T6PQLZ.mjs → chunk-XJYBU6LC.mjs} +1775 -1883
- package/dist/chunk-XJYBU6LC.mjs.map +1 -0
- package/dist/index.d.mts +125 -380
- package/dist/index.d.ts +125 -380
- package/dist/index.js +2143 -2565
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +451 -770
- package/dist/index.mjs.map +1 -1
- package/dist/theme.d.mts +14 -17
- package/dist/theme.d.ts +14 -17
- package/dist/theme.js +1762 -1873
- package/dist/theme.js.map +1 -1
- package/dist/theme.mjs +1 -7
- package/package.json +1 -1
- package/skills/forest-alkemy-plus/SKILL.md +20 -1
- package/skills/forest-alkemy-plus/references/components.md +3 -3
- package/skills/forest-alkemy-plus/references/patterns.md +54 -6
- package/dist/chunk-I3T6PQLZ.mjs.map +0 -1
package/dist/theme.mjs
CHANGED
|
@@ -18,10 +18,7 @@ import {
|
|
|
18
18
|
fontFamilyMono,
|
|
19
19
|
fontSize,
|
|
20
20
|
fontWeight,
|
|
21
|
-
forestAgencyPreset,
|
|
22
21
|
forestAlkemyPlusPreset,
|
|
23
|
-
forestExternalPreset,
|
|
24
|
-
forestInternalPreset,
|
|
25
22
|
forestTheme,
|
|
26
23
|
forestThemeOptions,
|
|
27
24
|
getAvailablePresets,
|
|
@@ -45,7 +42,7 @@ import {
|
|
|
45
42
|
violet,
|
|
46
43
|
warning,
|
|
47
44
|
widths
|
|
48
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-XJYBU6LC.mjs";
|
|
49
46
|
export {
|
|
50
47
|
alpha,
|
|
51
48
|
avatarColors,
|
|
@@ -66,10 +63,7 @@ export {
|
|
|
66
63
|
fontFamilyMono,
|
|
67
64
|
fontSize,
|
|
68
65
|
fontWeight,
|
|
69
|
-
forestAgencyPreset,
|
|
70
66
|
forestAlkemyPlusPreset,
|
|
71
|
-
forestExternalPreset,
|
|
72
|
-
forestInternalPreset,
|
|
73
67
|
forestTheme,
|
|
74
68
|
forestThemeOptions,
|
|
75
69
|
getAvailablePresets,
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@ name: forest-alkemy-plus
|
|
|
3
3
|
description: Forest UI Design System rules for the Alkemy+ (charcoal/red) preset. Enforces correct imports, component usage, and theming with @matteoaliano/forest-ui. Use when the project uses forest-ui, forest-alkemy-plus preset, or when user builds UI components in a forest-ui project. Triggers on "forest", "forest-ui", "forest alkemy", "forest alkemy+", "@matteoaliano/forest-ui".
|
|
4
4
|
metadata:
|
|
5
5
|
author: Forest Design System
|
|
6
|
-
version: 0.
|
|
6
|
+
version: 1.0.0
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# Forest UI — Alkemy+ Preset
|
|
@@ -17,6 +17,8 @@ metadata:
|
|
|
17
17
|
5. **NEVER create custom component wrappers** for things Forest UI already provides.
|
|
18
18
|
6. **TypeScript is required.** All components export their prop types (e.g. `type ButtonProps`).
|
|
19
19
|
7. **ALWAYS use the Outlined variant of MUI icons.** Import from `@mui/icons-material/*Outlined` (e.g. `CloseOutlined`, `MailOutlined`). Never use filled, Rounded, Sharp, or TwoTone variants.
|
|
20
|
+
8. **A clickable card MUST use `CardActionArea`.** Wrap the card's content in `<CardActionArea onClick={…}>` — never put `onClick`/`href` on a bare `<Card>`. `CardActionArea` carries the faux-3D bevel + glaze + press affordance (the same interaction language as `Button`), which is how users tell a clickable card apart from a static one. Static (non-interactive) cards must **not** use it.
|
|
21
|
+
9. **Every authenticated product surface MUST use the App Shell layout.** Build the top-level chrome from the App Shell pattern (AppBar + SidebarNav) — see Setup → App Shell and `references/patterns.md`. Its AppBar actions MUST include the color-mode toggle (`ColorModeToggle`), which is the single sanctioned dark-mode switch. Do not hand-roll a bespoke app frame or scatter ad-hoc theme toggles elsewhere.
|
|
20
22
|
|
|
21
23
|
## Setup
|
|
22
24
|
|
|
@@ -85,6 +87,21 @@ function App() {
|
|
|
85
87
|
}
|
|
86
88
|
```
|
|
87
89
|
|
|
90
|
+
## Dark Mode
|
|
91
|
+
|
|
92
|
+
The preset ships a dark color scheme (MUI `colorSchemes` / CSS variables). It
|
|
93
|
+
activates on a **`.dark` class** on any ancestor — same convention as
|
|
94
|
+
`@matteoaliano/forest-web`.
|
|
95
|
+
|
|
96
|
+
- Class toggle: put `dark` on `<html>` (`document.documentElement.classList.toggle("dark", isDark)`) — NOT a deep wrapper, or portaled components (Modal/Menu/Tooltip) stay light. `useColorScheme` handles this for you.
|
|
97
|
+
- Initial scheme: `<ForestProvider preset="forest-alkemy+" defaultMode="system">` — `defaultMode` is `"light"` (default), `"dark"`, or `"system"` (follows `prefers-color-scheme`).
|
|
98
|
+
- Programmatic: `const { mode, setMode } = useColorScheme();` (re-exported from the package) — call `setMode("dark" | "light" | "system")`.
|
|
99
|
+
- Next.js App Router: render `<InitColorSchemeScript attribute="class" />` (re-exported) as the first child of `<body>` to avoid a flash of the wrong scheme.
|
|
100
|
+
- **Where the switch lives:** expose it as the `ColorModeToggle` in the App Shell's AppBar actions (see `references/patterns.md` → App Shell). That is the sanctioned, single entry point for toggling dark mode — don't duplicate it.
|
|
101
|
+
|
|
102
|
+
Do NOT hand-roll a second theme or a `.dark` stylesheet — the palette already
|
|
103
|
+
inverts through the preset's dark tokens; just toggle the class or use `setMode`.
|
|
104
|
+
|
|
88
105
|
## Typography & Fonts
|
|
89
106
|
|
|
90
107
|
- **Aeonik** — the default font for all UI text (headings, body, labels, buttons, etc.). Family name: `"Aeonik"`.
|
|
@@ -139,3 +156,5 @@ See `references/components.md` for full API details, `references/patterns.md` fo
|
|
|
139
156
|
6. **Missing `<ForestProvider>`** at the app root — theme won't apply
|
|
140
157
|
7. **Using filled MUI icons** (`Close`) instead of Outlined (`CloseOutlined`)
|
|
141
158
|
8. **Building custom multi-select** instead of using the `<MultiSelect>` component
|
|
159
|
+
9. **`onClick` on a bare `<Card>`** — a clickable card must wrap its content in `<CardActionArea>` so it gets the 3D bevel/glaze/press affordance. `<Card onClick>` looks identical to a static card and gives no click cue.
|
|
160
|
+
10. **Theme-only components rendering like stock MUI grey** — `Switch`, `Checkbox`, `Radio`, `ToggleButton` (and other pure re-exports) carry NO styling of their own; 100% of their Forest look comes from the theme applied by `<ForestProvider>`. If one looks unstyled/plain-grey while the rest of the app looks right: (a) confirm you imported it from `@matteoaliano/forest-ui`, **not** `@mui/material` (a stray `import Switch from "@mui/material/Switch"` is the usual culprit — it renders with zero error), and (b) confirm it's actually inside `<ForestProvider>` (watch portals/modals and any nested non-Forest MUI `ThemeProvider`, which resets the overrides for its subtree).
|
|
@@ -52,7 +52,7 @@ import Button from "@mui/material/Button";
|
|
|
52
52
|
| `Switch` | `import { Switch } from "@matteoaliano/forest-ui"` | `color: "default" \| "primary" \| "secondary" \| "error" \| "info" \| "success" \| "warning"`, `size: "small" \| "medium"`, `checked`, `disabled` | — |
|
|
53
53
|
| `ToggleButton` + `ToggleButtonGroup` | `import { ToggleButton, ToggleButtonGroup } from "@matteoaliano/forest-ui"` | `value`, `exclusive`, `onChange`, `orientation: "horizontal" \| "vertical"`, `size: "small" \| "medium" \| "large"`, `color`, `fullWidth` | — |
|
|
54
54
|
| `Fab` | `import { Fab } from "@matteoaliano/forest-ui"` | `variant: "circular" \| "extended"`, `size: "small" \| "medium" \| "large"`, `color`, `disabled`, `href` | `color="primary"` |
|
|
55
|
-
| `IconButton` | `import { IconButton } from "@matteoaliano/forest-ui"` | `color: "inherit" \| "primary" \| "secondary" \| "error" \| "info" \| "success" \| "warning" \| "default"`, `size: "small" \| "medium" \| "large"`, `disabled`, `disableRipple` |
|
|
55
|
+
| `IconButton` | `import { IconButton } from "@matteoaliano/forest-ui"` | `variant: "text" \| "contained" \| "outlined"` (contained/outlined get the faux-3D button affordance; text is the bare icon), `color: "inherit" \| "primary" \| "secondary" \| "error" \| "info" \| "success" \| "warning" \| "default"`, `size: "small" \| "medium" \| "large"`, `disabled`, `disableRipple` | `variant="text"` |
|
|
56
56
|
| `Autocomplete` | `import { Autocomplete } from "@matteoaliano/forest-ui"` | `options`, `renderInput` (required), `multiple`, `freeSolo`, `disableClearable`, `loading`, `groupBy`, `limitTags`, `filterOptions` | — |
|
|
57
57
|
| `Search` | `import { Search } from "@matteoaliano/forest-ui"` | `onClear`, `placeholder`, all TextField props (except `variant`) | `variant="outlined"`, `placeholder="Search..."`, built-in search icon and clear button |
|
|
58
58
|
| `Input` | `import { Input } from "@matteoaliano/forest-ui"` | `value`, `onChange`, `placeholder`, `multiline`, `rows`, `startAdornment`, `endAdornment`, `disabled`, `error` — all InputBase props | — |
|
|
@@ -90,9 +90,9 @@ import Button from "@mui/material/Button";
|
|
|
90
90
|
| `Accordion` family | `import { Accordion, AccordionSummary, AccordionDetails, AccordionActions } from "@matteoaliano/forest-ui"` | `expanded`, `defaultExpanded`, `onChange`, `disabled`, `disableGutters`, `square` | — |
|
|
91
91
|
| `AppBar` + `Toolbar` | `import { AppBar, Toolbar } from "@matteoaliano/forest-ui"` | `position`, `color`, `elevation`; Toolbar: `variant: "dense" \| "regular"` | — |
|
|
92
92
|
| `AppBarNavItem` | `import { AppBarNavItem } from "@matteoaliano/forest-ui"` | `label` (required), `icon`, `selected`, `href`, `sx` — navigation button for AppBar | — |
|
|
93
|
-
| `Card` family | `import { Card, CardContent, CardHeader, CardActions, CardMedia } from "@matteoaliano/forest-ui"` | `variant: "elevation" \| "outlined"`, `elevation`, `raised`; CardHeader: `title`, `subheader`, `avatar`, `action` | — |
|
|
93
|
+
| `Card` family | `import { Card, CardContent, CardHeader, CardActions, CardMedia } from "@matteoaliano/forest-ui"` | `variant: "elevation" \| "outlined"`, `elevation`, `raised`; CardHeader: `title`, `subheader`, `avatar`, `action`. **If the card is clickable, wrap its content in `CardActionArea`** (see below) | — |
|
|
94
94
|
| `Paper` | `import { Paper } from "@matteoaliano/forest-ui"` | `variant: "elevation" \| "outlined"`, `elevation` (0-24), `square` | — |
|
|
95
|
-
| `CardActionArea` | `import { CardActionArea } from "@matteoaliano/forest-ui"` | `onClick`, `href`, `disabled` |
|
|
95
|
+
| `CardActionArea` | `import { CardActionArea } from "@matteoaliano/forest-ui"` | `onClick`, `href`, `disabled`. **REQUIRED for any clickable card** — carries the faux-3D bevel + glaze + press affordance (same interaction language as Button) that signals a card is interactive. Never put `onClick` on a bare `Card`. | `<Card><CardActionArea onClick={…}><CardContent>…</CardContent></CardActionArea></Card>` |
|
|
96
96
|
| `Drawer` | `import { Drawer } from "@matteoaliano/forest-ui"` | `open`, `onClose`, `anchor: "left" \| "right" \| "top" \| "bottom"`, `variant: "permanent" \| "persistent" \| "temporary"`, `hideBackdrop` | `anchor="right"`, `hideBackdrop={true}` |
|
|
97
97
|
| `SwipeableDrawer` | `import { SwipeableDrawer } from "@matteoaliano/forest-ui"` | `onOpen` (required), `onClose` (required), `open`, `anchor`, `swipeAreaWidth` |
|
|
98
98
|
|
|
@@ -134,22 +134,63 @@ function ConfirmDialog({ open, onClose, onConfirm }) {
|
|
|
134
134
|
}
|
|
135
135
|
```
|
|
136
136
|
|
|
137
|
-
### App Shell (AppBar + Sidebar Navigation)
|
|
137
|
+
### App Shell (AppBar + Sidebar Navigation) — MANDATORY layout
|
|
138
138
|
|
|
139
|
-
|
|
139
|
+
**This is the required top-level layout for every authenticated Forest UI product surface.** Do not hand-roll a bespoke app chrome per project — start from this shell so brand, spacing, sidebar behavior, and the dark-mode toggle are consistent across all Forest apps. A full-width AppBar sits 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
|
+
Two requirements are non-negotiable:
|
|
142
|
+
|
|
143
|
+
1. **The AppBar actions MUST include the color-mode toggle** (`ColorModeToggle` below, backed by `useColorScheme`). This is the single sanctioned dark-mode switch — do not scatter ad-hoc toggles elsewhere.
|
|
144
|
+
2. **The brand is always the logomark**, and it flips to `color="negative"` when dark mode is active so it stays legible on the dark AppBar.
|
|
140
145
|
|
|
141
146
|
```tsx
|
|
142
147
|
import { useState } from "react";
|
|
143
148
|
import {
|
|
144
|
-
AppBar, Toolbar, Logo, Search, IconButton, Badge,
|
|
149
|
+
AppBar, Toolbar, Logo, Search, IconButton, Badge, Tooltip,
|
|
145
150
|
SidebarNav, SidebarItem, List, Typography, Box, Avatar,
|
|
151
|
+
useColorScheme,
|
|
146
152
|
} from "@matteoaliano/forest-ui";
|
|
147
153
|
import NotificationsOutlined from "@mui/icons-material/NotificationsOutlined";
|
|
148
154
|
import SettingsOutlined from "@mui/icons-material/SettingsOutlined";
|
|
155
|
+
import DarkModeOutlined from "@mui/icons-material/DarkModeOutlined";
|
|
156
|
+
import LightModeOutlined from "@mui/icons-material/LightModeOutlined";
|
|
149
157
|
import HomeOutlined from "@mui/icons-material/HomeOutlined";
|
|
150
158
|
import BarChartOutlined from "@mui/icons-material/BarChartOutlined";
|
|
151
159
|
import PeopleOutlined from "@mui/icons-material/PeopleOutlined";
|
|
152
160
|
|
|
161
|
+
// Brand: always the logomark, negative artwork in dark mode.
|
|
162
|
+
function AppShellLogo() {
|
|
163
|
+
const { mode, systemMode } = useColorScheme();
|
|
164
|
+
const resolved = mode === "system" ? systemMode : mode;
|
|
165
|
+
return (
|
|
166
|
+
<Logo
|
|
167
|
+
product="studio"
|
|
168
|
+
variant="logomark"
|
|
169
|
+
color={resolved === "dark" ? "negative" : "default"}
|
|
170
|
+
/>
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Mandatory light/dark switch. `useColorScheme` toggles the `.dark` class on
|
|
175
|
+
// <html> for you — the preset's dark tokens invert the whole palette.
|
|
176
|
+
function ColorModeToggle() {
|
|
177
|
+
const { mode, systemMode, setMode } = useColorScheme();
|
|
178
|
+
const resolved = mode === "system" ? systemMode : mode;
|
|
179
|
+
const isDark = resolved === "dark";
|
|
180
|
+
return (
|
|
181
|
+
<Tooltip title={isDark ? "Switch to light mode" : "Switch to dark mode"}>
|
|
182
|
+
<IconButton
|
|
183
|
+
size="small"
|
|
184
|
+
color="inherit"
|
|
185
|
+
aria-label="Toggle color mode"
|
|
186
|
+
onClick={() => setMode(isDark ? "light" : "dark")}
|
|
187
|
+
>
|
|
188
|
+
{isDark ? <LightModeOutlined fontSize="small" /> : <DarkModeOutlined fontSize="small" />}
|
|
189
|
+
</IconButton>
|
|
190
|
+
</Tooltip>
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
153
194
|
function AppShell({ children }) {
|
|
154
195
|
const [sidebarOpen, setSidebarOpen] = useState(true);
|
|
155
196
|
|
|
@@ -162,16 +203,19 @@ function AppShell({ children }) {
|
|
|
162
203
|
sx={{ zIndex: (theme) => theme.zIndex.drawer + 1 }}
|
|
163
204
|
>
|
|
164
205
|
<Toolbar variant="dense" sx={{ px: { xs: "16px", sm: "16px" } }}>
|
|
165
|
-
{/* Left — Logo */}
|
|
206
|
+
{/* Left — Logo (centered over the 48px sidebar rail) */}
|
|
166
207
|
<Box sx={{ flex: 1, display: "flex", alignItems: "center" }}>
|
|
167
|
-
<
|
|
208
|
+
<Box sx={{ width: 48, ml: "-16px", display: "flex", justifyContent: "center" }}>
|
|
209
|
+
<AppShellLogo />
|
|
210
|
+
</Box>
|
|
168
211
|
</Box>
|
|
169
212
|
{/* Center — Search (always visually centered) */}
|
|
170
213
|
<Box sx={{ flex: 1, display: "flex", justifyContent: "center" }}>
|
|
171
214
|
<Search size="small" sx={{ width: "100%", maxWidth: 480 }} />
|
|
172
215
|
</Box>
|
|
173
|
-
{/* Right — Actions */}
|
|
216
|
+
{/* Right — Actions (ColorModeToggle is mandatory) */}
|
|
174
217
|
<Box sx={{ flex: 1, display: "flex", alignItems: "center", justifyContent: "flex-end", gap: 1 }}>
|
|
218
|
+
<ColorModeToggle />
|
|
175
219
|
<IconButton size="small" color="inherit">
|
|
176
220
|
<Badge color="error" variant="dot">
|
|
177
221
|
<NotificationsOutlined fontSize="small" />
|
|
@@ -216,6 +260,8 @@ function AppShell({ children }) {
|
|
|
216
260
|
```
|
|
217
261
|
|
|
218
262
|
**Key patterns:**
|
|
263
|
+
- `ColorModeToggle` in AppBar actions — mandatory, single sanctioned dark-mode switch
|
|
264
|
+
- Logomark flips to `color="negative"` in dark mode — brand stays legible on the dark AppBar
|
|
219
265
|
- `Toolbar variant="dense"` — 48px height instead of 64px
|
|
220
266
|
- Three `flex: 1` columns in Toolbar — keeps Search centered regardless of left/right content width
|
|
221
267
|
- `px: { xs: "16px", sm: "16px" }` on Toolbar — aligns logo with sidebar icons (overrides MUI's responsive 24px default)
|
|
@@ -223,6 +269,8 @@ function AppShell({ children }) {
|
|
|
223
269
|
- `height: "100%"` on SidebarNav — sidebar border extends full height
|
|
224
270
|
- `backgroundColor: "background.paper"` on content — contrasts with sidebar/AppBar background
|
|
225
271
|
|
|
272
|
+
See the **Patterns/App Shell** Storybook entry for the canonical, runnable version (Default / CollapsedSidebar / HoverSidebar / StudioBrand).
|
|
273
|
+
|
|
226
274
|
### Detail Drawer
|
|
227
275
|
|
|
228
276
|
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.
|