synthesisui 0.16.80 → 0.16.81

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.
@@ -0,0 +1,235 @@
1
+ /**
2
+ * IS THIS A COMPONENT OF A DESIGN SYSTEM?
3
+ *
4
+ * Nothing asked. The definition reader matched `export const X` / `export function X`
5
+ * for any capitalised name, and pointed at a whole monorepo it found **704
6
+ * components in a repo that has 37** (dono, 01/08). The other 667 were:
7
+ *
8
+ * ds-activation-page a route
9
+ * ds-auth-layout a Next layout
10
+ * ds-cache-providers a provider
11
+ * ds-approvals-monitoring-template
12
+ *
13
+ * A page is not a component of a design system, and it has no visual identity to
14
+ * draw - which is why the vitrine came back unreadable. The renderer was not the
15
+ * problem; 95% of what entered had nothing to render.
16
+ *
17
+ * WHAT THIS IS NOT. It is not a quality bar and it never asks whether something is
18
+ * good. Every test here is about CATEGORY: a route, a provider, a config object.
19
+ * And nothing disappears - every rejection carries a reason, and the import prints
20
+ * them grouped, because "649 seen, not imported, and here is why" is information and
21
+ * a silent drop is a lie.
22
+ *
23
+ * FEWER AND BETTER. 37 components that all draw is a transformative result; 704
24
+ * where 30 draw is a broken product. This is the pipeline choosing legibility over
25
+ * coverage.
26
+ */
27
+ import { localOnlyNames } from "./components-scan.js";
28
+ /**
29
+ * Next's own reserved file names. Exact, not heuristic: the framework defines these,
30
+ * so a file called `page.tsx` IS a route in every Next codebase there is.
31
+ */
32
+ const RESERVED_FILE = /(^|[/\\])(page|layout|template|default|error|global-error|not-found|loading|route|middleware|instrumentation|sitemap|robots|opengraph-image|icon|apple-icon|manifest)\.[jt]sx?$/;
33
+ /** Nuxt, SvelteKit and Remix equivalents, for the same exact reason. */
34
+ const RESERVED_FILE_OTHER = /(^|[/\\])(\+page|\+layout|\+server|_app|_document|_layout)\.[a-z]+$/;
35
+ /**
36
+ * A name that means WHOLE SCREEN in every codebase.
37
+ *
38
+ * Suffix-decisive, and deliberately short. `Page`, `Screen`, `Route` and `Template`
39
+ * name a destination; `Layout` names the frame around one. A component library does
40
+ * not export those, and the 704-component run was mostly these four words.
41
+ *
42
+ * `View` is NOT here on purpose: `TreeView` and `ListView` are components in a
43
+ * hundred libraries. It only counts when the file itself is a route.
44
+ */
45
+ const SCREEN_SUFFIX = /(Page|Screen|Route|Template|Layout)$/;
46
+ /** Something that carries context down the tree rather than drawing. */
47
+ const PROVIDER_SUFFIX = /(Provider|Providers|Context|Boundary|Guard|Wrapper)$/;
48
+ /** Any JSX at all in the file. A capitalised export with none is a constant, a
49
+ * config object or a helper - `export const ROUTES = {…}` is not a component. */
50
+ const HAS_JSX = /<[A-Za-z][^>]*>|<>/;
51
+ /**
52
+ * Any sign that this file decides how something LOOKS.
53
+ *
54
+ * The generic catch, and the one that gets the providers whose names give nothing
55
+ * away. A file with no class, no style, no styled-anything and no variant helper is
56
+ * plumbing: it may render, but every pixel it produces was decided somewhere else.
57
+ */
58
+ const HAS_STYLING = /className|class=|style=|styled[.(]|css`|cva\(|\btv\(|makeStyles|sx=|tw`/;
59
+ /** `createContext` + a `.Provider` in the return is a provider whatever it is
60
+ * called - `RootWrapper` and `CommonProviders` both read this way. */
61
+ const IS_CONTEXT = /createContext\s*[<(]/;
62
+ /** A lowercase JSX tag: an html element this file renders ITSELF. */
63
+ const OWN_ELEMENT = /<(?:[a-z][a-z0-9]*)(\s|>|\/)/;
64
+ /** Every capitalised JSX tag the file opens, dots included. */
65
+ const RENDERED_TAG = /<([A-Z][A-Za-z0-9_]*(?:\.[A-Za-z0-9_]+)*)/g;
66
+ /** `import … from "some-package"` - not relative, not an alias of theirs. */
67
+ const THIRD_PARTY_IMPORT = /import\s+(?:type\s+)?([\s\S]*?)\s+from\s+["'](?![./]|@\/)([^"']+)["']/g;
68
+ /**
69
+ * A file whose every pixel belongs to somebody else's component.
70
+ *
71
+ * `apps/web-dashboard/src/components/ui/_legacy/_chakra-ui/accordion.tsx` forwards
72
+ * to `<Accordion.ItemTrigger>` and renders no element of its own; 205 files like it
73
+ * arrived as components of the system (dono, 01/08). The first version rejected them
74
+ * for "deciding nothing about how anything looks", which reached the right outcome
75
+ * with a false reason - `gap="4"` and `rotate={…}` ARE styling decisions, just not
76
+ * through a class.
77
+ *
78
+ * The truthful category is the one this pipeline already has a word for: EXTERNAL.
79
+ * A component that renders only a third party's components, with no element of its
80
+ * own, is an ADAPTER for that library. Its markup and its pixels are that library's,
81
+ * and a design system that claimed them would be claiming what it cannot re-theme.
82
+ *
83
+ * The test needs no list of style props, which is why it holds across Chakra, Panda,
84
+ * MUI and whatever comes next: no html element of its own AND it imports components
85
+ * from a package.
86
+ */
87
+ function adapterFor(source, internal) {
88
+ // It renders an html element of its own: whatever else it does, some pixel here is
89
+ // this file's decision.
90
+ if (OWN_ELEMENT.test(source))
91
+ return null;
92
+ /**
93
+ * THE POLYMORPHIC ESCAPE, and it is not a detail.
94
+ *
95
+ * `function Text({ as: Component = "p" })` renders `<Component>` and no lowercase
96
+ * tag, so the first version of this test rejected the real `Text` and `Pill` of a
97
+ * real library as adapters (spec, 01/08). `Component` is a prop rename - a name the
98
+ * file BINDS - and a tag a file binds itself is not somebody else's component.
99
+ *
100
+ * The reachability reader already answers exactly this question, so it answers it
101
+ * here rather than being approximated a second time.
102
+ */
103
+ const own = localOnlyNames(source);
104
+ const fromPackage = new Map();
105
+ THIRD_PARTY_IMPORT.lastIndex = 0;
106
+ for (const m of source.matchAll(THIRD_PARTY_IMPORT)) {
107
+ const spec = m[2];
108
+ if (internal.some((pre) => spec === pre || spec.startsWith(`${pre}/`))) {
109
+ continue;
110
+ }
111
+ for (const raw of m[1].replace(/[{}]/g, ",").split(",")) {
112
+ const local = (raw
113
+ .trim()
114
+ .split(/\s+as\s+/)
115
+ .pop() ?? "").trim();
116
+ if (/^[A-Z]/.test(local))
117
+ fromPackage.set(local, spec);
118
+ }
119
+ }
120
+ if (fromPackage.size === 0)
121
+ return null;
122
+ RENDERED_TAG.lastIndex = 0;
123
+ let theirs = null;
124
+ let seen = 0;
125
+ for (const m of source.matchAll(RENDERED_TAG)) {
126
+ const root = m[1].split(".")[0];
127
+ if (own.has(root))
128
+ return null;
129
+ const spec = fromPackage.get(root);
130
+ // A tag from neither - their own component, imported relatively - means this file
131
+ // composes the system rather than adapting a library.
132
+ if (!spec)
133
+ return null;
134
+ theirs ??= spec;
135
+ seen += 1;
136
+ }
137
+ return seen > 0 ? theirs : null;
138
+ }
139
+ export function gateComponent(input) {
140
+ const { name, file, source } = input;
141
+ if (RESERVED_FILE.test(file) || RESERVED_FILE_OTHER.test(file)) {
142
+ return {
143
+ ok: false,
144
+ why: "route",
145
+ because: `\`${file}\` is a file name the framework reserves - it is a route, not a component somebody can import`,
146
+ };
147
+ }
148
+ if (SCREEN_SUFFIX.test(name)) {
149
+ return {
150
+ ok: false,
151
+ why: "screen",
152
+ because: `\`${name}\` names a whole screen or the frame around one. A design system is made of the pieces a screen is built FROM`,
153
+ };
154
+ }
155
+ if (PROVIDER_SUFFIX.test(name) || IS_CONTEXT.test(source)) {
156
+ return {
157
+ ok: false,
158
+ why: "provider",
159
+ because: `\`${name}\` carries context or wraps a tree rather than drawing anything of its own`,
160
+ };
161
+ }
162
+ const adapter = adapterFor(source, input.internal ?? []);
163
+ if (adapter) {
164
+ return {
165
+ ok: false,
166
+ why: "adapter",
167
+ because: `\`${name}\` renders \`${adapter}\`'s components and no element of its own - its markup and every pixel are that library's, so there is nothing here for this system to re-theme`,
168
+ };
169
+ }
170
+ if (!HAS_JSX.test(source)) {
171
+ return {
172
+ ok: false,
173
+ why: "config",
174
+ because: `\`${name}\` is a capitalised export in a file with no markup - a constant, a config object or a helper`,
175
+ };
176
+ }
177
+ if (!HAS_STYLING.test(source)) {
178
+ return {
179
+ ok: false,
180
+ why: "no-styling",
181
+ because: `\`${name}\` renders but decides nothing about how anything looks - every pixel it produces was decided somewhere else`,
182
+ };
183
+ }
184
+ return { ok: true };
185
+ }
186
+ /**
187
+ * The rejections, grouped and counted, in the order that reads best out loud.
188
+ *
189
+ * NO SILENT CAPS. A run that quietly imports 40 of 704 reads as "your project has 40
190
+ * components", which is the one thing a census must never imply. Every name is
191
+ * carried; the printer shows the first few per group and says how many more.
192
+ */
193
+ export const SKIP_LABEL = {
194
+ route: "routes and framework files",
195
+ screen: "whole screens, and the frames around them",
196
+ provider: "providers, contexts and wrappers",
197
+ adapter: "adapters for another library's components",
198
+ config: "constants, config and helpers",
199
+ "no-jsx": "capitalised exports with no markup",
200
+ "no-styling": "components that decide nothing about how anything looks",
201
+ };
202
+ export function groupSkips(skips) {
203
+ const order = [
204
+ "screen",
205
+ "route",
206
+ "provider",
207
+ "adapter",
208
+ "no-styling",
209
+ "config",
210
+ "no-jsx",
211
+ ];
212
+ const by = new Map();
213
+ for (const s of skips) {
214
+ by.set(s.why, [...(by.get(s.why) ?? []), s.name]);
215
+ }
216
+ return order
217
+ .filter((why) => (by.get(why) ?? []).length > 0)
218
+ .map((why) => ({
219
+ why,
220
+ label: SKIP_LABEL[why],
221
+ names: (by.get(why) ?? []).sort(),
222
+ }));
223
+ }
224
+ /**
225
+ * One line saying what the gate did, in the terms the decision was actually made on.
226
+ *
227
+ * The sentence a person needs in order to trust a number that just dropped by an
228
+ * order of magnitude - and to notice if it dropped too far.
229
+ */
230
+ export function describeGate(kept, skipped) {
231
+ if (skipped === 0) {
232
+ return `${kept} component${kept === 1 ? "" : "s"} - every capitalised export here is one.`;
233
+ }
234
+ return `${kept} component${kept === 1 ? "" : "s"}, and ${skipped} export${skipped === 1 ? "" : "s"} left out. A page is not a component of a design system: it has no visual identity to draw, and importing one puts a name in your vitrine that nobody can compose with. Everything left out is listed below with the reason, so you can disagree with any of it.`;
235
+ }
@@ -91,6 +91,20 @@ const SPACING_PROPERTY = {
91
91
  mx: "marginInline",
92
92
  my: "marginBlock",
93
93
  gap: "gap",
94
+ /**
95
+ * SIZE, which the first version had no entry for at all.
96
+ *
97
+ * A real `cva` puts the whole size axis in `h-8 px-3`, `h-12 px-6`, `h-16 px-10` -
98
+ * six options, and only the padding came through, so every size read as the same
99
+ * height (probe, 01/08). A control's height IS its size.
100
+ */
101
+ h: "height",
102
+ w: "width",
103
+ size: "size",
104
+ "min-h": "minHeight",
105
+ "min-w": "minWidth",
106
+ "max-h": "maxHeight",
107
+ "max-w": "maxWidth",
94
108
  };
95
109
  /** Tailwind's radius scale. */
96
110
  const RADIUS = {
@@ -221,6 +235,35 @@ export function readUtility(utility, declared) {
221
235
  const keyword = TYPE_KEYWORD[core];
222
236
  if (keyword)
223
237
  return keyword;
238
+ /**
239
+ * AN ARBITRARY VALUE POINTING AT ONE OF THEIR OWN TOKENS.
240
+ *
241
+ * `rounded-[var(--radius-md)]`, `gap-[var(--spacing-sm)]`,
242
+ * `[font-size:var(--text-body-s)]`. This library writes half its scale that way, and
243
+ * none of it was read - the base of a real Button came back with two declarations out
244
+ * of fifteen (probe, 01/08).
245
+ *
246
+ * It is the STRONGEST kind of value in the file, not the weakest: an arbitrary value
247
+ * naming a custom property is somebody pointing at their own declared token by hand.
248
+ * The bracket is a Tailwind escape hatch; the `var()` inside it is a token ref.
249
+ */
250
+ const arbitraryProp = /^\[([a-z-]+):(.+)\]$/.exec(core);
251
+ if (arbitraryProp) {
252
+ const property = arbitraryProp[1].replace(/-([a-z])/g, (_, c) => c.toUpperCase());
253
+ const value = resolveArbitrary(arbitraryProp[2], declared);
254
+ if (value)
255
+ return { property, value: value.value, token: value.token };
256
+ }
257
+ const bracket = core.indexOf("-[");
258
+ if (bracket !== -1 && core.endsWith("]")) {
259
+ const head = core.slice(0, bracket);
260
+ const inner = core.slice(bracket + 2, -1);
261
+ const property = COLOR_PROPERTY[head] ?? SPACING_PROPERTY[head] ?? BRACKET_PROPERTY[head];
262
+ const value = resolveArbitrary(inner, declared);
263
+ if (property && value) {
264
+ return { property, value: value.value, token: value.token };
265
+ }
266
+ }
224
267
  const dash = core.indexOf("-");
225
268
  if (dash === -1)
226
269
  return null;
@@ -299,6 +342,14 @@ export function readUtility(utility, declared) {
299
342
  }
300
343
  return null;
301
344
  }
345
+ /**
346
+ * `opacity-50`, which a real `cva` uses for its whole disabled look:
347
+ * `disabled:opacity-50 disabled:grayscale-[0.5]`. Tailwind's own scale is the
348
+ * percentage, so the value is the number over one hundred.
349
+ */
350
+ if (prefix === "opacity" && /^\d{1,3}$/.test(rest)) {
351
+ return { property: "opacity", value: String(Number(rest) / 100) };
352
+ }
302
353
  const spaceProp = SPACING_PROPERTY[prefix];
303
354
  if (spaceProp && SPACING[rest]) {
304
355
  return { property: spaceProp, value: SPACING[rest] };
@@ -324,6 +375,71 @@ export function readUtility(utility, declared) {
324
375
  * before it is the family, hyphens intact, because `royal-blue-500` is one
325
376
  * family called `royal-blue`.
326
377
  */
378
+ /**
379
+ * Utility heads whose arbitrary value is not a colour or a length in the tables above.
380
+ * `rounded-[…]` is a radius, `shadow-[…]` an elevation, `text-[…]` ambiguous by design
381
+ * (a colour or a size) and resolved by what the value looks like.
382
+ */
383
+ const BRACKET_PROPERTY = {
384
+ rounded: "borderRadius",
385
+ shadow: "boxShadow",
386
+ border: "borderWidth",
387
+ leading: "lineHeight",
388
+ tracking: "letterSpacing",
389
+ font: "fontFamily",
390
+ opacity: "opacity",
391
+ z: "zIndex",
392
+ grid: "gridTemplateColumns",
393
+ aspect: "aspectRatio",
394
+ };
395
+ /**
396
+ * What is inside the brackets, resolved.
397
+ *
398
+ * `var(--radius-md)` where they declared `--radius-md` is a REF - the value travels as
399
+ * the token they named it. A `var()` they never declared, or a plain literal, travels
400
+ * as the literal they typed, which keeps v1 a mirror and leaves normalising to a v2
401
+ * they approve.
402
+ */
403
+ function resolveArbitrary(inner, declared) {
404
+ const raw = inner.replace(/_/g, " ").trim();
405
+ if (!raw)
406
+ return null;
407
+ const v = /^var\(\s*(--[a-zA-Z0-9_-]+)\s*(?:,[^)]*)?\)$/.exec(raw);
408
+ if (!v) {
409
+ // A literal in brackets is still a real value somebody typed.
410
+ return /[<>{}@;]/.test(raw) ? null : { value: raw };
411
+ }
412
+ const name = v[1];
413
+ if (!declared.has(name))
414
+ return { value: raw };
415
+ return { value: tokenRefFor(name), token: name };
416
+ }
417
+ /**
418
+ * A declared custom property, as a token ref in the document's own namespace.
419
+ *
420
+ * `--color-ocean-500` → `{color.ocean.500}`, `--radius-md` → `{radius.md}`,
421
+ * `--text-body-s` → `{typography.scale.body-s.fontSize}`. Their name is the path,
422
+ * which is the whole "your names travel unchanged" promise applied one level deeper.
423
+ */
424
+ function tokenRefFor(name) {
425
+ const bare = name.replace(/^--/, "");
426
+ if (bare.startsWith("color-"))
427
+ return refFor(bare.slice("color-".length));
428
+ if (bare.startsWith("radius-"))
429
+ return `{radius.${bare.slice(7)}}`;
430
+ if (bare.startsWith("spacing-"))
431
+ return `{spacing.${bare.slice(8)}}`;
432
+ if (bare.startsWith("shadow-"))
433
+ return `{shadow.${bare.slice(7)}}`;
434
+ if (bare.startsWith("text-")) {
435
+ return `{typography.scale.${bare.slice(5)}.fontSize}`;
436
+ }
437
+ if (bare.startsWith("font-"))
438
+ return `{typography.families.${bare.slice(5)}}`;
439
+ // A namespace we do not model. The literal `var()` is still correct CSS against
440
+ // their own stylesheet, and inventing a ref would point at nothing.
441
+ return `var(${name})`;
442
+ }
327
443
  function refFor(name) {
328
444
  const m = /^(.*)-(\d{2,4})$/.exec(name);
329
445
  if (!m)