@assure-one/design-system 1.36.0 → 1.38.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 +45 -16
- package/codemods/README.md +93 -0
- package/codemods/lib/registry.mjs +3 -0
- package/codemods/transforms/cm-09-side-drawer-to-sheet.mjs +348 -0
- package/codemods/transforms/cm-13-tooltip-delay.mjs +99 -0
- package/codemods/transforms/cm-18-data-table-parts.mjs +234 -0
- package/dist/css/components.css +1 -1
- package/dist/css/legacy-aliases.css +33 -1
- package/dist/css/shadcn.css +2 -2
- package/dist/css/tailwind.css +9 -0
- package/dist/css/tokens.css +72 -38
- package/dist/design-system-provider-B5MbvbRc.d.ts +609 -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-CQwzTm0v.d.ts → index-vztxMSfl.d.ts} +7 -4
- package/dist/index.d.ts +1757 -303
- package/dist/index.js +4125 -1956
- package/dist/index.js.map +1 -1
- package/dist/next/index.d.ts +1 -1
- package/dist/styles.css +1 -1
- package/dist/{system-BDU18fVg.d.ts → system-CyAVbHhM.d.ts} +16 -16
- 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 +47 -14
- package/docs/components.registry.json +2415 -461
- package/docs/for-ai-agents.md +2 -0
- package/eslint-config/index.mjs +210 -0
- package/eslint-config/stylelint-rules.mjs +54 -0
- package/eslint-config/stylelint.mjs +73 -0
- package/package.json +8 -1
- package/dist/design-system-provider-cPUklDJv.d.ts +0 -220
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CM-13 — `Tooltip delayMs` → `delayDuration` (class A; plan §29 seq 2,
|
|
3
|
+
* registry `C-TOOLTIP-DELAY`).
|
|
4
|
+
*
|
|
5
|
+
* W5-12 made `delayMs` a deprecated alias of Radix's `delayDuration` on the
|
|
6
|
+
* design-system `Tooltip` (`TooltipProps` in `src/primitives/tooltip.tsx`):
|
|
7
|
+
* both name the same milliseconds and `delayDuration` wins when both are
|
|
8
|
+
* given, so the rename is behaviour-neutral and a pure rename — only the
|
|
9
|
+
* attribute's name is replaced; a literal or a dynamic `delayMs={x}` keeps
|
|
10
|
+
* its value and its formatting.
|
|
11
|
+
*
|
|
12
|
+
* | rule | registry | what happens |
|
|
13
|
+
* | --------------- | --------------- | ----------------------------------- |
|
|
14
|
+
* | `delay-renamed` | C-TOOLTIP-DELAY | `delayMs={…}` → `delayDuration={…}` |
|
|
15
|
+
*
|
|
16
|
+
* ## What it leaves alone, and lists under "could not be transformed"
|
|
17
|
+
*
|
|
18
|
+
* - An element that passes **both** `delayDuration` and `delayMs`:
|
|
19
|
+
* `delayDuration` already wins in the component, and a rename would make a
|
|
20
|
+
* duplicate attribute. A human deletes `delayMs`.
|
|
21
|
+
* - An element with a spread (`<Tooltip delayMs={300} {...props}>`): the
|
|
22
|
+
* spread may carry `delayDuration`, and which one wins depends on attribute
|
|
23
|
+
* order — renaming the literal could change it.
|
|
24
|
+
*
|
|
25
|
+
* Elements already on `delayDuration` are not touched (idempotency). Local
|
|
26
|
+
* components called Tooltip are not touched; test files are skipped, as by
|
|
27
|
+
* the scanner that measures C-TOOLTIP-DELAY.
|
|
28
|
+
*/
|
|
29
|
+
import { analyseForms } from "../lib/forms.mjs";
|
|
30
|
+
import { applyEdits, openingOf, renameAttribute } from "../lib/jsx-edit.mjs";
|
|
31
|
+
|
|
32
|
+
export const meta = {
|
|
33
|
+
id: "CM-13",
|
|
34
|
+
title: "Tooltip: delayMs → delayDuration",
|
|
35
|
+
class: "A",
|
|
36
|
+
oneShot: false,
|
|
37
|
+
requires: { codemods: [], dsVersion: null },
|
|
38
|
+
parses: ["code"],
|
|
39
|
+
includeTests: false,
|
|
40
|
+
usesTypeScript: true,
|
|
41
|
+
usesPostcss: false,
|
|
42
|
+
registryIds: ["C-TOOLTIP-DELAY"],
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** The design-system component that accepts the deprecated `delayMs` (W5-12). */
|
|
46
|
+
export const DELAYED_COMPONENTS = new Set(["Tooltip"]);
|
|
47
|
+
|
|
48
|
+
export const LEGACY_PROP = "delayMs";
|
|
49
|
+
export const PROP = "delayDuration";
|
|
50
|
+
|
|
51
|
+
export function transform(file, { ts }) {
|
|
52
|
+
const facts = analyseForms(ts, file.source, file.rel);
|
|
53
|
+
const findings = [];
|
|
54
|
+
const notTransformed = [];
|
|
55
|
+
const edits = [];
|
|
56
|
+
|
|
57
|
+
for (const el of facts.elements) {
|
|
58
|
+
if (!el.isDs || !DELAYED_COMPONENTS.has(el.base) || el.component !== el.base) continue;
|
|
59
|
+
if (!el.props.has(LEGACY_PROP)) continue;
|
|
60
|
+
|
|
61
|
+
if (el.props.has(PROP)) {
|
|
62
|
+
notTransformed.push({
|
|
63
|
+
line: el.line,
|
|
64
|
+
reason: "both-names",
|
|
65
|
+
detail: `<${el.tag}> passes \`${LEGACY_PROP}\` and \`${PROP}\` — \`${PROP}\` already wins; delete \`${LEGACY_PROP}\` by hand`,
|
|
66
|
+
});
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (el.spread) {
|
|
70
|
+
notTransformed.push({
|
|
71
|
+
line: el.line,
|
|
72
|
+
reason: "spread-props",
|
|
73
|
+
detail: `<${el.tag} {…}> — the spread may carry \`${PROP}\`; rename by hand once you know it does not`,
|
|
74
|
+
});
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const edit = renameAttribute(ts, facts.sf, openingOf(ts, el.node), LEGACY_PROP, PROP);
|
|
79
|
+
if (!edit) continue;
|
|
80
|
+
edits.push(edit);
|
|
81
|
+
findings.push({
|
|
82
|
+
line: el.props.get(LEGACY_PROP).line,
|
|
83
|
+
registryId: "C-TOOLTIP-DELAY",
|
|
84
|
+
rule: "delay-renamed",
|
|
85
|
+
match: `<${el.tag} ${LEGACY_PROP}>`,
|
|
86
|
+
component: el.component,
|
|
87
|
+
action: "applied",
|
|
88
|
+
gate: null,
|
|
89
|
+
detail: { from: LEGACY_PROP, to: PROP, value: el.props.get(LEGACY_PROP).text },
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
output: edits.length ? applyEdits(file.source, edits) : file.source,
|
|
95
|
+
findings,
|
|
96
|
+
notTransformed,
|
|
97
|
+
parseErrors: facts.parseErrors,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CM-18 — `DataTable*` presentational parts → `Table*` (class A; plan §29
|
|
3
|
+
* seq 12, registry `C-DT-PARTS`, lands with W6-03).
|
|
4
|
+
*
|
|
5
|
+
* W6-03 made the `DataTable*` table parts thin aliases of the `Table*` parts:
|
|
6
|
+
* one styling source, the same props. Where a part is a 1:1 copy the rename
|
|
7
|
+
* is mechanical and this codemod applies it — tag, closing tag and the named
|
|
8
|
+
* import:
|
|
9
|
+
*
|
|
10
|
+
* | from | to | note |
|
|
11
|
+
* | ----------------- | ------------- | ----------------------------------------------------------- |
|
|
12
|
+
* | `DataTableHead` | `TableHeader` | `<thead>` |
|
|
13
|
+
* | `DataTableBody` | `TableBody` | `<tbody>` |
|
|
14
|
+
* | `DataTableRow` | `TableRow` | `selected` is the same prop |
|
|
15
|
+
* | `DataTableCell` | `TableCell` | same props (`align`, `numeric`, `muted`, `truncate`, `width`)|
|
|
16
|
+
* | `DataTableHeader` | `TableHead` | plain, or controlled `sortable sort onSortChange` — `sortable` is dropped because `TableHead` is sortable whenever `sort` is passed |
|
|
17
|
+
*
|
|
18
|
+
* What changes for the rendered DOM: the `data-slot` attributes read
|
|
19
|
+
* `table-*` instead of `data-table-*`, and `TableHead` does not stamp
|
|
20
|
+
* `scope="col"` (a `<th>` inside `<thead>` is a column header by default).
|
|
21
|
+
* Classes, geometry and colours are the same — the `DataTable*` parts render
|
|
22
|
+
* through the `Table*` parts since W6-03.
|
|
23
|
+
*
|
|
24
|
+
* ## What it leaves alone, and lists under "could not be transformed"
|
|
25
|
+
*
|
|
26
|
+
* - `DataTableHeader sortable` **without** a controlled `sort` (uncontrolled
|
|
27
|
+
* sorting, `defaultSort`): `TableHead` has no uncontrolled sort state — the
|
|
28
|
+
* consumer wires `sort` from `useDataTable` or its own state.
|
|
29
|
+
* - `DataTableHeader sortable={expr}`: whether the header is sortable is not
|
|
30
|
+
* visible here.
|
|
31
|
+
* - `DataTableHeader {...props}`: the spread may carry `sortable`.
|
|
32
|
+
*
|
|
33
|
+
* Not touched at all (not parallel copies of a `Table*` part, they stay):
|
|
34
|
+
* `DataTable` (the card frame), `DataTableToolbar`, `DataTableSearch`,
|
|
35
|
+
* `DataTableSpacer`, `DataTableResultsCount`, `DataTableCellName` /
|
|
36
|
+
* `DataTableCellMono` / `DataTableCellId` / `DataTableCellDue`,
|
|
37
|
+
* `DataTableCheckbox`, `DataTablePagination`, and the `DataTable*Props` types.
|
|
38
|
+
* An aliased import (`DataTableCell as Cell`) and a namespace import are left
|
|
39
|
+
* alone too. The import specifier is rewritten only when no reference to the
|
|
40
|
+
* old name remains in the file (a `typeof DataTableCell` keeps it, and the
|
|
41
|
+
* new name is added next to it). Local components with the same names are
|
|
42
|
+
* not touched; test files are skipped, as by the scanner that measures
|
|
43
|
+
* C-DT-PARTS.
|
|
44
|
+
*/
|
|
45
|
+
import { analyseForms } from "../lib/forms.mjs";
|
|
46
|
+
import { applyEdits, openingOf, removeAttribute } from "../lib/jsx-edit.mjs";
|
|
47
|
+
|
|
48
|
+
export const meta = {
|
|
49
|
+
id: "CM-18",
|
|
50
|
+
title: "DataTable* presentational parts → Table* (pure rename where the part is a 1:1 copy)",
|
|
51
|
+
class: "A",
|
|
52
|
+
oneShot: false,
|
|
53
|
+
requires: { codemods: [], dsVersion: null },
|
|
54
|
+
parses: ["code"],
|
|
55
|
+
includeTests: false,
|
|
56
|
+
usesTypeScript: true,
|
|
57
|
+
usesPostcss: false,
|
|
58
|
+
registryIds: ["C-DT-PARTS"],
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/** `DataTable*` part → the `Table*` part it aliases (src/composites/data-table.tsx, W6-03). */
|
|
62
|
+
export const PART_MAP = {
|
|
63
|
+
DataTableHead: "TableHeader",
|
|
64
|
+
DataTableBody: "TableBody",
|
|
65
|
+
DataTableRow: "TableRow",
|
|
66
|
+
DataTableCell: "TableCell",
|
|
67
|
+
DataTableHeader: "TableHead",
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const RULES = {
|
|
71
|
+
"part-renamed": { action: "applied", severity: "low" },
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const DS_PACKAGE = "@assure-one/design-system";
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Identifier references to `name` outside its import specifier and outside
|
|
78
|
+
* JSX tag names — `typeof DataTableCell`, `as={DataTableCell}`, a plain
|
|
79
|
+
* value use. While one exists the old import must stay.
|
|
80
|
+
*/
|
|
81
|
+
function hasOtherReferences(ts, sf, name) {
|
|
82
|
+
let found = false;
|
|
83
|
+
const visit = (node) => {
|
|
84
|
+
if (found) return;
|
|
85
|
+
if (ts.isIdentifier(node) && node.text === name) {
|
|
86
|
+
const parent = node.parent;
|
|
87
|
+
const isImport = parent && ts.isImportSpecifier(parent);
|
|
88
|
+
const isTag =
|
|
89
|
+
parent &&
|
|
90
|
+
(ts.isJsxOpeningElement(parent) ||
|
|
91
|
+
ts.isJsxSelfClosingElement(parent) ||
|
|
92
|
+
ts.isJsxClosingElement(parent)) &&
|
|
93
|
+
parent.tagName === node;
|
|
94
|
+
if (!isImport && !isTag) found = true;
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
ts.forEachChild(node, visit);
|
|
98
|
+
};
|
|
99
|
+
visit(sf);
|
|
100
|
+
return found;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function transform(file, { ts }) {
|
|
104
|
+
const facts = analyseForms(ts, file.source, file.rel);
|
|
105
|
+
const sf = facts.sf;
|
|
106
|
+
const findings = [];
|
|
107
|
+
const notTransformed = [];
|
|
108
|
+
const edits = [];
|
|
109
|
+
/** old local name → new name, for the elements that were renamed */
|
|
110
|
+
const renamed = new Map();
|
|
111
|
+
/** old local names that still have an element on them */
|
|
112
|
+
const remaining = new Set();
|
|
113
|
+
|
|
114
|
+
for (const el of facts.elements) {
|
|
115
|
+
if (!el.isDs || !PART_MAP[el.base] || el.component !== el.base) continue;
|
|
116
|
+
// An aliased import (`DataTableCell as Cell`): the local name is not the part's.
|
|
117
|
+
if (el.tag !== el.base) {
|
|
118
|
+
remaining.add(el.tag);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
const to = PART_MAP[el.base];
|
|
122
|
+
const opening = openingOf(ts, el.node);
|
|
123
|
+
const inner = [];
|
|
124
|
+
let sortableDropped = false;
|
|
125
|
+
|
|
126
|
+
if (el.base === "DataTableHeader") {
|
|
127
|
+
const sortable = el.props.get("sortable");
|
|
128
|
+
if (el.spread) {
|
|
129
|
+
notTransformed.push({
|
|
130
|
+
line: el.line,
|
|
131
|
+
reason: "spread-props",
|
|
132
|
+
detail: `<${el.tag} {…}> — the spread may carry \`sortable\`; rename to \`TableHead\` by hand once you know it does not`,
|
|
133
|
+
});
|
|
134
|
+
remaining.add(el.tag);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (sortable) {
|
|
138
|
+
if (sortable.text !== "true") {
|
|
139
|
+
notTransformed.push({
|
|
140
|
+
line: el.line,
|
|
141
|
+
reason: "dynamic-sortable",
|
|
142
|
+
detail: `<${el.tag} sortable={…}> — whether the header sorts is not visible here; \`TableHead\` sorts whenever \`sort\` is passed`,
|
|
143
|
+
});
|
|
144
|
+
remaining.add(el.tag);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (!el.props.has("sort") || el.props.has("defaultSort")) {
|
|
148
|
+
notTransformed.push({
|
|
149
|
+
line: el.line,
|
|
150
|
+
reason: "uncontrolled-sort",
|
|
151
|
+
detail: `<${el.tag} sortable> without a controlled \`sort\` — \`TableHead\` has no uncontrolled sort state; wire \`sort\` + \`onSortChange\` (from \`useDataTable\`) first`,
|
|
152
|
+
});
|
|
153
|
+
remaining.add(el.tag);
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
const drop = removeAttribute(ts, sf, opening, "sortable");
|
|
157
|
+
if (drop) {
|
|
158
|
+
inner.push(drop);
|
|
159
|
+
sortableDropped = true;
|
|
160
|
+
}
|
|
161
|
+
} else if (el.props.has("defaultSort")) {
|
|
162
|
+
notTransformed.push({
|
|
163
|
+
line: el.line,
|
|
164
|
+
reason: "uncontrolled-sort",
|
|
165
|
+
detail: `<${el.tag} defaultSort> — \`TableHead\` has no uncontrolled sort state`,
|
|
166
|
+
});
|
|
167
|
+
remaining.add(el.tag);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
inner.push({ pos: opening.tagName.getStart(sf), end: opening.tagName.getEnd(), text: to });
|
|
173
|
+
if (ts.isJsxElement(el.node)) {
|
|
174
|
+
const closing = el.node.closingElement.tagName;
|
|
175
|
+
inner.push({ pos: closing.getStart(sf), end: closing.getEnd(), text: to });
|
|
176
|
+
}
|
|
177
|
+
edits.push(...inner);
|
|
178
|
+
renamed.set(el.base, to);
|
|
179
|
+
findings.push({
|
|
180
|
+
line: el.line,
|
|
181
|
+
registryId: "C-DT-PARTS",
|
|
182
|
+
rule: "part-renamed",
|
|
183
|
+
match: `<${el.tag}>`,
|
|
184
|
+
component: el.component,
|
|
185
|
+
action: "applied",
|
|
186
|
+
gate: null,
|
|
187
|
+
severity: RULES["part-renamed"].severity,
|
|
188
|
+
detail: { from: el.base, to, sortableDropped },
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// The import: each renamed name becomes its `Table*` part when nothing else
|
|
193
|
+
// in the file still refers to it; otherwise the new name is added next to it.
|
|
194
|
+
if (renamed.size) {
|
|
195
|
+
for (const stmt of sf.statements) {
|
|
196
|
+
if (!ts.isImportDeclaration(stmt) || !ts.isStringLiteral(stmt.moduleSpecifier)) continue;
|
|
197
|
+
const spec = stmt.moduleSpecifier.text;
|
|
198
|
+
if (spec !== DS_PACKAGE && !spec.startsWith(`${DS_PACKAGE}/`)) continue;
|
|
199
|
+
const bindings = stmt.importClause?.namedBindings;
|
|
200
|
+
if (!bindings || !ts.isNamedImports(bindings)) continue;
|
|
201
|
+
const present = new Set(bindings.elements.map((e) => e.name.text));
|
|
202
|
+
const list = bindings.elements;
|
|
203
|
+
for (const [from, to] of renamed) {
|
|
204
|
+
const index = list.findIndex((e) => !e.propertyName && e.name.text === from);
|
|
205
|
+
if (index < 0) continue;
|
|
206
|
+
const element = list[index];
|
|
207
|
+
const keep = remaining.has(from) || hasOtherReferences(ts, sf, from);
|
|
208
|
+
if (keep) {
|
|
209
|
+
if (!present.has(to)) {
|
|
210
|
+
edits.push({ pos: element.getEnd(), text: `, ${to}` });
|
|
211
|
+
present.add(to);
|
|
212
|
+
}
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
if (present.has(to)) {
|
|
216
|
+
// `TableCell` is already imported: the old specifier goes.
|
|
217
|
+
const fromPos = index === 0 ? element.getStart(sf) : list[index - 1].getEnd();
|
|
218
|
+
const toPos = index === 0 && list.length > 1 ? list[1].getStart(sf) : element.getEnd();
|
|
219
|
+
edits.push({ pos: fromPos, end: toPos, text: "" });
|
|
220
|
+
} else {
|
|
221
|
+
edits.push({ pos: element.getStart(sf), end: element.getEnd(), text: to });
|
|
222
|
+
present.add(to);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
output: edits.length ? applyEdits(file.source, edits) : file.source,
|
|
230
|
+
findings,
|
|
231
|
+
notTransformed,
|
|
232
|
+
parseErrors: facts.parseErrors,
|
|
233
|
+
};
|
|
234
|
+
}
|