@assure-one/design-system 1.35.0 → 1.37.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 +52 -0
- package/codemods/README.md +155 -12
- package/codemods/lib/registry.mjs +5 -0
- package/codemods/transforms/cm-07-input-size.mjs +104 -0
- package/codemods/transforms/cm-08-search-select-to-combobox.mjs +236 -0
- package/codemods/transforms/cm-09-side-drawer-to-sheet.mjs +348 -0
- package/codemods/transforms/cm-10-date-picker-value-change.mjs +416 -0
- package/codemods/transforms/cm-13-tooltip-delay.mjs +99 -0
- package/codemods/transforms/cm-14-hidden-mirrors.mjs +2 -2
- package/dist/css/components.css +1 -1
- package/dist/css/legacy-aliases.css +25 -1
- package/dist/css/shadcn.css +2 -2
- package/dist/css/tailwind.css +9 -0
- package/dist/css/tokens.css +64 -38
- package/dist/design-system-provider-C8UlFe52.d.ts +549 -0
- package/dist/icons/index.d.ts +2 -2
- package/dist/icons/index.js +6 -2
- package/dist/icons/index.js.map +1 -1
- package/dist/{index-B3OiMlQv.d.ts → index-vztxMSfl.d.ts} +7 -4
- package/dist/index.d.ts +2259 -345
- package/dist/index.js +7229 -3057
- package/dist/index.js.map +1 -1
- package/dist/next/index.d.ts +20 -0
- package/dist/next/index.js +16 -0
- package/dist/next/index.js.map +1 -0
- package/dist/styles.css +1 -1
- package/dist/{system-lyhOUTpa.d.ts → system-CyAVbHhM.d.ts} +17 -17
- package/dist/testing/index.cjs +133 -11
- package/dist/testing/index.d.cts +98 -2
- package/dist/testing/index.d.ts +98 -2
- package/dist/testing/index.js +132 -12
- package/dist/tokens/index.d.ts +1 -1
- package/dist/tokens/index.js +8 -8
- package/dist/tokens/index.js.map +1 -1
- package/docs/components.md +606 -0
- package/docs/components.registry.json +1604 -0
- package/docs/for-ai-agents.md +153 -0
- package/package.json +15 -3
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CM-09 — `SideDrawer` → `Sheet` (class R; plan §29 seq 11, registry
|
|
3
|
+
* `C-SIDEDRAWER`, lands with W5-08).
|
|
4
|
+
*
|
|
5
|
+
* W5-08 turned `SideDrawer` into a deprecated adapter over `Sheet`. A drawer
|
|
6
|
+
* with a `SideDrawer.Header` is a mechanical rewrite onto the Sheet parts;
|
|
7
|
+
* what has no Sheet equivalent is reported for a human.
|
|
8
|
+
*
|
|
9
|
+
* ## What it rewrites
|
|
10
|
+
*
|
|
11
|
+
* ```tsx
|
|
12
|
+
* <SideDrawer open={open} onClose={onClose} width="md" side="left">
|
|
13
|
+
* <SideDrawer.Header title={name} subtitle="…" onClose={onClose} />
|
|
14
|
+
* <SideDrawer.Body className="…">…</SideDrawer.Body>
|
|
15
|
+
* <SideDrawer.Footer>…</SideDrawer.Footer>
|
|
16
|
+
* </SideDrawer>
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* becomes
|
|
20
|
+
*
|
|
21
|
+
* ```tsx
|
|
22
|
+
* <Sheet open={open} onOpenChange={(open) => !open && onClose()}>
|
|
23
|
+
* <SheetContent side="left" size="lg">
|
|
24
|
+
* <SheetHeader>
|
|
25
|
+
* <SheetTitle>{name}</SheetTitle>
|
|
26
|
+
* <SheetDescription>…</SheetDescription>
|
|
27
|
+
* </SheetHeader>
|
|
28
|
+
* <SheetBody className="…">…</SheetBody>
|
|
29
|
+
* <SheetFooter>…</SheetFooter>
|
|
30
|
+
* </SheetContent>
|
|
31
|
+
* </Sheet>
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* - `onClose` → `onOpenChange`: a handler by reference is called, an arrow
|
|
35
|
+
* with an expression body is inlined, anything else is called through
|
|
36
|
+
* `(expr)()`
|
|
37
|
+
* - `width` → `size` on the content: `sm` → `sm` (420px), `md` → `lg`
|
|
38
|
+
* (640px); `lg` (50vw) and the omitted default have no step and become
|
|
39
|
+
* `xl` (760px), reported as `width-approximate`
|
|
40
|
+
* - `side` moves to the content
|
|
41
|
+
* - `SideDrawer.Header title subtitle` → `SheetHeader` with `SheetTitle` and
|
|
42
|
+
* `SheetDescription`; the header's `onClose` is dropped because the sheet's
|
|
43
|
+
* built-in close button (the last child of the content) closes through
|
|
44
|
+
* `onOpenChange`; a header without `onClose` gets `showCloseButton={false}`
|
|
45
|
+
* - `SideDrawer.Body` → `SheetBody`, `SideDrawer.Footer` → `SheetFooter`
|
|
46
|
+
* (`className` kept)
|
|
47
|
+
* - the named import follows when no `SideDrawer` element is left
|
|
48
|
+
*
|
|
49
|
+
* ## What it reports
|
|
50
|
+
*
|
|
51
|
+
* | rule | action | severity | why |
|
|
52
|
+
* | ------------------- | ------- | -------- | ---------------------------------------------------------------------------- |
|
|
53
|
+
* | `renamed` | applied | low | root, content, parts and import rewritten |
|
|
54
|
+
* | `width-approximate` | applied | medium | `lg` / default (50vw) → `xl` (760px); set `--ds-sheet-width` for an exact width |
|
|
55
|
+
* | `header-close` | applied | low | the header's close button is now the sheet's built-in one |
|
|
56
|
+
* | `rendering` | applied | low | Sheet parts pad differently (`p-6` header/footer, `px-6` body; no rules between them) |
|
|
57
|
+
* | `no-header` | review | high | a drawer without `SideDrawer.Header` has no title to become the `SheetTitle` |
|
|
58
|
+
* | `width-dynamic` | review | high | `width={expr}` cannot be mapped to a `size` step |
|
|
59
|
+
* | `spread-props` | review | medium | `{...props}` on the drawer or a part may carry any of the above |
|
|
60
|
+
*
|
|
61
|
+
* Elements already on `Sheet` are not touched (idempotency). Local components
|
|
62
|
+
* with the same name are not touched; test files are skipped.
|
|
63
|
+
*/
|
|
64
|
+
import { analyseForms, isWithin } from "../lib/forms.mjs";
|
|
65
|
+
import { applyEdits, openingOf } from "../lib/jsx-edit.mjs";
|
|
66
|
+
|
|
67
|
+
export const meta = {
|
|
68
|
+
id: "CM-09",
|
|
69
|
+
title: "SideDrawer → Sheet (drawers with a header rewritten; the rest reported)",
|
|
70
|
+
class: "R",
|
|
71
|
+
oneShot: false,
|
|
72
|
+
requires: { codemods: [], dsVersion: null },
|
|
73
|
+
parses: ["code"],
|
|
74
|
+
includeTests: false,
|
|
75
|
+
usesTypeScript: true,
|
|
76
|
+
usesPostcss: false,
|
|
77
|
+
registryIds: ["C-SIDEDRAWER"],
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const RULES = {
|
|
81
|
+
renamed: { action: "applied", severity: "low" },
|
|
82
|
+
"width-approximate": { action: "applied", severity: "medium" },
|
|
83
|
+
"header-close": { action: "applied", severity: "low" },
|
|
84
|
+
rendering: { action: "applied", severity: "low" },
|
|
85
|
+
"no-header": { action: "review", severity: "high" },
|
|
86
|
+
"width-dynamic": { action: "review", severity: "high" },
|
|
87
|
+
"spread-props": { action: "review", severity: "medium" },
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/** `width` preset → `size` step. `lg` (50vw) has no step; `xl` is the nearest. */
|
|
91
|
+
export const WIDTH_TO_SIZE = { sm: "sm", md: "lg", lg: "xl" };
|
|
92
|
+
|
|
93
|
+
const PART_TO_SHEET = {
|
|
94
|
+
"SideDrawer.Header": "SheetHeader",
|
|
95
|
+
"SideDrawer.Body": "SheetBody",
|
|
96
|
+
"SideDrawer.Footer": "SheetFooter",
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/** Children text for a title/subtitle attribute: a literal becomes text, an expression stays. */
|
|
100
|
+
function childrenOf(ts, sf, attr) {
|
|
101
|
+
const init = attr.initializer;
|
|
102
|
+
if (!init) return "";
|
|
103
|
+
if (ts.isStringLiteral(init)) {
|
|
104
|
+
return /[{}<>]/.test(init.text) ? `{${init.getText(sf)}}` : init.text;
|
|
105
|
+
}
|
|
106
|
+
return init.getText(sf);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** The `onOpenChange` handler that calls the drawer's `onClose` on close. */
|
|
110
|
+
function closeHandler(ts, sf, attr) {
|
|
111
|
+
const init = attr.initializer;
|
|
112
|
+
const inner = init && ts.isJsxExpression(init) ? init.expression : null;
|
|
113
|
+
if (!inner) return null;
|
|
114
|
+
if (ts.isIdentifier(inner) || ts.isPropertyAccessExpression(inner)) {
|
|
115
|
+
return `(open) => !open && ${inner.getText(sf)}()`;
|
|
116
|
+
}
|
|
117
|
+
if (ts.isArrowFunction(inner) && inner.parameters.length === 0 && !ts.isBlock(inner.body)) {
|
|
118
|
+
return `(open) => !open && ${inner.body.getText(sf)}`;
|
|
119
|
+
}
|
|
120
|
+
return `(open) => !open && (${inner.getText(sf)})()`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const attrsOf = (ts, sf, opening) => {
|
|
124
|
+
const map = new Map();
|
|
125
|
+
for (const attr of opening.attributes.properties) {
|
|
126
|
+
if (ts.isJsxAttribute(attr)) map.set(attr.name.getText(sf), attr);
|
|
127
|
+
}
|
|
128
|
+
return map;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const indentOf = (sf, node) => {
|
|
132
|
+
const text = sf.text;
|
|
133
|
+
const lineStart = text.lastIndexOf("\n", node.getStart(sf)) + 1;
|
|
134
|
+
return /^[ \t]*/.exec(text.slice(lineStart, node.getStart(sf)))[0];
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export function transform(file, { ts }) {
|
|
138
|
+
const facts = analyseForms(ts, file.source, file.rel);
|
|
139
|
+
const sf = facts.sf;
|
|
140
|
+
const findings = [];
|
|
141
|
+
const edits = [];
|
|
142
|
+
const needed = new Set();
|
|
143
|
+
let remaining = 0;
|
|
144
|
+
let renamedAny = false;
|
|
145
|
+
|
|
146
|
+
const drawers = facts.elements.filter(
|
|
147
|
+
(el) => el.isDs && el.base === "SideDrawer" && el.component === "SideDrawer",
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
for (const el of drawers) {
|
|
151
|
+
const base = {
|
|
152
|
+
line: el.line,
|
|
153
|
+
registryId: "C-SIDEDRAWER",
|
|
154
|
+
match: `<${el.tag}>`,
|
|
155
|
+
component: el.component,
|
|
156
|
+
gate: null,
|
|
157
|
+
detail: {},
|
|
158
|
+
};
|
|
159
|
+
const add = (rule, detail = {}) =>
|
|
160
|
+
findings.push({ ...base, rule, ...RULES[rule], detail: { ...base.detail, ...detail } });
|
|
161
|
+
|
|
162
|
+
const parts = facts.elements.filter(
|
|
163
|
+
(p) =>
|
|
164
|
+
p.isDs &&
|
|
165
|
+
p.base === "SideDrawer" &&
|
|
166
|
+
p.component !== "SideDrawer" &&
|
|
167
|
+
isWithin(facts.elements, p.index, el.index) &&
|
|
168
|
+
!drawers.some(
|
|
169
|
+
(other) =>
|
|
170
|
+
other !== el &&
|
|
171
|
+
isWithin(facts.elements, p.index, other.index) &&
|
|
172
|
+
isWithin(facts.elements, other.index, el.index),
|
|
173
|
+
),
|
|
174
|
+
);
|
|
175
|
+
const header = parts.find((p) => p.component === "SideDrawer.Header") ?? null;
|
|
176
|
+
const width = el.props.get("width");
|
|
177
|
+
|
|
178
|
+
let blocked = false;
|
|
179
|
+
if (el.spread || parts.some((p) => p.spread)) {
|
|
180
|
+
add("spread-props");
|
|
181
|
+
blocked = true;
|
|
182
|
+
}
|
|
183
|
+
if (width && width.expression) {
|
|
184
|
+
add("width-dynamic");
|
|
185
|
+
blocked = true;
|
|
186
|
+
}
|
|
187
|
+
if (!header || ts.isJsxElement(header.node)) {
|
|
188
|
+
add("no-header");
|
|
189
|
+
blocked = true;
|
|
190
|
+
}
|
|
191
|
+
if (blocked || !ts.isJsxElement(el.node)) {
|
|
192
|
+
remaining += 1;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ── The drawer's own opening tag → <Sheet open onOpenChange> ─────────
|
|
197
|
+
const opening = el.node.openingElement;
|
|
198
|
+
const attrs = attrsOf(ts, sf, opening);
|
|
199
|
+
const inner = [];
|
|
200
|
+
inner.push({ pos: opening.tagName.getStart(sf), end: opening.tagName.getEnd(), text: "Sheet" });
|
|
201
|
+
const closing = el.node.closingElement.tagName;
|
|
202
|
+
inner.push({ pos: closing.getStart(sf), end: closing.getEnd(), text: "Sheet" });
|
|
203
|
+
|
|
204
|
+
const onClose = attrs.get("onClose");
|
|
205
|
+
if (onClose) {
|
|
206
|
+
const handler = closeHandler(ts, sf, onClose);
|
|
207
|
+
if (handler) {
|
|
208
|
+
inner.push({
|
|
209
|
+
pos: onClose.getStart(sf),
|
|
210
|
+
end: onClose.getEnd(),
|
|
211
|
+
text: `onOpenChange={${handler}}`,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const removeAttr = (name) => {
|
|
216
|
+
const list = opening.attributes.properties;
|
|
217
|
+
const index = list.findIndex((a) => ts.isJsxAttribute(a) && a.name.getText(sf) === name);
|
|
218
|
+
if (index < 0) return;
|
|
219
|
+
const from = index === 0 ? opening.attributes.pos : list[index - 1].getEnd();
|
|
220
|
+
inner.push({ pos: from, end: list[index].getEnd(), text: "" });
|
|
221
|
+
};
|
|
222
|
+
removeAttr("width");
|
|
223
|
+
removeAttr("side");
|
|
224
|
+
|
|
225
|
+
// ── The content: side, size, showCloseButton ─────────────────────────
|
|
226
|
+
const preset = width ? width.literals[0] : "lg";
|
|
227
|
+
const size = WIDTH_TO_SIZE[preset] ?? "xl";
|
|
228
|
+
const contentAttrs = [];
|
|
229
|
+
const side = attrs.get("side");
|
|
230
|
+
contentAttrs.push(side ? `side=${side.initializer.getText(sf)}` : 'side="right"');
|
|
231
|
+
contentAttrs.push(`size="${size}"`);
|
|
232
|
+
const headerAttrs = attrsOf(ts, sf, header.node);
|
|
233
|
+
if (!headerAttrs.has("onClose")) contentAttrs.push("showCloseButton={false}");
|
|
234
|
+
needed.add("Sheet").add("SheetContent").add("SheetHeader").add("SheetTitle");
|
|
235
|
+
|
|
236
|
+
// ── SideDrawer.Header → SheetHeader with title and description ───────
|
|
237
|
+
const headerIndent = indentOf(sf, header.node);
|
|
238
|
+
const headerRows = [];
|
|
239
|
+
headerRows.push(`<SheetTitle>${childrenOf(ts, sf, headerAttrs.get("title"))}</SheetTitle>`);
|
|
240
|
+
const subtitle = headerAttrs.get("subtitle");
|
|
241
|
+
if (subtitle) {
|
|
242
|
+
needed.add("SheetDescription");
|
|
243
|
+
const value = childrenOf(ts, sf, subtitle);
|
|
244
|
+
headerRows.push(
|
|
245
|
+
subtitle.initializer && ts.isJsxExpression(subtitle.initializer)
|
|
246
|
+
? `{${subtitle.initializer.expression.getText(sf)} ? <SheetDescription>${value}</SheetDescription> : null}`
|
|
247
|
+
: `<SheetDescription>${value}</SheetDescription>`,
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
const headerClass = headerAttrs.get("className");
|
|
251
|
+
const headerOpen = `<SheetHeader${headerClass ? ` ${headerClass.getText(sf)}` : ""}>`;
|
|
252
|
+
inner.push({
|
|
253
|
+
pos: header.node.getStart(sf),
|
|
254
|
+
end: header.node.getEnd(),
|
|
255
|
+
text: [
|
|
256
|
+
headerOpen,
|
|
257
|
+
...headerRows.map((row) => `${headerIndent} ${row}`),
|
|
258
|
+
`${headerIndent}</SheetHeader>`,
|
|
259
|
+
].join("\n"),
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// ── Body / Footer → SheetBody / SheetFooter ──────────────────────────
|
|
263
|
+
for (const part of parts) {
|
|
264
|
+
if (part === header) continue;
|
|
265
|
+
const to = PART_TO_SHEET[part.component];
|
|
266
|
+
if (!to) continue;
|
|
267
|
+
needed.add(to);
|
|
268
|
+
const partOpening = openingOf(ts, part.node);
|
|
269
|
+
inner.push({
|
|
270
|
+
pos: partOpening.tagName.getStart(sf),
|
|
271
|
+
end: partOpening.tagName.getEnd(),
|
|
272
|
+
text: to,
|
|
273
|
+
});
|
|
274
|
+
if (ts.isJsxElement(part.node)) {
|
|
275
|
+
const tag = part.node.closingElement.tagName;
|
|
276
|
+
inner.push({ pos: tag.getStart(sf), end: tag.getEnd(), text: to });
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// ── Assemble: apply the inner edits to the element's own text, indent
|
|
281
|
+
// the children one level and wrap them in <SheetContent>. ──────────────
|
|
282
|
+
const start = el.node.getStart(sf);
|
|
283
|
+
const end = el.node.getEnd();
|
|
284
|
+
const local = inner.map((e) => ({ ...e, pos: e.pos - start, end: e.end - start }));
|
|
285
|
+
const text = applyEdits(file.source.slice(start, end), local);
|
|
286
|
+
// Where the (edited) opening tag ends: its original length plus what the
|
|
287
|
+
// edits inside it added or removed.
|
|
288
|
+
const openingEnd = opening.getEnd() - start;
|
|
289
|
+
const openEnd = local
|
|
290
|
+
.filter((e) => e.end <= openingEnd)
|
|
291
|
+
.reduce((at, e) => at + e.text.length - (e.end - e.pos), openingEnd);
|
|
292
|
+
const closeStart = text.lastIndexOf("</Sheet>");
|
|
293
|
+
const indent = indentOf(sf, el.node);
|
|
294
|
+
const children = text.slice(openEnd, closeStart).replace(/\n(?=[^\n])/g, "\n ");
|
|
295
|
+
const trimmed = children.replace(/\s+$/, "");
|
|
296
|
+
edits.push({
|
|
297
|
+
pos: start,
|
|
298
|
+
end,
|
|
299
|
+
text: `${text.slice(0, openEnd)}\n${indent} <SheetContent ${contentAttrs.join(" ")}>${trimmed}\n${indent} </SheetContent>\n${indent}${text.slice(closeStart)}`,
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
renamedAny = true;
|
|
303
|
+
add("renamed");
|
|
304
|
+
if (WIDTH_TO_SIZE[preset] === "xl" || !WIDTH_TO_SIZE[preset]) {
|
|
305
|
+
add("width-approximate", { width: preset, size });
|
|
306
|
+
}
|
|
307
|
+
if (headerAttrs.has("onClose")) {
|
|
308
|
+
add("header-close", {
|
|
309
|
+
header: headerAttrs.get("onClose").initializer?.getText(sf) ?? null,
|
|
310
|
+
drawer: onClose?.initializer?.getText(sf) ?? null,
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
add("rendering");
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// The import: `SideDrawer` → the Sheet parts the file now uses, when no
|
|
317
|
+
// element is left on the old name.
|
|
318
|
+
if (renamedAny && remaining === 0) {
|
|
319
|
+
for (const stmt of sf.statements) {
|
|
320
|
+
if (!ts.isImportDeclaration(stmt) || !ts.isStringLiteral(stmt.moduleSpecifier)) continue;
|
|
321
|
+
if (!stmt.moduleSpecifier.text.startsWith("@assure-one/design-system")) continue;
|
|
322
|
+
const bindings = stmt.importClause?.namedBindings;
|
|
323
|
+
if (!bindings || !ts.isNamedImports(bindings)) continue;
|
|
324
|
+
const present = new Set(bindings.elements.map((e) => (e.propertyName ?? e.name).text));
|
|
325
|
+
for (const element of bindings.elements) {
|
|
326
|
+
const imported = (element.propertyName ?? element.name).text;
|
|
327
|
+
if (imported !== "SideDrawer" || element.propertyName) continue;
|
|
328
|
+
const names = [...needed].filter((n) => !present.has(n)).sort();
|
|
329
|
+
if (names.length) {
|
|
330
|
+
edits.push({ pos: element.getStart(sf), end: element.getEnd(), text: names.join(", ") });
|
|
331
|
+
} else {
|
|
332
|
+
const list = bindings.elements;
|
|
333
|
+
const index = list.indexOf(element);
|
|
334
|
+
const from = index === 0 ? element.getStart(sf) : list[index - 1].getEnd();
|
|
335
|
+
const to = index === 0 && list.length > 1 ? list[1].getStart(sf) : element.getEnd();
|
|
336
|
+
edits.push({ pos: from, end: to, text: "" });
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return {
|
|
343
|
+
output: edits.length ? applyEdits(file.source, edits) : file.source,
|
|
344
|
+
findings,
|
|
345
|
+
notTransformed: [],
|
|
346
|
+
parseErrors: facts.parseErrors,
|
|
347
|
+
};
|
|
348
|
+
}
|