@assure-one/design-system 1.31.0 → 1.33.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 +61 -6
- package/codemods/0.2.0-radix-migration.mjs +315 -0
- package/codemods/README.md +365 -0
- package/codemods/lib/css-selectors.mjs +33 -0
- package/codemods/lib/css-values.mjs +223 -0
- package/codemods/lib/ds-stylesheet.mjs +168 -0
- package/codemods/lib/environment.mjs +72 -0
- package/codemods/lib/files.mjs +100 -0
- package/codemods/lib/forms.mjs +253 -0
- package/codemods/lib/jsx.mjs +0 -0
- package/codemods/lib/ledger.mjs +84 -0
- package/codemods/lib/registry.mjs +32 -0
- package/codemods/lib/report.mjs +119 -0
- package/codemods/lib/runner.mjs +164 -0
- package/codemods/run.mjs +161 -0
- package/codemods/transforms/cm-14-hidden-mirrors.mjs +275 -0
- package/codemods/transforms/cm-15-dom-selectors.mjs +573 -0
- package/codemods/transforms/cm-16-globals-css.mjs +487 -0
- package/codemods/transforms/cm-20-select-sentinels.mjs +442 -0
- package/dist/css/base.css +60 -0
- package/dist/css/components.css +7 -0
- package/dist/css/legacy-aliases.css +665 -0
- package/dist/css/shadcn.css +155 -0
- package/dist/css/tailwind.css +296 -0
- package/dist/css/tokens.css +630 -0
- package/dist/index.d.ts +807 -27
- package/dist/index.js +2038 -683
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/system-BDU18fVg.d.ts +559 -0
- package/dist/testing/index.cjs +458 -0
- package/dist/testing/index.d.cts +253 -0
- package/dist/testing/index.d.ts +253 -0
- package/dist/testing/index.js +452 -0
- package/dist/testing/setup.cjs +123 -0
- package/dist/testing/setup.js +121 -0
- package/dist/testing/style-stub.cjs +7 -0
- package/dist/testing/style-stub.js +5 -0
- package/dist/tokens/index.d.ts +50 -439
- package/dist/tokens/index.js +557 -50
- package/dist/tokens/index.js.map +1 -1
- package/package.json +74 -5
package/README.md
CHANGED
|
@@ -11,6 +11,8 @@ The single source of truth for visual design across all Assure products.
|
|
|
11
11
|
- [**`CHANGELOG.md`**](./CHANGELOG.md) — release-by-release log of what shipped.
|
|
12
12
|
- [**`CONTRIBUTING.md`**](./CONTRIBUTING.md) — dev loop, release flow, versioning, pitfalls.
|
|
13
13
|
- [**`CLAUDE.md`**](./CLAUDE.md) — invariants and conventions for AI assistants working in this repo.
|
|
14
|
+
- [**`docs/integration/css.md`**](./docs/integration/css.md) — how an application loads our CSS: layer order, import sequence, the compatibility preset, removing `@source`, runtime brand theming.
|
|
15
|
+
- [**`docs/testing.md`**](./docs/testing.md) — rendering the real package in your app's Jest tests (`@assure-one/design-system/testing`, experimental).
|
|
14
16
|
- [**`claude-skills/`**](./claude-skills) — drop-in Claude Code skill for consuming projects.
|
|
15
17
|
|
|
16
18
|
## Install
|
|
@@ -38,6 +40,49 @@ export default function Page() {
|
|
|
38
40
|
}
|
|
39
41
|
```
|
|
40
42
|
|
|
43
|
+
### The split CSS entries (experimental)
|
|
44
|
+
|
|
45
|
+
`styles.css` stays the supported way to consume the design system, unchanged.
|
|
46
|
+
Alongside it, the package now also publishes the individual stylesheets the new
|
|
47
|
+
CSS mode is built from. They are **experimental**: nothing requires them yet,
|
|
48
|
+
no component reads them, and importing them changes nothing on its own.
|
|
49
|
+
|
|
50
|
+
| Subpath | What it is |
|
|
51
|
+
| --- | --- |
|
|
52
|
+
| `./css/tokens.css` | The `--ds-*` design tokens: `:root`, the colour scheme scope and the brand scopes. Usable without any component. |
|
|
53
|
+
| `./css/legacy-aliases.css` | Read-aliases from today's token names to the namespaced ones (`--color-surface: var(--ds-color-surface)`), for as long as your own CSS reads design-system token names. |
|
|
54
|
+
| `./css/tailwind.css` | A Tailwind `@theme` bridge, so `bg-surface/40`, `hover:text-fg-3` and `rounded-control` compile in *your* build instead of silently producing nothing. |
|
|
55
|
+
| `./css/shadcn.css` | The app-vocabulary bridge: `background`, `foreground`, `primary`, `muted`, `destructive`, the radius scale and the rest, each reading one `--app-*` input you can override. |
|
|
56
|
+
| `./css/base.css` | Optional document defaults: body background, ink, font and `color-scheme`. Ships no preflight, no element rules and no font import. |
|
|
57
|
+
| `./css/components.css` | The component utilities compiled with Tailwind's `ds` prefix (`ds:flex`, `ds:bg-surface`), inside `@layer ds` and bound to the `--ds-*` tokens. **Unused until the class-vocabulary flip (W1-11):** no component emits a prefixed class yet, so importing it styles nothing. Published so the file, its size and its hygiene can be reviewed before the flip. |
|
|
58
|
+
|
|
59
|
+
Import order is `tokens.css` → `legacy-aliases.css` (optional) → `tailwind.css`
|
|
60
|
+
→ `shadcn.css` (optional) → `base.css` (optional) → `components.css`, after your own
|
|
61
|
+
`@import "tailwindcss"` and under the layer statement
|
|
62
|
+
`@layer theme, base, ds, components, utilities;`.
|
|
63
|
+
[**`docs/integration/css.md`**](./docs/integration/css.md) has the full
|
|
64
|
+
sequence, where the per-app preset goes, when the `@source` into our package may
|
|
65
|
+
be removed, and the fixture run behind each of those claims. The reasoning lives
|
|
66
|
+
in [ADR-004](./docs/adr/004-css-delivery-cascade.md) and
|
|
67
|
+
[ADR-005](./docs/adr/005-app-vocabulary-bridge.md).
|
|
68
|
+
|
|
69
|
+
### Runtime brand theming (experimental)
|
|
70
|
+
|
|
71
|
+
For a tenant colour known only at runtime, `@assure-one/design-system/tokens`
|
|
72
|
+
exports `createBrandTheme()`: a validated seed becomes the custom properties a
|
|
73
|
+
built-in brand scope declares, ready to spread into a `style` attribute.
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
import { createBrandTheme } from "@assure-one/design-system/tokens";
|
|
77
|
+
|
|
78
|
+
<div style={{ ...createBrandTheme({ brand: "#2258d8", accent: "#f59e0b" }) }}>…</div>;
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
It is pure and SSR-deterministic, clamps a fill too pale to read against the
|
|
82
|
+
canvas, and guarantees WCAG AA for every foreground it emits. The seed shape is
|
|
83
|
+
gated by decision D7 and may change while that decision is open — see
|
|
84
|
+
[`docs/integration/css.md`](./docs/integration/css.md#5-runtime-brand-theming).
|
|
85
|
+
|
|
41
86
|
## Develop
|
|
42
87
|
|
|
43
88
|
```bash
|
|
@@ -52,14 +97,24 @@ See [`CONTRIBUTING.md`](./CONTRIBUTING.md) for the full dev loop, including iter
|
|
|
52
97
|
|
|
53
98
|
## Architecture
|
|
54
99
|
|
|
55
|
-
3-tier token system
|
|
100
|
+
3-tier token system, authored as DTCG JSON in [`tokens/src`](./tokens/README.md)
|
|
101
|
+
and generated into `src/tokens/` and the `./css/*` entries (ADR-003):
|
|
56
102
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
103
|
+
| Tier | Example (namespaced name → value) | Reads |
|
|
104
|
+
| --- | --- | --- |
|
|
105
|
+
| reference | `--ds-ref-brand-pro-fg: #6c42f8` | a literal; the raw ramps, one per brand |
|
|
106
|
+
| system (semantic) | `--ds-color-action-brand-bg: var(--ds-ref-brand-pro-fg)` | the reference tier; **this is the public contract** |
|
|
107
|
+
| component | `--ds-menu-item-hover-bg: var(--ds-color-canvas-sunken)` | the system tier; only where a component must be themable on its own |
|
|
108
|
+
|
|
109
|
+
Components consume **system tokens**, never reference values directly. Themes
|
|
110
|
+
(colour scheme, per-product brand, a runtime brand from `createBrandTheme()`)
|
|
111
|
+
re-map system → reference without touching component code.
|
|
61
112
|
|
|
62
|
-
|
|
113
|
+
Every token has two published spellings: the namespaced `--ds-*` name above and
|
|
114
|
+
the legacy name it ships as today (`--color-brand`, `--color-pro-fg`,
|
|
115
|
+
`--color-menu-item-hover-bg`). `css/legacy-aliases.css` declares
|
|
116
|
+
`<legacy>: var(<--ds-* name>)` in the same scopes, so both resolve to the same
|
|
117
|
+
value — proved by `pnpm tokens:ds-check`.
|
|
63
118
|
|
|
64
119
|
## Releases
|
|
65
120
|
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @assure-one/design-system 0.1.x → 0.2.0 migration codemod
|
|
3
|
+
*
|
|
4
|
+
* Run from the consumer repo (firm or portal):
|
|
5
|
+
*
|
|
6
|
+
* npx jscodeshift -t node_modules/@assure-one/design-system/codemods/0.2.0-radix-migration.mjs \
|
|
7
|
+
* --extensions=tsx,ts,jsx,js \
|
|
8
|
+
* --parser=tsx \
|
|
9
|
+
* 'src/**\/*.{ts,tsx,js,jsx}'
|
|
10
|
+
*
|
|
11
|
+
* Or, if you've copied the file locally:
|
|
12
|
+
*
|
|
13
|
+
* npx jscodeshift -t ./codemods/0.2.0-radix-migration.mjs \
|
|
14
|
+
* --extensions=tsx,ts,jsx,js --parser=tsx 'src/**\/*.{ts,tsx}'
|
|
15
|
+
*
|
|
16
|
+
* VERBOSE per-file logs:
|
|
17
|
+
* VERBOSE=1 npx jscodeshift -t ... 'src/**\/*.tsx'
|
|
18
|
+
*
|
|
19
|
+
* What it does
|
|
20
|
+
* ------------
|
|
21
|
+
* 1. <Slider value={n} /> → <Slider value={[n]} />
|
|
22
|
+
* <Slider defaultValue={n} /> → <Slider defaultValue={[n]} />
|
|
23
|
+
* Adds a `// TODO 0.2.0: onValueChange now receives number[]` line above
|
|
24
|
+
* any <Slider> with `onValueChange` so the consumer can review the body.
|
|
25
|
+
*
|
|
26
|
+
* 2. <Avatar><Image src=... alt=... /></Avatar> (next/image inner)
|
|
27
|
+
* → <Avatar src=... alt=... />
|
|
28
|
+
* Drops the inner <Image>. If <Avatar> already has `src`, leaves it and
|
|
29
|
+
* emits a TODO. If the <Image> isn't `next/image`, leaves it untouched.
|
|
30
|
+
*
|
|
31
|
+
* 3. useToast — no-op. `useToast()` already returned `{ toast }`; 0.2.0
|
|
32
|
+
* adds `dismiss` (additive). Existing call sites keep working.
|
|
33
|
+
*
|
|
34
|
+
* 4. TeamMemberSelect — no-op. The `value: ""` (unassigned) contract is
|
|
35
|
+
* preserved on the consumer-facing API; the `__unassigned__` sentinel
|
|
36
|
+
* is internal only.
|
|
37
|
+
*
|
|
38
|
+
* Idempotent: running twice changes nothing the second time. Already-array
|
|
39
|
+
* Slider values and already-flattened Avatars are skipped.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
const SLIDER_NUMERIC_PROPS = ["value", "defaultValue"];
|
|
43
|
+
const TODO_SLIDER = " TODO 0.2.0: onValueChange now receives number[] (was number) ";
|
|
44
|
+
const TODO_SLIDER_IDENT =
|
|
45
|
+
" TODO 0.2.0: Slider value/defaultValue is now number[] — wrap in [] if this expression is still number ";
|
|
46
|
+
const TODO_AVATAR_CONFLICT =
|
|
47
|
+
" TODO 0.2.0: <Avatar> already has src — could not auto-merge with inner next/image child ";
|
|
48
|
+
const TODO_AVATAR_MERGED =
|
|
49
|
+
" TODO 0.2.0: review hoisted Avatar src/alt (was inner next/image child) ";
|
|
50
|
+
|
|
51
|
+
export default function transformer(file, api) {
|
|
52
|
+
const j = api.jscodeshift;
|
|
53
|
+
const root = j(file.source);
|
|
54
|
+
let changed = false;
|
|
55
|
+
const log = [];
|
|
56
|
+
|
|
57
|
+
/* -------------------------------------------------------------------- */
|
|
58
|
+
/* Helpers */
|
|
59
|
+
/* -------------------------------------------------------------------- */
|
|
60
|
+
|
|
61
|
+
function jsxOpeningName(opening) {
|
|
62
|
+
const name = opening.name;
|
|
63
|
+
if (!name) return null;
|
|
64
|
+
if (name.type === "JSXIdentifier") return name.name;
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function findAttr(opening, attrName) {
|
|
69
|
+
return opening.attributes?.find(
|
|
70
|
+
(a) =>
|
|
71
|
+
a.type === "JSXAttribute" &&
|
|
72
|
+
a.name?.type === "JSXIdentifier" &&
|
|
73
|
+
a.name.name === attrName,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function isAlreadyArrayExpression(attr) {
|
|
78
|
+
if (!attr || attr.type !== "JSXAttribute") return false;
|
|
79
|
+
const v = attr.value;
|
|
80
|
+
if (!v || v.type !== "JSXExpressionContainer") return false;
|
|
81
|
+
const expr = v.expression;
|
|
82
|
+
if (!expr) return false;
|
|
83
|
+
// already array literal
|
|
84
|
+
if (expr.type === "ArrayExpression") return true;
|
|
85
|
+
// identifier / member / call — assume the consumer is already passing an
|
|
86
|
+
// array; do not double-wrap. (Idempotency over a brittle type check.)
|
|
87
|
+
return (
|
|
88
|
+
expr.type === "Identifier" ||
|
|
89
|
+
expr.type === "MemberExpression" ||
|
|
90
|
+
expr.type === "CallExpression" ||
|
|
91
|
+
expr.type === "ConditionalExpression" ||
|
|
92
|
+
expr.type === "LogicalExpression"
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function isScalarNumericLiteralExpression(attr) {
|
|
97
|
+
if (!attr || attr.type !== "JSXAttribute") return false;
|
|
98
|
+
const v = attr.value;
|
|
99
|
+
// Bare `value=42` is not legal JSX numeric, so we only care about expr
|
|
100
|
+
// containers wrapping a NumericLiteral or unary minus numeric.
|
|
101
|
+
if (!v || v.type !== "JSXExpressionContainer") return false;
|
|
102
|
+
const e = v.expression;
|
|
103
|
+
if (!e) return false;
|
|
104
|
+
if (e.type === "NumericLiteral" || e.type === "Literal") {
|
|
105
|
+
return typeof e.value === "number";
|
|
106
|
+
}
|
|
107
|
+
if (
|
|
108
|
+
e.type === "UnaryExpression" &&
|
|
109
|
+
e.operator === "-" &&
|
|
110
|
+
e.argument &&
|
|
111
|
+
(e.argument.type === "NumericLiteral" ||
|
|
112
|
+
(e.argument.type === "Literal" && typeof e.argument.value === "number"))
|
|
113
|
+
) {
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function wrapAttrInArray(attr) {
|
|
120
|
+
const expr = attr.value.expression;
|
|
121
|
+
attr.value = j.jsxExpressionContainer(j.arrayExpression([expr]));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function hasLeadingComment(node, text) {
|
|
125
|
+
const list = node.leadingComments || [];
|
|
126
|
+
return list.some((c) => c.value === text);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function addLeadingComment(path, text) {
|
|
130
|
+
const node = path.node;
|
|
131
|
+
if (hasLeadingComment(node, text)) return false;
|
|
132
|
+
node.comments = node.comments || [];
|
|
133
|
+
const comment = { type: "CommentLine", value: text, leading: true, trailing: false };
|
|
134
|
+
node.comments.unshift(comment);
|
|
135
|
+
// jscodeshift / recast also reads `leadingComments`
|
|
136
|
+
node.leadingComments = node.leadingComments || [];
|
|
137
|
+
node.leadingComments.unshift(comment);
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* -------------------------------------------------------------------- */
|
|
142
|
+
/* Detect imported local names */
|
|
143
|
+
/* */
|
|
144
|
+
/* We only want to touch <Slider> and <Avatar> when they come from */
|
|
145
|
+
/* @assure-one/design-system (or are unambiguous globally). To stay */
|
|
146
|
+
/* safe across the firm + portal codebases, we scope by import source. */
|
|
147
|
+
/* -------------------------------------------------------------------- */
|
|
148
|
+
|
|
149
|
+
const dsLocalNames = new Set();
|
|
150
|
+
const nextImageLocalNames = new Set();
|
|
151
|
+
|
|
152
|
+
root.find(j.ImportDeclaration).forEach((path) => {
|
|
153
|
+
const src = path.node.source?.value;
|
|
154
|
+
if (!src) return;
|
|
155
|
+
if (src === "@assure-one/design-system") {
|
|
156
|
+
for (const spec of path.node.specifiers || []) {
|
|
157
|
+
if (spec.type === "ImportSpecifier" && spec.local?.name) {
|
|
158
|
+
dsLocalNames.add(spec.local.name);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (src === "next/image") {
|
|
163
|
+
for (const spec of path.node.specifiers || []) {
|
|
164
|
+
if (
|
|
165
|
+
(spec.type === "ImportDefaultSpecifier" ||
|
|
166
|
+
spec.type === "ImportSpecifier") &&
|
|
167
|
+
spec.local?.name
|
|
168
|
+
) {
|
|
169
|
+
nextImageLocalNames.add(spec.local.name);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// The DS exports `Slider` and `Avatar` under those exact names. If the
|
|
176
|
+
// consumer didn't import them from DS we leave their JSX alone — that
|
|
177
|
+
// avoids touching a local component that happens to share a name.
|
|
178
|
+
const sliderLocal = dsLocalNames.has("Slider") ? "Slider" : null;
|
|
179
|
+
const avatarLocal = dsLocalNames.has("Avatar") ? "Avatar" : null;
|
|
180
|
+
|
|
181
|
+
/* -------------------------------------------------------------------- */
|
|
182
|
+
/* Change 1 — Slider value/defaultValue scalar → array */
|
|
183
|
+
/* -------------------------------------------------------------------- */
|
|
184
|
+
|
|
185
|
+
if (sliderLocal) {
|
|
186
|
+
root
|
|
187
|
+
.find(j.JSXElement, {
|
|
188
|
+
openingElement: { name: { type: "JSXIdentifier", name: sliderLocal } },
|
|
189
|
+
})
|
|
190
|
+
.forEach((path) => {
|
|
191
|
+
const opening = path.node.openingElement;
|
|
192
|
+
|
|
193
|
+
let wrappedAny = false;
|
|
194
|
+
let needsIdentTodo = false;
|
|
195
|
+
for (const propName of SLIDER_NUMERIC_PROPS) {
|
|
196
|
+
const attr = findAttr(opening, propName);
|
|
197
|
+
if (!attr) continue;
|
|
198
|
+
if (isScalarNumericLiteralExpression(attr)) {
|
|
199
|
+
wrapAttrInArray(attr);
|
|
200
|
+
changed = true;
|
|
201
|
+
wrappedAny = true;
|
|
202
|
+
log.push(`Slider: ${propName}={n} → ${propName}={[n]}`);
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (isAlreadyArrayExpression(attr)) {
|
|
206
|
+
// Could be Identifier / MemberExpression / CallExpression / etc.
|
|
207
|
+
// We can't tell from AST whether it's number[] (already migrated)
|
|
208
|
+
// or number (needs migration). Leave it and emit a TODO so the
|
|
209
|
+
// consumer reviews. Idempotent on the array-literal case.
|
|
210
|
+
const v = attr.value?.expression;
|
|
211
|
+
if (v && v.type !== "ArrayExpression") {
|
|
212
|
+
needsIdentTodo = true;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (needsIdentTodo) {
|
|
217
|
+
if (addLeadingComment(path, TODO_SLIDER_IDENT)) {
|
|
218
|
+
changed = true;
|
|
219
|
+
log.push("Slider: added value/defaultValue identifier TODO comment");
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// TODO comment for onValueChange
|
|
224
|
+
const onValueChange = findAttr(opening, "onValueChange");
|
|
225
|
+
if (onValueChange) {
|
|
226
|
+
if (addLeadingComment(path, TODO_SLIDER)) {
|
|
227
|
+
changed = true;
|
|
228
|
+
log.push("Slider: added onValueChange TODO comment");
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/* -------------------------------------------------------------------- */
|
|
235
|
+
/* Change 2 — Avatar with next/image child → flat src/alt props */
|
|
236
|
+
/* -------------------------------------------------------------------- */
|
|
237
|
+
|
|
238
|
+
if (avatarLocal && nextImageLocalNames.size > 0) {
|
|
239
|
+
root
|
|
240
|
+
.find(j.JSXElement, {
|
|
241
|
+
openingElement: { name: { type: "JSXIdentifier", name: avatarLocal } },
|
|
242
|
+
})
|
|
243
|
+
.forEach((path) => {
|
|
244
|
+
const el = path.node;
|
|
245
|
+
const opening = el.openingElement;
|
|
246
|
+
const children = el.children || [];
|
|
247
|
+
|
|
248
|
+
// Find a single next/image child (ignoring whitespace text nodes)
|
|
249
|
+
const meaningful = children.filter(
|
|
250
|
+
(c) =>
|
|
251
|
+
!(
|
|
252
|
+
c.type === "JSXText" &&
|
|
253
|
+
(c.value === "" || /^\s*$/.test(c.value))
|
|
254
|
+
),
|
|
255
|
+
);
|
|
256
|
+
if (meaningful.length !== 1) return;
|
|
257
|
+
const child = meaningful[0];
|
|
258
|
+
if (child.type !== "JSXElement") return;
|
|
259
|
+
const childName = jsxOpeningName(child.openingElement);
|
|
260
|
+
if (!childName || !nextImageLocalNames.has(childName)) return;
|
|
261
|
+
|
|
262
|
+
// Don't merge if Avatar already has src — emit TODO and skip
|
|
263
|
+
const existingSrc = findAttr(opening, "src");
|
|
264
|
+
if (existingSrc) {
|
|
265
|
+
if (addLeadingComment(path, TODO_AVATAR_CONFLICT)) {
|
|
266
|
+
changed = true;
|
|
267
|
+
log.push("Avatar: src conflict — TODO comment added");
|
|
268
|
+
}
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Hoist src + alt from <Image>
|
|
273
|
+
const childOpening = child.openingElement;
|
|
274
|
+
const srcAttr = findAttr(childOpening, "src");
|
|
275
|
+
const altAttr = findAttr(childOpening, "alt");
|
|
276
|
+
if (!srcAttr) return; // nothing to hoist
|
|
277
|
+
|
|
278
|
+
const newAttrs = [...opening.attributes];
|
|
279
|
+
newAttrs.push(j.jsxAttribute(j.jsxIdentifier("src"), srcAttr.value));
|
|
280
|
+
if (altAttr && !findAttr(opening, "alt")) {
|
|
281
|
+
newAttrs.push(j.jsxAttribute(j.jsxIdentifier("alt"), altAttr.value));
|
|
282
|
+
}
|
|
283
|
+
opening.attributes = newAttrs;
|
|
284
|
+
|
|
285
|
+
// Self-close the Avatar (no remaining children)
|
|
286
|
+
opening.selfClosing = true;
|
|
287
|
+
el.closingElement = null;
|
|
288
|
+
el.children = [];
|
|
289
|
+
|
|
290
|
+
addLeadingComment(path, TODO_AVATAR_MERGED);
|
|
291
|
+
changed = true;
|
|
292
|
+
log.push("Avatar: hoisted next/image src/alt and removed inner element");
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/* -------------------------------------------------------------------- */
|
|
297
|
+
/* Done */
|
|
298
|
+
/* -------------------------------------------------------------------- */
|
|
299
|
+
|
|
300
|
+
if (!changed) return null;
|
|
301
|
+
|
|
302
|
+
if (process.env.VERBOSE) {
|
|
303
|
+
// eslint-disable-next-line no-console
|
|
304
|
+
console.log(`[0.2.0-radix-migration] ${file.path}`);
|
|
305
|
+
for (const line of log) {
|
|
306
|
+
// eslint-disable-next-line no-console
|
|
307
|
+
console.log(` - ${line}`);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return root.toSource({ quote: "double" });
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// jscodeshift parser hint
|
|
315
|
+
export const parser = "tsx";
|