@mantis-core/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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# @mantis-core/ui
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- fbd4637: Add the `mantis-llm` CLI (`pnpm exec mantis-llm`) so consuming projects can wire LLM discovery in one command.
|
|
8
|
+
|
|
9
|
+
- New `bin` shipped with the package: `mantis-llm` writes/updates a managed pointer block into the consuming project's `CLAUDE.md` / `AGENTS.md` (creating them if missing, idempotent), so any LLM working in that repo discovers the installed `@mantis-core/*` docs in `node_modules` without being told each session.
|
|
10
|
+
- The block carries the key usage rules inline (import from `@mantis-core/ui`, flat `FieldControllerProps`, mount providers when `next-core` is present) plus per-package pointers — all conditioned on which `@mantis-core/*` packages are actually installed.
|
|
11
|
+
- `--check` fails (exit ≠0) when the block is missing or stale, for use in a consumer's CI; `--files=` targets specific files.
|
|
12
|
+
- Tightened the published `files` allowlist so internal contributor docs (`STORYBOOK_*`, `component-doc-schema.md`) no longer ship to consumers, while all consumer-facing docs (`components/`, `field-controller.md`, `style-imports.md`, `dependency-map.md`) still do.
|
|
13
|
+
|
|
3
14
|
## 0.6.0
|
|
4
15
|
|
|
5
16
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -78,9 +78,15 @@ Example:
|
|
|
78
78
|
## LLM discovery
|
|
79
79
|
|
|
80
80
|
This package ships `llms.txt` (component index), `catalog.json` (machine-readable catalog), and
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
per-component `docs/` as published files, so a consuming app's LLM can read them straight from
|
|
82
|
+
`node_modules/@mantis-core/ui/`. To point the LLM at them, run once per consuming project:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
pnpm exec mantis-llm # or: npx mantis-llm · yarn mantis-llm
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
This writes a managed pointer block into the project's `CLAUDE.md` / `AGENTS.md` (idempotent;
|
|
89
|
+
`--check` for CI). See the "LLM discovery" section in the repo root `docs/consumir/GETTING_STARTED.md`.
|
|
84
90
|
|
|
85
91
|
## Public API Summary
|
|
86
92
|
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// mantis-llm — escribe/actualiza un bloque de punteros de discovery para LLMs
|
|
3
|
+
// (CLAUDE.md / AGENTS.md) en el proyecto que consume @mantis-core/*.
|
|
4
|
+
//
|
|
5
|
+
// El objetivo: que cualquier LLM trabajando en un proyecto consumidor sepa qué
|
|
6
|
+
// componentes existen y cómo usarlos, sin que el humano tenga que recordárselo.
|
|
7
|
+
// Los docs ya viajan dentro de node_modules/@mantis-core/*/ — esto solo agrega el
|
|
8
|
+
// puntero en la raíz del repo (lo único que un LLM lee por defecto).
|
|
9
|
+
//
|
|
10
|
+
// Uso (post-install, el bin vive dentro de @mantis-core/ui):
|
|
11
|
+
// pnpm exec mantis-llm # escribe/actualiza CLAUDE.md y AGENTS.md
|
|
12
|
+
// pnpm exec mantis-llm --files=CLAUDE.md
|
|
13
|
+
// pnpm exec mantis-llm --check # no escribe; exit≠0 si falta o está viejo (para CI)
|
|
14
|
+
// pnpm exec mantis-llm --self-check
|
|
15
|
+
|
|
16
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
17
|
+
import { dirname, resolve } from "node:path";
|
|
18
|
+
import { fileURLToPath } from "node:url";
|
|
19
|
+
|
|
20
|
+
const BLOCK_VERSION = "v1";
|
|
21
|
+
const START = `<!-- mantis-core:llm-pointers ${BLOCK_VERSION} START (auto-generado por \`pnpm exec mantis-llm\` — no editar a mano) -->`;
|
|
22
|
+
const END = `<!-- mantis-core:llm-pointers END -->`;
|
|
23
|
+
// Regex tolerante a cualquier versión previa del marcador START.
|
|
24
|
+
const BLOCK_RE =
|
|
25
|
+
/<!-- mantis-core:llm-pointers[^>]*START[^>]*-->[\s\S]*?<!-- mantis-core:llm-pointers END -->/;
|
|
26
|
+
|
|
27
|
+
const SELF_DIR = dirname(fileURLToPath(import.meta.url)); // .../@mantis-core/ui/bin
|
|
28
|
+
const SCOPE_DIR = resolve(SELF_DIR, "..", ".."); // .../@mantis-core
|
|
29
|
+
|
|
30
|
+
// Solo paquetes que shipean docs de consumo. commons/utils no shipean llms.txt.
|
|
31
|
+
const PACKAGES = ["ui", "next-core", "nuqs", "styles"];
|
|
32
|
+
|
|
33
|
+
const isInstalled = (pkg) => existsSync(resolve(SCOPE_DIR, pkg, "llms.txt"));
|
|
34
|
+
|
|
35
|
+
/** Líneas de puntero por paquete — SOLO archivos de consumo (nunca docs internos). */
|
|
36
|
+
function pointerLines(pkg) {
|
|
37
|
+
const base = `node_modules/@mantis-core/${pkg}`;
|
|
38
|
+
switch (pkg) {
|
|
39
|
+
case "ui":
|
|
40
|
+
return [
|
|
41
|
+
`- **@mantis-core/ui** — \`${base}/llms.txt\` (índice de componentes: whenToUse + import por componente, léelo primero) · \`${base}/catalog.json\` (props/tipos — ~132KB, solo para grep puntual) · \`${base}/docs/components/*.md\` (1 doc con ejemplo compilable por componente) · \`${base}/docs/contracts/field-controller.md\` (contrato de form fields) · \`${base}/docs/contracts/style-imports.md\` (qué SCSS importar)`,
|
|
42
|
+
];
|
|
43
|
+
case "next-core":
|
|
44
|
+
return [
|
|
45
|
+
`- **@mantis-core/next-core** — \`${base}/llms.txt\` · \`${base}/docs/setup.md\` (install → providers → estilos → primer form + tabla) · \`${base}/docs/theme.md\` (MantisThemeProvider: preset, color ramps, dark mode) · \`${base}/docs/providers.md\` · \`${base}/docs/hooks.md\` · \`${base}/docs/toasts.md\``,
|
|
46
|
+
];
|
|
47
|
+
case "nuqs":
|
|
48
|
+
return [
|
|
49
|
+
`- **@mantis-core/nuqs** — \`${base}/llms.txt\` (parsers de URL-state + \`useDataTableQuery\`)`,
|
|
50
|
+
];
|
|
51
|
+
case "styles":
|
|
52
|
+
return [
|
|
53
|
+
`- **@mantis-core/styles** — \`${base}/llms.txt\` (partials SCSS + design tokens)`,
|
|
54
|
+
];
|
|
55
|
+
default:
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Construye el CONTENIDO del bloque (sin los marcadores), condicionado a lo instalado. */
|
|
61
|
+
function buildBlock(installed) {
|
|
62
|
+
const hasNextCore = installed.includes("next-core");
|
|
63
|
+
|
|
64
|
+
const rules = [
|
|
65
|
+
"- Importá desde `@mantis-core/ui` (o subpaths `@mantis-core/ui/components/*`). Nunca desde `packages/*`.",
|
|
66
|
+
"- Los form fields son `FieldControllerProps` planos: `<TextField name control label />` — sin `controller={{...}}`.",
|
|
67
|
+
];
|
|
68
|
+
if (hasNextCore) {
|
|
69
|
+
rules.push(
|
|
70
|
+
"- Montá `MantisCoreBaseProvider` + `MantisThemeProvider` una sola vez en el root. Ver `node_modules/@mantis-core/next-core/docs/setup.md`.",
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
rules.push(
|
|
74
|
+
"- Leé el `llms.txt` del paquete primero (índice chico). No leas `catalog.json` entero (~132KB): usalo solo para buscar props puntuales.",
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const pointers = installed.flatMap(pointerLines);
|
|
78
|
+
|
|
79
|
+
return [
|
|
80
|
+
"## Mantis Core — componentes `@mantis-core/*` (LEER antes de escribir UI)",
|
|
81
|
+
"",
|
|
82
|
+
"Este proyecto usa `@mantis-core/*`. **Antes de crear o editar cualquier UI** con estos paquetes,",
|
|
83
|
+
"leé sus docs — ya están instaladas en `node_modules`, no hace falta clonar ni buscar en internet.",
|
|
84
|
+
"",
|
|
85
|
+
"### Reglas (no romper)",
|
|
86
|
+
...rules,
|
|
87
|
+
"",
|
|
88
|
+
"### Dónde mirar (por paquete instalado)",
|
|
89
|
+
...pointers,
|
|
90
|
+
].join("\n");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Envuelve el contenido con los marcadores. */
|
|
94
|
+
const wrap = (block) => `${START}\n${block}\n${END}`;
|
|
95
|
+
|
|
96
|
+
/** Extrae el contenido interno (sin marcadores) de un bloque ya presente. */
|
|
97
|
+
function extractInner(fileContent) {
|
|
98
|
+
const m = fileContent.match(BLOCK_RE);
|
|
99
|
+
if (!m) return null;
|
|
100
|
+
return m[0].replace(/^<!--[^>]*-->\n?/, "").replace(/\n?<!-- mantis-core:llm-pointers END -->$/, "");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Inserta/reemplaza el bloque en el contenido del archivo.
|
|
105
|
+
* @returns {string} nuevo contenido
|
|
106
|
+
*/
|
|
107
|
+
function applyBlock(existing, wrapped) {
|
|
108
|
+
if (existing == null || existing.trim() === "") {
|
|
109
|
+
return wrapped + "\n";
|
|
110
|
+
}
|
|
111
|
+
if (BLOCK_RE.test(existing)) {
|
|
112
|
+
return existing.replace(BLOCK_RE, wrapped);
|
|
113
|
+
}
|
|
114
|
+
// Sin bloque previo → append preservando el contenido del usuario.
|
|
115
|
+
const sep = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
116
|
+
return existing + sep + wrapped + "\n";
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function parseArgs(argv) {
|
|
120
|
+
const args = { files: ["CLAUDE.md", "AGENTS.md"], check: false, selfCheck: false, help: false };
|
|
121
|
+
for (const a of argv) {
|
|
122
|
+
if (a === "--check") args.check = true;
|
|
123
|
+
else if (a === "--self-check") args.selfCheck = true;
|
|
124
|
+
else if (a === "-h" || a === "--help") args.help = true;
|
|
125
|
+
else if (a.startsWith("--files=")) {
|
|
126
|
+
args.files = a
|
|
127
|
+
.slice("--files=".length)
|
|
128
|
+
.split(",")
|
|
129
|
+
.map((s) => s.trim())
|
|
130
|
+
.filter(Boolean);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return args;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const HELP = `mantis-llm — puntero de discovery de @mantis-core/* para LLMs
|
|
137
|
+
|
|
138
|
+
pnpm exec mantis-llm [--files=CLAUDE.md,AGENTS.md] [--check]
|
|
139
|
+
|
|
140
|
+
Escribe/actualiza un bloque gestionado en los archivos de agente del proyecto
|
|
141
|
+
consumidor apuntando a las docs de @mantis-core/* dentro de node_modules.
|
|
142
|
+
|
|
143
|
+
(sin flags) Escribe/actualiza el bloque en CLAUDE.md y AGENTS.md (los crea si faltan).
|
|
144
|
+
--files=... Lista de archivos destino separada por comas.
|
|
145
|
+
--check No escribe. Exit≠0 si el bloque falta o está desactualizado (para CI).
|
|
146
|
+
--self-check Corre el auto-test interno (idempotencia + check) en un dir temporal.
|
|
147
|
+
`;
|
|
148
|
+
|
|
149
|
+
function run(cwd, argv) {
|
|
150
|
+
const args = parseArgs(argv);
|
|
151
|
+
if (args.help) {
|
|
152
|
+
process.stdout.write(HELP);
|
|
153
|
+
return 0;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const installed = PACKAGES.filter(isInstalled);
|
|
157
|
+
if (installed.length === 0) {
|
|
158
|
+
process.stderr.write(
|
|
159
|
+
"mantis-llm: no encontré ningún @mantis-core/* con llms.txt junto a este bin. " +
|
|
160
|
+
"¿Se corrió desde un proyecto con @mantis-core instalado?\n",
|
|
161
|
+
);
|
|
162
|
+
return 1;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const wrapped = wrap(buildBlock(installed));
|
|
166
|
+
const expectedInner = buildBlock(installed).trim();
|
|
167
|
+
|
|
168
|
+
if (args.check) {
|
|
169
|
+
let drift = false;
|
|
170
|
+
for (const file of args.files) {
|
|
171
|
+
const path = resolve(cwd, file);
|
|
172
|
+
const inner = existsSync(path) ? extractInner(readFileSync(path, "utf8")) : null;
|
|
173
|
+
if (inner == null) {
|
|
174
|
+
process.stderr.write(`mantis-llm --check: falta el bloque en ${file}\n`);
|
|
175
|
+
drift = true;
|
|
176
|
+
} else if (inner.trim() !== expectedInner) {
|
|
177
|
+
process.stderr.write(`mantis-llm --check: bloque desactualizado en ${file} (re-corré \`pnpm exec mantis-llm\`)\n`);
|
|
178
|
+
drift = true;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (!drift) process.stdout.write(`mantis-llm --check: OK (${installed.join(", ")})\n`);
|
|
182
|
+
return drift ? 1 : 0;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
for (const file of args.files) {
|
|
186
|
+
const path = resolve(cwd, file);
|
|
187
|
+
const existed = existsSync(path);
|
|
188
|
+
const existing = existed ? readFileSync(path, "utf8") : null;
|
|
189
|
+
const next = applyBlock(existing, wrapped);
|
|
190
|
+
if (existed && next === existing) {
|
|
191
|
+
process.stdout.write(`mantis-llm: ${file} ya estaba al día\n`);
|
|
192
|
+
} else {
|
|
193
|
+
writeFileSync(path, next);
|
|
194
|
+
process.stdout.write(`mantis-llm: ${existed ? "actualizado" : "creado"} ${file}\n`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
process.stdout.write(`mantis-llm: paquetes detectados → ${installed.join(", ")}\n`);
|
|
198
|
+
return 0;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// --- self-check (ponytail: un check runnable, sin frameworks) --------------
|
|
202
|
+
async function selfCheck() {
|
|
203
|
+
const { mkdtempSync, rmSync } = await import("node:fs");
|
|
204
|
+
const { tmpdir } = await import("node:os");
|
|
205
|
+
const assert = (await import("node:assert/strict")).default;
|
|
206
|
+
|
|
207
|
+
const tmp = mkdtempSync(resolve(tmpdir(), "mantis-llm-"));
|
|
208
|
+
const claude = resolve(tmp, "CLAUDE.md");
|
|
209
|
+
|
|
210
|
+
// Reusa la lógica pura contra un set "instalado" fijo (no toca disco real de paquetes).
|
|
211
|
+
const installed = ["ui", "next-core"];
|
|
212
|
+
const wrapped = wrap(buildBlock(installed));
|
|
213
|
+
|
|
214
|
+
// 1) crea el archivo con el bloque
|
|
215
|
+
writeFileSync(claude, applyBlock(null, wrapped));
|
|
216
|
+
let content = readFileSync(claude, "utf8");
|
|
217
|
+
assert.ok(content.includes(START), "debe escribir el marcador START");
|
|
218
|
+
assert.ok(content.includes("MantisThemeProvider"), "con next-core debe incluir la regla de providers");
|
|
219
|
+
|
|
220
|
+
// 2) idempotente: 2ª aplicación no duplica
|
|
221
|
+
const again = applyBlock(content, wrapped);
|
|
222
|
+
assert.equal((again.match(/mantis-core:llm-pointers .*START/g) || []).length, 1, "no debe duplicar el bloque");
|
|
223
|
+
assert.equal(again, content, "2ª corrida no debe cambiar nada");
|
|
224
|
+
|
|
225
|
+
// 3) regla condicional: sin next-core NO aparece la regla de providers
|
|
226
|
+
const uiOnly = applyBlock(null, wrap(buildBlock(["ui"])));
|
|
227
|
+
assert.ok(!uiOnly.includes("MantisThemeProvider"), "sin next-core no debe mencionar providers");
|
|
228
|
+
|
|
229
|
+
// 4) extractInner round-trips y detecta drift
|
|
230
|
+
assert.equal(extractInner(content).trim(), buildBlock(installed).trim(), "extractInner debe recuperar el bloque");
|
|
231
|
+
|
|
232
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
233
|
+
process.stdout.write("mantis-llm --self-check: OK\n");
|
|
234
|
+
return 0;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const argv = process.argv.slice(2);
|
|
238
|
+
if (argv.includes("--self-check")) {
|
|
239
|
+
process.exit(await selfCheck());
|
|
240
|
+
} else {
|
|
241
|
+
process.exit(run(process.cwd(), argv));
|
|
242
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mantis-core/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Reusable React UI components for Mantis Core.",
|
|
6
6
|
"type": "module",
|
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
},
|
|
13
13
|
"main": "./dist/index.js",
|
|
14
14
|
"types": "./dist/index.d.ts",
|
|
15
|
+
"bin": {
|
|
16
|
+
"mantis-llm": "./bin/mantis-llm.mjs"
|
|
17
|
+
},
|
|
15
18
|
"exports": {
|
|
16
19
|
".": {
|
|
17
20
|
"types": "./dist/index.d.ts",
|
|
@@ -42,7 +45,12 @@
|
|
|
42
45
|
},
|
|
43
46
|
"files": [
|
|
44
47
|
"dist",
|
|
45
|
-
"
|
|
48
|
+
"bin",
|
|
49
|
+
"docs/README.md",
|
|
50
|
+
"docs/components",
|
|
51
|
+
"docs/contracts/field-controller.md",
|
|
52
|
+
"docs/contracts/style-imports.md",
|
|
53
|
+
"docs/contracts/dependency-map.md",
|
|
46
54
|
"README.md",
|
|
47
55
|
"COMPONENTS.md",
|
|
48
56
|
"llms.txt",
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
# Storybook Implementation & VoBo Rules — Mantis Core PR11 Forms stage
|
|
2
|
-
|
|
3
|
-
This is the executable contract for finishing the PrimeReact 11 migration of the **reusable** surface
|
|
4
|
-
(Forms family + `Button` + Toast) using Storybook as the isolated workbench and sign-off (VoBo) surface.
|
|
5
|
-
A subagent implementing a component MUST follow **Rule Set 1**; a reviewer/supervisor signing off MUST
|
|
6
|
-
apply **Rule Set 2**. Non-Forms components and `apps/demo-prime11` are FROZEN this stage (do not touch).
|
|
7
|
-
|
|
8
|
-
Scope this stage: existing Forms wrappers (buckets A/B/C), `Button`, Toast migration, and the
|
|
9
|
-
`useDataTableQuery` hook. **Deferred** (segregated but NOT built now): CheckboxGroup, Knob, Listbox,
|
|
10
|
-
ToggleButtonGroup, ButtonGroup, SpeedDial.
|
|
11
|
-
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
## Forms segregation by compatibility (READ FIRST)
|
|
15
|
-
|
|
16
|
-
`ifta`/`float` label modes and icon-fields (`InputGroup` addons) apply **only** to text-box inputs.
|
|
17
|
-
Wrapping a checkbox/rating/otp in `FloatLabel`/`IftaLabel`/`InputGroup` breaks the render. Fields are
|
|
18
|
-
segregated into buckets; the props contract is restricted per bucket so misuse fails at compile time.
|
|
19
|
-
|
|
20
|
-
| Bucket | Components | labelMode | Icon addons | Props contract |
|
|
21
|
-
| --- | --- | --- | --- | --- |
|
|
22
|
-
| **A — text-box** | TextField, TextArea\*, NumberField, PasswordInput, SearchInput, MaskField, PhoneInput, SelectField, AutoCompleteField, DateField, TagsInput | `ifta` \| `float`(in/on/over) \| `beside` \| `none` | **Yes** (`addonBefore`/`addonAfter`) | `FieldControllerProps` (full) |
|
|
23
|
-
| **B1 — toggle inline** | CheckboxField, SwitchField, ToggleInlineButton | `beside` \| `none` (default `beside`) | No | `ChoiceFieldControllerProps` |
|
|
24
|
-
| **B2 — group/range** | RadioGroup, RangeSlider, RatingInput, OtpInput, ColorPickerInput | `top` \| `none` (default `top`) | No | `ChoiceFieldControllerProps` |
|
|
25
|
-
| **C — special** | CKEditorInput, FileDropzone (+UploadFilesPreview) → `top`/`none`; HiddenField → no shell | per row | No | own |
|
|
26
|
-
|
|
27
|
-
\* TextArea: label modes yes; icon addons limited/none (document it). PasswordInput/SearchInput/DateField
|
|
28
|
-
have a built-in icon/trigger — document interaction with `addonAfter`.
|
|
29
|
-
|
|
30
|
-
`ChoiceFieldControllerProps = Omit<FieldControllerProps, "labelMode"|"floatVariant"|"addonBefore"|"addonAfter"|"variant"> & { labelMode?: "beside"|"top"|"none" }`.
|
|
31
|
-
|
|
32
|
-
---
|
|
33
|
-
|
|
34
|
-
## Rule Set 1 — Implementation
|
|
35
|
-
|
|
36
|
-
### 1a. General (every reusable component)
|
|
37
|
-
|
|
38
|
-
1. **Clean PR11 API.** No legacy PR10 aliases; named imports from PR11 subpaths; use the primitive's compound/headless model.
|
|
39
|
-
2. **Reusable wrapper contract.** Extend/compose the primitive props; **merge `className`/`style`** (never swallow); spread safe `...rest`; overrideable defaults; **typed variants**; `forwardRef` when focus/overlay/scroll/interop matters.
|
|
40
|
-
3. **Token styling.** Base tokens / CSS vars only; **no hardcoded colors**; keep the `mantis-core-*` namespace; `cssLayer` order `theme, base, primereact`.
|
|
41
|
-
4. **Layers.** `app → modules|components|lib`, `components → lib`. Forbidden: `components→modules`, `lib→modules`, `lib→app`.
|
|
42
|
-
5. **Strict typing.** No `any`; `pnpm -s tsc --noEmit` clean.
|
|
43
|
-
6. **Story required**, co-located, with the bucket's minimum set. No story ⇒ not done.
|
|
44
|
-
7. **Fix = root cause.** A defect a story reveals is fixed at the shared point, not patched per caller.
|
|
45
|
-
|
|
46
|
-
### 1b. Forms-specific (configurable fields + RHF)
|
|
47
|
-
|
|
48
|
-
8. **Every field routes through the shared contract:** `useFieldController` (RHF binding + aria + id) → `FieldShell` (label/addon/help/error). Do not re-implement binding or label placement in the field.
|
|
49
|
-
9. **Honor the axis per bucket:** A respects `labelMode`(ifta/float/beside/none), `floatVariant`, `variant`, `size`, `fluid` from `useFieldConfig` with per-prop override; B1/B2 use `ChoiceFieldControllerProps` (label `beside`/`top`/`none`, no float/ifta/addons/variant). Never hardcode an applicable axis.
|
|
50
|
-
10. **Wire value/onChange/onBlur** correctly to the PR11 primitive; **one** `useController` per component.
|
|
51
|
-
11. **Type coercion at the controller level** when the primitive emits a different shape than the form (NumberField, DateField, OtpInput…). Document the `field.value` shape.
|
|
52
|
-
12. **Icon groups only in bucket A** via `addonBefore`/`addonAfter`. Pass the addon **wrapped in `InputGroup.Addon`** (from `@primereact/ui/inputgroup`), e.g. `addonBefore={<InputGroup.Addon>@</InputGroup.Addon>}` — raw content (a bare `<span>`) renders detached/unstyled. `FieldShell` owns `InputGroup.Root`; the consumer never wraps the whole field. B/C expose no addons.
|
|
53
|
-
13. **Cross-field dependencies** use `useWatch` (never `watch()` in render).
|
|
54
|
-
14. **`required` marker is presentational**; validation lives in `rules`.
|
|
55
|
-
15. **Docs.** Update the component doc frontmatter / `catalog.json` (`pnpm gen:catalog`) with the `field.value` shape and supported config axes.
|
|
56
|
-
|
|
57
|
-
### Parametrization (cross-cutting)
|
|
58
|
-
|
|
59
|
-
Every component must be configurable **globally** (context provider: `MantisThemeProvider` → theme/colors/surfaces/dark + `FieldConfigProvider` → labelMode/floatVariant/variant/size/fluid) **or independently** (per-prop override). `Button` and Toast are parametrized by **theme tokens** (no hardcoded color). Everything is exercised from Storybook: global axes via toolbar `globalTypes`, overrides via per-story args.
|
|
60
|
-
|
|
61
|
-
### Story taxonomy (CSF3) — per bucket
|
|
62
|
-
|
|
63
|
-
Common to all: `Default`, `Playground` (args via `argTypes`), `Invalid` (with `errorMessage`), `Disabled`,
|
|
64
|
-
`HelpText`, `WithReactHookForm` (form-decorator with `defaultValues` + submit that shows the captured value).
|
|
65
|
-
Dark mode and size/variant are exercised via toolbar toggles, not separate stories.
|
|
66
|
-
|
|
67
|
-
- **A (text-box)** — add: `LabelModes` (matrix ifta / float·in / float·on / float·over / beside / none), `Sizes`, `Variants` (outlined/filled), `Fluid`, `WithAddons`. `Async`/`Empty` where applicable (Select/AutoComplete).
|
|
68
|
-
- **B1 (toggle inline)** — add: `LabelBeside`, `LabelNone`. NO float/ifta `LabelModes`, NO `WithAddons`.
|
|
69
|
-
- **B2 (group/range)** — add: `LabelTop`, `LabelNone`, plus control-specific stories (RadioGroup `Options`/`renderOption`, Rating `stars`, Slider `range`). NO float/ifta, NO addons.
|
|
70
|
-
- **C (special)** — a story fitting the component; no label/addon axes.
|
|
71
|
-
- **Button** — `Variants`, `Sizes`, `Severities`, `Outlined`, `Text`, `Rounded`, `Loading`, `Disabled`.
|
|
72
|
-
|
|
73
|
-
---
|
|
74
|
-
|
|
75
|
-
## Rule Set 2 — Review & VoBo
|
|
76
|
-
|
|
77
|
-
A component gets VoBo only when **ALL** hold:
|
|
78
|
-
|
|
79
|
-
1. **Typing** — `pnpm -s tsc --noEmit` clean; no `any`; generics `<TFieldValues, TName>` correct.
|
|
80
|
-
2. **Best practices / lint** — `pnpm lint` clean; meets the wrapper contract (merge `className`, `...rest`, `forwardRef`, defaults, typed variants); no hardcoded colors.
|
|
81
|
-
3. **Scalability** — composes over the primitive (does not duplicate it); honors the config axis; no layer-breaking coupling.
|
|
82
|
-
4. **Meets Rule Set 1** — item-by-item (incl. 1b for Forms).
|
|
83
|
-
5. **Risk tests** — Vitest green: RHF (value shape, coercion), state, async, transformation.
|
|
84
|
-
6. **Visual VoBo (most important)** — in the running Storybook, walk **every** story: it looks right; styles not bugged; **no overflow / no "arrows sticking out"**; alignment/spacing/focus/hover/dark/size correct; overlays/portals positioned; **zero console errors**. Check each `labelMode`/`floatVariant` and the icon groups specifically.
|
|
85
|
-
7. **Supervisor VoBo** — final human sign-off, recorded per component in the ledger.
|
|
86
|
-
|
|
87
|
-
**Support automation (cheap, advisory):** a11y addon (axe) 0 violations per story; render-smoke with no console
|
|
88
|
-
errors. `@storybook/test-runner` (Playwright) and Chromatic are deferred — add only if manual visual review
|
|
89
|
-
stops scaling.
|
|
90
|
-
|
|
91
|
-
Record sign-off in `STORYBOOK_VOBO_LEDGER.md`. A component is not `done` until its row is complete and the
|
|
92
|
-
supervisor has signed.
|
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
# Storybook VoBo Ledger — PR11 Forms stage
|
|
2
|
-
|
|
3
|
-
Sign-off tracker. A component is **done** only when every column is ✅ and the supervisor has signed.
|
|
4
|
-
Legend: ✅ pass · ⬜ pending · ❌ failing/blocked · N/A not applicable.
|
|
5
|
-
Columns map to `STORYBOOK_IMPLEMENTATION_AND_VOBO.md` Rule Set 2 (typing / lint / rules = Rule Set 1 / tests / visual / supervisor).
|
|
6
|
-
|
|
7
|
-
## Batch S — Contract segregation
|
|
8
|
-
|
|
9
|
-
| Item | typing | lint | rules | tests | visual | supervisor |
|
|
10
|
-
| --- | :--: | :--: | :--: | :--: | :--: | :--: |
|
|
11
|
-
| ChoiceFieldControllerProps | ✅ | ⬜ | ✅ | ✅ | N/A | ⬜ |
|
|
12
|
-
| FieldShell `top` mode (hard-cap = type+default, not runtime) | ✅ | ⬜ | ✅ | ✅ | ⬜ (via B2 stories) | ⬜ |
|
|
13
|
-
| field-typing.test-d (B1/B2 reject float/ifta labelMode) | ✅ | ⬜ | ✅ | ✅ | N/A | ⬜ |
|
|
14
|
-
|
|
15
|
-
## Bucket A — text-box ✅ supervisor VoBo 2026-07-05
|
|
16
|
-
|
|
17
|
-
Story taxonomy + WithAddons (InputGroup.Addon) done; 4 render defects fixed root-cause vs official PR11 docs
|
|
18
|
-
(NumberField addons `InputNumber.Root as={Fragment}`; PasswordInput expose size/variant; SearchInput `fluid`).
|
|
19
|
-
lint column: repo-wide `react-hooks/refs` debt pre-exists on unchanged lines — no NEW lint introduced (†).
|
|
20
|
-
|
|
21
|
-
| Component | typing | lint | rules | tests | visual | supervisor |
|
|
22
|
-
| --- | :--: | :--: | :--: | :--: | :--: | :--: |
|
|
23
|
-
| TextField | ✅ | † | ✅ | ✅ | ✅ | ✅ |
|
|
24
|
-
| TextArea | ✅ | † | ✅ | N/A | ✅ | ✅ |
|
|
25
|
-
| NumberField | ✅ | † | ✅ | N/A | ✅ | ✅ |
|
|
26
|
-
| PasswordInput | ✅ | † | ✅ | ✅ | ✅ | ✅ |
|
|
27
|
-
| SearchInput | ✅ | † | ✅ | N/A | ✅ | ✅ |
|
|
28
|
-
| MaskField | ✅ | † | ✅ | ✅ | ✅ | ✅ |
|
|
29
|
-
| PhoneInput | ✅ | † | ✅ | ✅ | ✅ | ✅ |
|
|
30
|
-
| SelectField | ✅ | † | ✅ | N/A | ✅‡ | ✅ |
|
|
31
|
-
| AutoCompleteField | ✅ | † | ✅ | N/A | ✅‡ | ✅ |
|
|
32
|
-
| DateField | ✅ | † | ✅ | ✅ | ✅‡ | ✅ |
|
|
33
|
-
| TagsInput | ✅ | † | ✅ | ✅ | ✅‡ | ✅ |
|
|
34
|
-
|
|
35
|
-
‡ Visual self-verified via Playwright + preview server (DOM measurement + screenshots) after fixing: placeholder overlap, multiselect filter/checkmark, creatable-in-portal, Today/Clear button bar, themed chips, **TagsInput nested focus ring** (the `.p-inputtags` container owns the field border/focus ring — the entry `InputText` was rendering a SECOND border+focus box-shadow/outline; made it chrome-less via inline `border/background/box-shadow/outline:0`, matching PR11's own `.p-inputtags .p-autocomplete-input` rule), and the **fluid axis** (see below). Awaiting final supervisor sign-off.
|
|
36
|
-
|
|
37
|
-
**FLUID AXIS — root-caused + fixed (verified by DOM width measurement, fluid on↔off via the SB globals channel).**
|
|
38
|
-
Root cause: in PrimeReact 11.0.0-rc.1 the `<Fluid>` **context does NOT propagate** to composed inputs inside `FieldShell`
|
|
39
|
-
(`.p-fluid` wrapper renders full-width but the inner `.p-inputtext` stayed inline-block ~181px — no `p-inputtext-fluid`).
|
|
40
|
-
So NO plain field was actually full-width under `config.fluid`; the earlier overlay `w-full` hacks only masked it on Select/Date/AutoComplete.
|
|
41
|
-
Fix (PR11-idiomatic): every PR11 field primitive reads `props.fluid ?? context.$fluid` and applies its `p-<name>-fluid` width:100% class,
|
|
42
|
-
so `useFieldController` now resolves `fluid = props.fluid ?? config.fluid` and each bucket-A field passes `fluid={f.fluid}` to its primitive.
|
|
43
|
-
Verified widths in the 480px story frame (fluid ON → ~416, OFF → intrinsic): TextField 416↔181, TextArea 416↔166, NumberField 416↔181,
|
|
44
|
-
SelectField 416↔62, AutoCompleteField 416↔181, DateField 416↔205 (root **and** inner input — the input sits in `IconField` where the
|
|
45
|
-
`fluid` prop resolves to ~46px, so it keeps `w-full` to fill the fluid-driven root), MaskField/PhoneInput 416↔181.
|
|
46
|
-
Intentionally **always full-width** (their root is a block-level container that fills the parent regardless of the prop — auto-mode would
|
|
47
|
-
leave a mismatched input, and full is the sensible default): SearchInput (`IconField`), PasswordInput (`IconField` in toggle mode), TagsInput (`InputTags`).
|
|
48
|
-
|
|
49
|
-
† Pre-existing repo lint debt (`react-hooks/refs` flags `{...f.aria}`/`f.disabled` spreads across ALL fields, on lines untouched by this work). Tracked as a separate cleanup, not a blocker for this stage.
|
|
50
|
-
| SelectField | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ |
|
|
51
|
-
| AutoCompleteField | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ |
|
|
52
|
-
| DateField | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ |
|
|
53
|
-
| TagsInput | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ |
|
|
54
|
-
|
|
55
|
-
## Bucket B1 — toggle inline
|
|
56
|
-
|
|
57
|
-
Stories done (Default/HelpText/Required/Disabled/LabelBeside/LabelNone; Toggle adds Multiple/WithRenderOption).
|
|
58
|
-
Visual self-verified via Playwright DOM+interaction (screenshots were flaky): themed controls, label beside,
|
|
59
|
-
click→checked/on/pressed all render (incl. ToggleButton rc-context workaround). No defects. lint = repo debt (†).
|
|
60
|
-
|
|
61
|
-
| Component | typing | lint | rules | tests | visual | supervisor |
|
|
62
|
-
| --- | :--: | :--: | :--: | :--: | :--: | :--: |
|
|
63
|
-
| CheckboxField | ✅ | † | ✅ | N/A | ✅‡ | ✅ |
|
|
64
|
-
| SwitchField | ✅ | † | ✅ | N/A | ✅‡ | ✅ |
|
|
65
|
-
| ToggleInlineButton | ✅ | † | ✅ | N/A | ✅‡ | ✅ |
|
|
66
|
-
|
|
67
|
-
## Bucket B2 — group/range
|
|
68
|
-
|
|
69
|
-
Stories done (Default/HelpText/Required/Disabled/LabelTop/LabelNone + control-specific). Visual self-verified via
|
|
70
|
-
Playwright (label on top, themed controls). Fix: RangeSlider range mode rendered ONE handle → now two indexed
|
|
71
|
-
handles (official Slider Range example). lint = repo debt (†). Awaiting final supervisor sign-off.
|
|
72
|
-
|
|
73
|
-
| Component | typing | lint | rules | tests | visual | supervisor |
|
|
74
|
-
| --- | :--: | :--: | :--: | :--: | :--: | :--: |
|
|
75
|
-
| RadioGroup | ✅ | † | ✅ | ✅ | ✅‡ | ✅ |
|
|
76
|
-
| RangeSlider | ✅ | † | ✅ | ✅ | ✅‡ (fixed range handles) | ✅ |
|
|
77
|
-
| RatingInput | ✅ | † | ✅ | ✅ | ✅‡ | ✅ |
|
|
78
|
-
| OtpInput | ✅ | † | ✅ | ✅ | ✅‡ | ✅ |
|
|
79
|
-
| ColorPickerInput | ✅ | † | ✅ | N/A | ✅‡ | ✅ |
|
|
80
|
-
|
|
81
|
-
## Bucket C — special
|
|
82
|
-
|
|
83
|
-
Stories written; typecheck + build green. Playwright pass: CKEditor + HiddenField OK; FileDropzone/UploadFilesPreview
|
|
84
|
-
have an open rendering issue (see notes). Batch-0 baseline improved: wired `@mantis-core/styles` SCSS (via sass) into
|
|
85
|
-
`.storybook/preview.tsx`, and CKEditorInput now self-imports `ckeditor5/ckeditor5.css`.
|
|
86
|
-
|
|
87
|
-
| Component | typing | lint | rules | tests | visual | supervisor |
|
|
88
|
-
| --- | :--: | :--: | :--: | :--: | :--: | :--: |
|
|
89
|
-
| CKEditorInput | ✅ | † | ✅ | ✅ | ✅‡ (self-imports ckeditor CSS + respects tokens) | ✅ |
|
|
90
|
-
| FileDropzone | ✅ | † | ✅ | N/A | ✅‡ (respects tokens — dashed border/surface/icon) | ✅ |
|
|
91
|
-
| UploadFilesPreview | ✅ | † | ✅ | N/A | ✅‡ (respects tokens — dropzone + cover-badge tiles) | ✅ |
|
|
92
|
-
| HiddenField | ✅ | † | ✅ | N/A | N/A (invisible, wiring verified) | ✅‡ |
|
|
93
|
-
|
|
94
|
-
RESOLVED — theme unification + dark mode for the 3 Mantis-token consumers (CKEditor / FileDropzone / UploadFilesPreview):
|
|
95
|
-
these are the ONLY Forms components that paint from the Mantis `--*` token layer (via `@mantis-core/styles` SCSS); every
|
|
96
|
-
other field renders through PrimeReact primitives (`--p-*`) and already followed the toolbar + dark. Two defects, both fixed:
|
|
97
|
-
|
|
98
|
-
1. **Tokens were static** — `tokens.template.scss` is a fixed brand palette (green, no working dark: its `.dark` block
|
|
99
|
-
keyed off `.dark` while the app toggles `.p-dark`). So the 3 never followed the toolbar primary/surface/dark. Fix:
|
|
100
|
-
`MantisStorybookProvider` now derives the Mantis tokens (`--background/--foreground/--card/--primary/--accent/--muted/
|
|
101
|
-
--border/--input/--ring`) from the SAME `primary`/`surface`/`dark` globals that drive the PR11 preset, set inline on
|
|
102
|
-
`<html>` per active light/dark. `--accent` tracks the primary hue (one step off) so idle/hover two-tone survives.
|
|
103
|
-
|
|
104
|
-
2. **SCSS anchored fills to literal `white`** — `color-mix(var(--primary), white 96%)` forced near-white surfaces
|
|
105
|
-
regardless of theme (dark impossible). Fix: `white` → `var(--card)` in `file-dropzone.scss`, `ckeditor.scss`
|
|
106
|
-
(blockquote), and `UploadFilesPreview.tsx` inline styles — zero light change (`--card`=white in light), correct in dark.
|
|
107
|
-
Plus a `--ck-*` chrome bridge in `ckeditor.scss` so the CKEditor toolbar/buttons/dropdowns follow the theme (else dark
|
|
108
|
-
icons on dark surface). Verified via Playwright in light+emerald and dark+{rose,ocean}; `build-storybook` exit 0.
|
|
109
|
-
|
|
110
|
-
## Form utilities
|
|
111
|
-
|
|
112
|
-
| Component | typing | lint | rules | tests | visual | supervisor |
|
|
113
|
-
| --- | :--: | :--: | :--: | :--: | :--: | :--: |
|
|
114
|
-
Baseline fix this batch: `next/navigation` `useRouter()` crashed all Button-based components in react-vite Storybook
|
|
115
|
-
("invariant expected app router to be mounted") → aliased to a no-op stub (`.storybook/next-navigation-mock.ts` +
|
|
116
|
-
`main.ts` resolve.alias). Self-verified via Playwright.
|
|
117
|
-
|
|
118
|
-
| FormActionsBar | ✅ | † | ✅ | N/A | ✅‡ (renders after useRouter stub; primary "Guardar" faintness RESOLVED — was missing token layer, `--primary` now resolves) | ⬜ |
|
|
119
|
-
| FormWizard | ✅ | † | ✅ | N/A | ✅‡ (stepper + fields + submit render) | ⬜ |
|
|
120
|
-
| DirtyFormGuard | ✅ | † | ✅ | ⚠️ needs `useDirtyFormGuard.test.ts` (missing; logic-only) | N/A (invisible) | ⬜ |
|
|
121
|
-
| ErrorMessage | ✅ | † | ✅ | ⚠️ small render test recommended | ✅‡ (shows on Submit) | ⬜ |
|
|
122
|
-
| RenderField / FormFieldRender | ✅ | † | ✅ | N/A | ✅‡ (renders fields from descriptor) | ⬜ |
|
|
123
|
-
|
|
124
|
-
## Buttons
|
|
125
|
-
|
|
126
|
-
Stories authored (co-located `Button.stories.tsx`, title `Buttons/Button`): Default, Variants (8), Outlined, Text,
|
|
127
|
-
Sizes, Rounded, WithIcon, IconOnly, Loading, Disabled, Block, Playground. Visual self-verified via Playwright (light+emerald,
|
|
128
|
-
dark+ocean).
|
|
129
|
-
|
|
130
|
-
**MIGRATED to PR11's native design system (removed the deprecated PR10 SCSS layer)** — supervisor decisions: use PR11
|
|
131
|
-
severity colors from the preset (delete `buttons.scss`), and drop the unused `accent`/`premium` variants.
|
|
132
|
-
- `packages/mantis-core-styles/scss/buttons.scss` **DELETED** (+ its `@use` in `index.scss`). It was the PR10-style layer:
|
|
133
|
-
per-variant `.p-button-<variant>` mixins coloring buttons from Mantis `--*` tokens, plus dead `.p-button-premium`/`accent`.
|
|
134
|
-
- `Button.tsx`: `ButtonVariant` drops `accent` (8 variants); `variant`→PR11 `severity` only (no more `p-button-<variant>` class).
|
|
135
|
-
ALL button colors now come from the PR11 preset. Consequences (intended): `secondary` is now PR11 neutral (was amber),
|
|
136
|
-
`gray`→`contrast` (near-black light / near-white dark), success/warn/danger/info use the Aura severity palettes. `primary`
|
|
137
|
-
follows the toolbar primary ramp. `accent`/`premium` unused repo-wide, so removing them is caller-safe (verified by grep).
|
|
138
|
-
- `Button.test.tsx` updated: assert only the PR11 severity class (`p-button-warn`/`p-button-danger`), not the removed wrapper class. 5/5 pass.
|
|
139
|
-
- **PrimeIcons font**: Button's `icon` string prop (`pi pi-*`) + loading spinner (`pi pi-spin`) need `primeicons/primeicons.css`,
|
|
140
|
-
imported nowhere → added to `.storybook/preview.tsx` + `primeicons` devDep. (Forms use `@primeicons/react` SVG, so it only surfaced here.)
|
|
141
|
-
The earlier "primary Guardar faint" (FormActionsBar) is RESOLVED — was the missing token layer; `--primary` now resolves.
|
|
142
|
-
|
|
143
|
-
**Global severity colors — now controllable via the PR11 preset (not SCSS).** PR11 severity components read from the
|
|
144
|
-
primitive palettes (`success`→`{green}`, `warn`→`{orange}`, `danger`→`{red}`, `info`→`{sky}`; `secondary`/`contrast`→`{surface}`,
|
|
145
|
-
already toolbar-driven). `buildPreset(preset, primary, surface, severities?)` now takes optional per-severity ramps and injects
|
|
146
|
-
`primitive: { green, orange, red, sky }` — recoloring ALL severity components globally (buttons/tags/badges/messages), not just
|
|
147
|
-
buttons. Wired into `MantisThemeConfig.severities` (real `MantisThemeProvider`) + 4 Storybook toolbar dropdowns (Success/Warn/
|
|
148
|
-
Danger/Info), reusing the `PRIMARY_RAMPS` pool. Default (unset) = Aura defaults, byte-identical (no `primitive` block emitted).
|
|
149
|
-
Verified via Playwright: default success=green#22c55e/danger=red#ef4444/warn=orange#f97316/info=sky#0ea5e9; overriding sevSuccess→rose
|
|
150
|
-
recolors the success button to rose (and any severity component) live. next-core + ui typecheck, theme tests 13/13, build green.
|
|
151
|
-
|
|
152
|
-
| Component | typing | lint | rules | tests | visual | supervisor |
|
|
153
|
-
| --- | :--: | :--: | :--: | :--: | :--: | :--: |
|
|
154
|
-
| Button | ✅ | † | ✅ | ✅ (Button.test.tsx 5/5) | ✅‡ (8 variants PR11-native severities, light+dark, follow toolbar primary; icons+spinner render) | ⬜ |
|
|
155
|
-
|
|
156
|
-
## Toast (PR11 migration)
|
|
157
|
-
|
|
158
|
-
Migrated `@mantis-core/next-core/toasts` off `react-hot-toast` to PR11's NATIVE toaster (Zag-based singleton `toast` from
|
|
159
|
-
`@primereact/ui/toaster` + `Toast`/`Toaster` compound). `useMantisToasts` API PRESERVED (add{Success,Error,Info,Warning,Loading}Toast,
|
|
160
|
-
promiseToast, jsxToast, dismiss, remove) — now built on `toast.{success,info,warn,error}`/`.promise`/`.dismiss`. Deleted
|
|
161
|
-
`mantis-toast-theme.ts`(+ test) — styling is now native (severity colors from the preset, follow the toolbar). New `MantisToaster`
|
|
162
|
-
host (promoted from the demo's app-toaster pattern) mounted by `MantisCoreBaseProvider` (replaces the react-hot-toast `<Toaster>`;
|
|
163
|
-
`toasterProps` removed). `react-hot-toast` dep removed; `@primereact/ui` added to next-core peers. API deltas (intended): `opts` type
|
|
164
|
-
is now `MantisToastOptions` (Pick of ToastType) not react-hot-toast's; `addLoadingToast` uses `loading:true` + duration Infinity;
|
|
165
|
-
`remove`≡`dismiss`; `jsxToast` callback no longer gets a live toast handle (use the returned id + `dismiss`). CRUD hooks unaffected
|
|
166
|
-
(they mock `../toasts`; crud-hooks.test 6/6). Story `Feedback/Toast` (mounts MantisToaster + buttons per severity + promise + dismiss-all).
|
|
167
|
-
Visual self-verified via playwright-cli: toasts fire, animate in to opacity 1, render severity-themed (success green "Guardado correctamente",
|
|
168
|
-
etc.), auto-dismiss ~5s, loading persists; Zag collapses the stack by default (front shown, hover-expands) — native PR11 behavior.
|
|
169
|
-
typecheck (next-core + ui), build-storybook exit 0.
|
|
170
|
-
|
|
171
|
-
| Item | typing | lint | rules | tests | visual | supervisor |
|
|
172
|
-
| --- | :--: | :--: | :--: | :--: | :--: | :--: |
|
|
173
|
-
| useMantisToasts (PR11, API preserved) | ✅ | † | ✅ | ✅ (crud-hooks 6/6, mocked) | ✅‡ (fires + severity-themed + auto-dismiss + loading persists) | ⬜ |
|
|
174
|
-
| MantisToaster host | ✅ | † | ✅ | N/A | ✅‡ (renders toasts top-right, Zag stack) | ⬜ |
|
|
175
|
-
|
|
176
|
-
## Hook
|
|
177
|
-
|
|
178
|
-
Headless `useDataTableQuery` in `@mantis-core/nuqs` (`src/use-data-table-query.ts`): pagination + sorting + filtering in one
|
|
179
|
-
state object, with OPT-IN URL sync via nuqs. Two backends selected by the (construction-time constant) `urlSync`: `useState`
|
|
180
|
-
(headless, no routing) or nuqs `useQueryStates` (deep-link + back/forward) — identical `DataTableQuery<F>` return in both.
|
|
181
|
-
Filter kinds map to parsers: text→`parseAsString`, enum→`createParseAsEnumArray`, stringArray→`parseAsStringArray`, json→`parseAsSuperJson`.
|
|
182
|
-
`setPageSize`/`setFilter`/`setFilters`/`clearFilter`/`resetFilters` reset page to 0; `toggleSort` cycles asc→desc→cleared; `multiSort`
|
|
183
|
-
keeps an array; `queryParams` is the backend-ready `{page,pageSize,sort,filters}`. Root-cause fix along the way: `parseAsSuperJson`
|
|
184
|
-
had an unannotated `createParser` generic that inferred to `null`, making `.withDefault()` uncallable for every caller → annotated
|
|
185
|
-
`createParser<unknown>`. Story `Hooks/useDataTableQuery` — a rich, themed "Directorio de cuentas" admin demo built from the **Mantis
|
|
186
|
-
Forms wrappers** (TextField/SelectField×2 multiple/TagsInput/RangeSlider/DateField/SwitchField in a RHF form bridged one-way to the
|
|
187
|
-
hook via useWatch→setFilters, with a useRef dedupe so unrelated re-renders don't reset page) + a themed sortable table (Tag status
|
|
188
|
-
badges) + Prev/Next + rows-per-page, wired to **REAL nuqs URL sync** via `NuqsAdapter` (`nuqs/adapters/react`, `keyPrefix:"dt"`) and a
|
|
189
|
-
live "URL / queryParams" panel. Verified via playwright-cli in light+emerald AND dark+ocean: typing search → `?dtq=…` + table narrows;
|
|
190
|
-
paginate → `dtpage` (1-based via parseAsIndex) persists (no bridge-reset); sort → `dtsort={json…}`; switch → `dtactiveOnly={json:true}`
|
|
191
|
-
+ page resets; Limpiar → URL clean. All complex state (arrays/dates/ranges/booleans/sort) round-trips through the URL.
|
|
192
|
-
|
|
193
|
-
| Item | typing | lint | rules | tests | visual | supervisor |
|
|
194
|
-
| --- | :--: | :--: | :--: | :--: | :--: | :--: |
|
|
195
|
-
| useDataTableQuery | ✅ | † | ✅ | ✅ (17/17: local + URL round-trip per filter kind) | ✅‡ (live: paginate 01-05→06-10, search resets page, enum filter, reset — all correct) | ⬜ |
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
# Component Doc Schema
|
|
2
|
-
|
|
3
|
-
Single source of truth for each component in `@mantis-core/ui`. One file per component at
|
|
4
|
-
`docs/components/<kebab-name>.md`. The file has **YAML frontmatter** (machine data) plus a short
|
|
5
|
-
prose body (`## Example`, optional `## Notes`).
|
|
6
|
-
|
|
7
|
-
`catalog.json`, `COMPONENTS.md`, `llms.txt`, `docs/contracts/dependency-map.md`, and
|
|
8
|
-
`docs/contracts/style-imports.md` are **generated** from these files by `pnpm gen:catalog` — never
|
|
9
|
-
edit them by hand. To document a component, edit its `.md` here and re-run the generator.
|
|
10
|
-
|
|
11
|
-
## File shape
|
|
12
|
-
|
|
13
|
-
```md
|
|
14
|
-
---
|
|
15
|
-
name: TextField
|
|
16
|
-
category: forms
|
|
17
|
-
import: '@mantis-core/ui/components/Forms/TextField'
|
|
18
|
-
whenToUse: Single-line text field wired to react-hook-form.
|
|
19
|
-
baseLibrary: '@primereact/ui/inputtext'
|
|
20
|
-
formContract: FieldControllerProps
|
|
21
|
-
requiredStyles: ['scss/inputs.scss']
|
|
22
|
-
relatedComponents: ['SelectField', 'FieldShell']
|
|
23
|
-
relatedNextCore: []
|
|
24
|
-
props:
|
|
25
|
-
- { name: placeholder, type: string, required: false, description: 'Native <input> passthrough beyond FieldControllerProps.' }
|
|
26
|
-
- { name: maxLength, type: number, required: false, description: 'Native <input> passthrough beyond FieldControllerProps.' }
|
|
27
|
-
---
|
|
28
|
-
|
|
29
|
-
## Example
|
|
30
|
-
|
|
31
|
-
```tsx
|
|
32
|
-
import { TextField } from '@mantis-core/ui/components/Forms/TextField';
|
|
33
|
-
import { useForm } from 'react-hook-form';
|
|
34
|
-
|
|
35
|
-
function Example() {
|
|
36
|
-
const { control } = useForm({ defaultValues: { title: '' } });
|
|
37
|
-
return <TextField name='title' control={control} label='Title' />;
|
|
38
|
-
}
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
## Notes
|
|
42
|
-
|
|
43
|
-
- Optional prose. Gotchas, variants, edge cases. Keep terse — LLM-optimized, no over-info.
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
## Frontmatter fields
|
|
47
|
-
|
|
48
|
-
| Field | Type | Required | Notes |
|
|
49
|
-
|---|---|---|---|
|
|
50
|
-
| `name` | string | ✅ | Component identifier, PascalCase. Matches the export. |
|
|
51
|
-
| `category` | string | ✅ | One of: `forms`, `tables`, `pages`, `maps`, `overlays`, `navigation`, `data`, `feedback`, `auth`, `buttons`, `charts`. Groups `catalog.json`. |
|
|
52
|
-
| `import` | string | ✅¹ | **Deep path**, verbatim into `catalog.json.import`. Form `@mantis-core/ui/components/<Area>/<Name>`. Exceptions exist (`.../components/table-toolbar`, `.../components/table-filter-field`) — store the real path, do not derive it. ¹Omit **only** for overview/guide docs (e.g. `admin-page-layout.md`): an `import`-less file ships but is excluded from `catalog.json` and `llms.txt`. |
|
|
53
|
-
| `whenToUse` | string | ✅ | One line. Drives `catalog.json.whenToUse` + the `llms.txt` index. |
|
|
54
|
-
| `baseLibrary` | string | ➖ | Underlying lib. One of: `@primereact/ui/<part>` (e.g. `@primereact/ui/select`, `@primereact/ui/inputtext`, `@primereact/ui/datatable`), `chart.js`, `leaflet`, `ckeditor`, or `custom` for components with no third-party base. Drives `dependency-map.md`. |
|
|
55
|
-
| `formContract` | string | ➖ | Shared contract the component follows. RHF-bound field wrappers use `FieldControllerProps` (see [`field-controller.md`](./field-controller.md)); non-text-box fields (toggles, groups, ranges) use `ChoiceFieldControllerProps`. Omit if the component isn't RHF-bound. |
|
|
56
|
-
| `requiredStyles` | string[] | ➖ | SCSS partials from `@mantis-core/styles`, e.g. `['scss/inputs.scss']`. Drives `style-imports.md`. Empty/omit when the component uses only utility classes or inline styles. |
|
|
57
|
-
| `relatedComponents` | string[] | ➖ | Sibling component `name`s. Cross-reference for LLMs. |
|
|
58
|
-
| `relatedNextCore` | string[] | ➖ | Hooks/utils from `@mantis-core/next-core` it pairs with, e.g. `['useList']`. Empty for stateless components. |
|
|
59
|
-
| `props` | object[] | ✅ | Each: `{ name, type, required, default?, description? }`. `name`/`type` strings, `required` boolean, `default`/`description` optional strings. Maps 1:1 to `catalog.json.props[]`. For a wrapper extending `FieldControllerProps`/`ChoiceFieldControllerProps`, list only the props it adds or overrides beyond the shared contract (link the contract via `formContract` instead of repeating `name`/`control`/`rules`/… on every wrapper). Empty array `[]` for components with no documented props (factories, etc.). |
|
|
60
|
-
|
|
61
|
-
## `formContract`: the flattened API (PR11)
|
|
62
|
-
|
|
63
|
-
Form wrappers no longer take a `controller={{ ... }}` object prop. They extend
|
|
64
|
-
`FieldControllerProps` (or `ChoiceFieldControllerProps` for non-text-box fields) directly as their
|
|
65
|
-
own props, alongside their component-specific props:
|
|
66
|
-
|
|
67
|
-
```tsx
|
|
68
|
-
// PR11 — flattened, no `controller` object
|
|
69
|
-
<TextField name='email' control={control} label='Email' rules={{ required: true }} />
|
|
70
|
-
|
|
71
|
-
// NOT this (PR10 shape, removed)
|
|
72
|
-
<TextInput controller={{ name: 'email', control }} label='Email' />
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
See [`field-controller.md`](./field-controller.md) for the full prop list, `FieldShell` label
|
|
76
|
-
modes, and the recipe for building a new wrapper.
|
|
77
|
-
|
|
78
|
-
## Body
|
|
79
|
-
|
|
80
|
-
- `## Example` — **enforced**: if the `## Example` heading is present it MUST contain exactly one fenced
|
|
81
|
-
```tsx block (closing fence on its own line) — the generator throws otherwise. The code inside is
|
|
82
|
-
extracted verbatim into `catalog.json.example`. Use the heading `## Example` (singular); `##
|
|
83
|
-
Examples` is not recognized. Avoid an inline ``` inside the snippet.
|
|
84
|
-
- **Self-contained and compilable**: the `## Example` snippet MUST include its own `import`
|
|
85
|
-
statements — importing the component from the exact deep path declared in frontmatter `import`,
|
|
86
|
-
plus any other modules it uses (`react-hook-form`, other Mantis Core components, etc.) — and MUST
|
|
87
|
-
define a `function Example() { ... }` component that renders the snippet's JSX. A CI check
|
|
88
|
-
compiles every doc's `## Example` block in isolation (no ambient imports, no shared scope across
|
|
89
|
-
docs), so a snippet missing an import or referencing an undefined variable fails the build. Only
|
|
90
|
-
this `## Example` fence is compiled by `check-doc-examples`; other ```tsx fences elsewhere in a
|
|
91
|
-
doc are illustrative and not type-checked. Keep it copy-pasteable and minimal — one realistic
|
|
92
|
-
usage, not a feature tour.
|
|
93
|
-
- `## Notes` — optional prose. Not consumed by the generator beyond shipping in the `.md`.
|
|
94
|
-
|
|
95
|
-
## Multi-package docs
|
|
96
|
-
|
|
97
|
-
The same frontmatter-driven approach extends beyond `@mantis-core/ui` to `@mantis-core/next-core`,
|
|
98
|
-
`@mantis-core/nuqs`, and `@mantis-core/styles`. Each package's `docs/*.md` files use the same
|
|
99
|
-
minimal frontmatter subset — `name`, `import`, `whenToUse`, `props` — with `category` taking
|
|
100
|
-
per-package values (e.g. `providers`, `theme`, `toasts` for `next-core`; `hooks`, `parsers` for
|
|
101
|
-
`nuqs`; `styles` for `styles`). A generator produces a per-package `llms.txt` from these files, the
|
|
102
|
-
same way `pnpm gen:catalog` does for `@mantis-core/ui`. This section is a pointer only — the
|
|
103
|
-
frontmatter contract and `## Example` rules above apply as-is; generator specifics live with that
|
|
104
|
-
package's own generator script, not here.
|
|
105
|
-
|
|
106
|
-
## Generated outputs (do not hand-edit)
|
|
107
|
-
|
|
108
|
-
| Output | Built from |
|
|
109
|
-
|---|---|
|
|
110
|
-
| `catalog.json` | All frontmatter, grouped by `category`. Shape: `{ name, import, whenToUse, props, example, category }`. |
|
|
111
|
-
| `COMPONENTS.md` | Index linking every `docs/components/*.md`. |
|
|
112
|
-
| `llms.txt` | LLM entry-point: `name · whenToUse · import · link`, grouped by category. |
|
|
113
|
-
| `docs/contracts/dependency-map.md` | `name → baseLibrary`, sectioned PrimeReact / non-PrimeReact / custom. |
|
|
114
|
-
| `docs/contracts/style-imports.md` | `name → requiredStyles`, plus the consolidated `@use` block. |
|
|
115
|
-
|
|
116
|
-
## Hand-maintained (not generated)
|
|
117
|
-
|
|
118
|
-
- `docs/contracts/field-controller.md` — shared RHF contract, not derivable from one component.
|
|
119
|
-
- `docs/README.md`, `README.md` — narrative docs.
|
|
120
|
-
- Each component's `## Notes` prose.
|