synthesisui 0.16.30 → 0.16.32
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/dist/commands/sync.js +1 -1
- package/dist/component-codegen.js +96 -21
- package/package.json +1 -1
package/dist/commands/sync.js
CHANGED
|
@@ -16,7 +16,7 @@ export function decisionLine(d, slug) {
|
|
|
16
16
|
case "authored":
|
|
17
17
|
return ` ✓ ${d.id} authored on the platform${note}. Get it: npx synthesisui@latest upgrade ${slug}`;
|
|
18
18
|
case "declined":
|
|
19
|
-
return ` ✕ ${d.id} declined${note}.
|
|
19
|
+
return ` ✕ ${d.id} declined${note}. Now a RULE of the system - it travels with the next upgrade, and agents obey it.`;
|
|
20
20
|
case "platform":
|
|
21
21
|
return ` → ${d.id} routed to the synthesisui team${note}. It is their queue now.`;
|
|
22
22
|
default:
|
|
@@ -127,14 +127,48 @@ export function propsTypeName(reactMajor) {
|
|
|
127
127
|
? "ComponentProps"
|
|
128
128
|
: "ComponentPropsWithoutRef";
|
|
129
129
|
}
|
|
130
|
-
function propsType(axes, tag, base) {
|
|
130
|
+
function propsType(axes, tag, base, withAs = false) {
|
|
131
131
|
const extras = axes.map((a) => a.boolean
|
|
132
132
|
? ` ${a.prop}?: boolean;`
|
|
133
133
|
: ` ${a.prop}?: ${a.options.map((o) => `"${o}"`).join(" | ")};`);
|
|
134
|
+
if (withAs)
|
|
135
|
+
extras.push(" /** Render as a different element (th, dt, h2, li…) - the recipe is", " * styling only, so the document's semantics stay yours. */", " as?: ElementType;");
|
|
134
136
|
if (extras.length === 0)
|
|
135
137
|
return `${base}<"${tag}">`;
|
|
136
138
|
return `${base}<"${tag}"> & {\n${extras.join("\n")}\n}`;
|
|
137
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* THE ELEMENT IS THE CALLER'S DECISION (1kro2o, 29/07).
|
|
142
|
+
*
|
|
143
|
+
* The generated table rendered every part as a <div>, so a pricing
|
|
144
|
+
* comparison could not express <th scope="col"> - and the agent hand-extended
|
|
145
|
+
* Heading/Text with an `as` prop knowing regeneration would erase it. The
|
|
146
|
+
* recipe is TYPE STYLING; the outline, the scopes, the list semantics belong
|
|
147
|
+
* to the markup's author. The cast to the default tag is deliberate: a union
|
|
148
|
+
* of intrinsic tags is more than the JSX spread can narrow, and every tag
|
|
149
|
+
* offered takes the same attributes in practice - the pattern is the exact
|
|
150
|
+
* one the field agent wrote by hand.
|
|
151
|
+
*/
|
|
152
|
+
const asTagLine = (tag) => ` const Tag = (as ?? "${tag}") as "${tag}";`;
|
|
153
|
+
/** The render pieces for an element that offers `as`. `type="button"` must
|
|
154
|
+
* follow the RENDERED tag - a Button rendered as an <a> with type="button"
|
|
155
|
+
* is invalid HTML wearing a straight face. Void elements never offer `as`:
|
|
156
|
+
* their children/attribute contracts do not survive a tag change. */
|
|
157
|
+
function asElement(tag, attrs, voidEl) {
|
|
158
|
+
if (voidEl)
|
|
159
|
+
return { jsxTag: tag, jsxAttrs: attrs, setup: "", offersAs: false };
|
|
160
|
+
const hasTypeButton = attrs.includes(' type="button"');
|
|
161
|
+
const staticAttrs = attrs.replace(' type="button"', "");
|
|
162
|
+
const typeLine = hasTypeButton
|
|
163
|
+
? `\n {...(Tag === "button" ? { type: "button" as const } : {})}`
|
|
164
|
+
: "";
|
|
165
|
+
return {
|
|
166
|
+
jsxTag: "Tag",
|
|
167
|
+
jsxAttrs: `${staticAttrs}${typeLine}`,
|
|
168
|
+
setup: `${asTagLine(tag)}\n`,
|
|
169
|
+
offersAs: true,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
138
172
|
function dataAttrLines(axes) {
|
|
139
173
|
return axes
|
|
140
174
|
.map((a) => a.boolean
|
|
@@ -471,43 +505,51 @@ ${inner}
|
|
|
471
505
|
}
|
|
472
506
|
function emitCssMode(slug, name, recipe, version, props) {
|
|
473
507
|
const { tag, attrs, voidEl } = elementFor(name, recipe);
|
|
508
|
+
const el = asElement(tag, attrs, voidEl);
|
|
474
509
|
const axes = axesOf(recipe.variants);
|
|
475
510
|
const comp = pascal(name);
|
|
476
511
|
const propNames = axes.map((a) => a.prop);
|
|
477
|
-
const destructure = [
|
|
478
|
-
|
|
512
|
+
const destructure = [
|
|
513
|
+
...propNames,
|
|
514
|
+
...(el.offersAs ? ["as"] : []),
|
|
515
|
+
"className",
|
|
516
|
+
"...props",
|
|
517
|
+
].join(", ");
|
|
518
|
+
const rootJsx = ` <${el.jsxTag}${el.jsxAttrs}\n className={${joinCls([`"ds-${name}"`, "className"])}}\n${dataAttrLines(axes)}${axes.length ? "\n" : ""} {...props}\n />`;
|
|
479
519
|
const parts = Object.entries(recipe.parts ?? {}).map(([partName, part]) => {
|
|
480
520
|
const partAxes = axesOf(part.variants ?? {});
|
|
481
521
|
const partComp = `${comp}${pascal(partName)}`;
|
|
522
|
+
const control = partIsInteractive(part);
|
|
523
|
+
const partTag = control ? "button" : "div";
|
|
524
|
+
const partEl = asElement(partTag, control ? ' type="button"' : "", false);
|
|
482
525
|
const partDestructure = [
|
|
483
526
|
...partAxes.map((a) => a.prop),
|
|
527
|
+
"as",
|
|
484
528
|
"className",
|
|
485
529
|
"...props",
|
|
486
530
|
].join(", ");
|
|
487
|
-
const control = partIsInteractive(part);
|
|
488
|
-
const partTag = control ? "button" : "div";
|
|
489
|
-
const partAttrs = control ? '\n type="button"' : "";
|
|
490
531
|
return `
|
|
491
532
|
/** Part "${partName}" of ${comp} - compose it inside <${comp}>. */
|
|
492
|
-
export function ${partComp}({ ${partDestructure} }: ${propsType(partAxes, partTag, props)}) {
|
|
493
|
-
return (
|
|
494
|
-
<${
|
|
533
|
+
export function ${partComp}({ ${partDestructure} }: ${propsType(partAxes, partTag, props, true)}) {
|
|
534
|
+
${partEl.setup} return (
|
|
535
|
+
<${partEl.jsxTag}${partEl.jsxAttrs}
|
|
495
536
|
className={${joinCls([`"ds-${name}-${kebab(partName)}"`, "className"])}}
|
|
496
537
|
${dataAttrLines(partAxes)}${partAxes.length ? "\n" : ""} {...props}
|
|
497
538
|
/>
|
|
498
539
|
);
|
|
499
540
|
}`;
|
|
500
541
|
});
|
|
542
|
+
const needsElementType = el.offersAs || Object.keys(recipe.parts ?? {}).length > 0;
|
|
501
543
|
return `${header(slug, name, version, "css")}
|
|
502
544
|
import "./${name}.css";
|
|
503
545
|
|
|
504
|
-
import type { ${props} } from "react";
|
|
546
|
+
import type { ${needsElementType ? `ElementType, ${props}` : props} } from "react";
|
|
505
547
|
|
|
506
|
-
type ${comp}Props = ${propsType(axes, tag, props)};
|
|
548
|
+
type ${comp}Props = ${propsType(axes, tag, props, el.offersAs)};
|
|
507
549
|
|
|
508
550
|
${compositionHint(comp, name, recipe, voidEl)}
|
|
509
551
|
export function ${comp}({ ${destructure} }: ${comp}Props) {
|
|
510
|
-
return (
|
|
552
|
+
${el.setup} return (
|
|
511
553
|
${rootJsx}
|
|
512
554
|
);
|
|
513
555
|
}
|
|
@@ -515,6 +557,7 @@ ${parts.join("\n")}`;
|
|
|
515
557
|
}
|
|
516
558
|
function emitTailwindMode(slug, name, recipe, version, props) {
|
|
517
559
|
const { tag, attrs, voidEl } = elementFor(name, recipe);
|
|
560
|
+
const el = asElement(tag, attrs, voidEl);
|
|
518
561
|
const axes = axesOf(recipe.variants);
|
|
519
562
|
const comp = pascal(name);
|
|
520
563
|
const variantConsts = axes
|
|
@@ -543,39 +586,71 @@ function emitTailwindMode(slug, name, recipe, version, props) {
|
|
|
543
586
|
];
|
|
544
587
|
const destructure = [
|
|
545
588
|
...axes.map((a) => a.prop),
|
|
589
|
+
...(el.offersAs ? ["as"] : []),
|
|
546
590
|
"className",
|
|
547
591
|
"...props",
|
|
548
592
|
].join(", ");
|
|
593
|
+
const needsElementType = el.offersAs || Object.keys(recipe.parts ?? {}).length > 0;
|
|
549
594
|
return `${header(slug, name, version, "tailwind")}
|
|
550
595
|
|
|
551
|
-
import type { ${props} } from "react";
|
|
596
|
+
import type { ${needsElementType ? `ElementType, ${props}` : props} } from "react";
|
|
552
597
|
import { cn } from "../cn";
|
|
553
598
|
|
|
554
599
|
const BASE = ${JSON.stringify(tailwindClassList(recipe, excluded))};
|
|
555
600
|
${[...variantConsts, ...booleanConsts].join("\n")}
|
|
556
601
|
|
|
557
|
-
type ${comp}Props = ${propsType(axes, tag, props)};
|
|
602
|
+
type ${comp}Props = ${propsType(axes, tag, props, el.offersAs)};
|
|
558
603
|
|
|
559
604
|
${compositionHint(comp, name, recipe, voidEl)}
|
|
560
605
|
export function ${comp}({ ${destructure} }: ${comp}Props) {
|
|
561
|
-
return (
|
|
562
|
-
<${
|
|
606
|
+
${el.setup} return (
|
|
607
|
+
<${el.jsxTag}${el.jsxAttrs}
|
|
563
608
|
className={${resolveCls(clsParts)}}
|
|
564
|
-
{...props}
|
|
609
|
+
${dataAttrLines(axes)}${axes.length ? "\n" : ""} {...props}
|
|
565
610
|
/>
|
|
566
611
|
);
|
|
567
612
|
}
|
|
568
613
|
${Object.entries(recipe.parts ?? {})
|
|
569
614
|
.map(([partName, part]) => {
|
|
570
615
|
const partComp = `${comp}${pascal(partName)}`;
|
|
616
|
+
const partAxes = axesOf(part.variants ?? {});
|
|
571
617
|
const control = partIsInteractive(part);
|
|
572
618
|
const partTag = control ? "button" : "div";
|
|
573
|
-
const
|
|
619
|
+
const partEl = asElement(partTag, control ? ' type="button"' : "", false);
|
|
620
|
+
/**
|
|
621
|
+
* THE STATE THE CATALOGUE DOCUMENTS, DELIVERED (1kro2o, 29/07).
|
|
622
|
+
*
|
|
623
|
+
* A part's variants used to be dropped entirely in this flavour: the
|
|
624
|
+
* GUIDE documented data-active="true", tokens.css styled it, and the
|
|
625
|
+
* generated TabsTrigger rendered an active trigger identical to an
|
|
626
|
+
* inactive one - the promise died in the last mile of codegen.
|
|
627
|
+
*
|
|
628
|
+
* They emit as data-[attr=option]: utilities, not as const-switching
|
|
629
|
+
* like the root: the compiled selector is class+attribute, which
|
|
630
|
+
* outranks the plain base honestly (the same ladder css mode gets from
|
|
631
|
+
* [data-*] selectors) - AND the styling answers the data attribute
|
|
632
|
+
* itself, so it works whether the caller passes the typed prop or
|
|
633
|
+
* toggles data-active from its own state, which is how the GUIDE
|
|
634
|
+
* documents the contract.
|
|
635
|
+
*/
|
|
636
|
+
const partVariantClasses = partAxes.flatMap((a) => a.options.flatMap((o) => blockToTailwind(part.variants?.[a.key]?.[o] ?? {}, `data-[${a.attr}=${o}]:`)));
|
|
637
|
+
const partCls = [tailwindClassList(part), ...partVariantClasses]
|
|
638
|
+
.filter(Boolean)
|
|
639
|
+
.join(" ");
|
|
640
|
+
const partDestructure = [
|
|
641
|
+
...partAxes.map((a) => a.prop),
|
|
642
|
+
"as",
|
|
643
|
+
"className",
|
|
644
|
+
"...props",
|
|
645
|
+
].join(", ");
|
|
574
646
|
return `
|
|
575
647
|
/** Part "${partName}" of ${comp} - compose it inside <${comp}>. */
|
|
576
|
-
export function ${partComp}({
|
|
577
|
-
return (
|
|
578
|
-
<${
|
|
648
|
+
export function ${partComp}({ ${partDestructure} }: ${propsType(partAxes, partTag, props, true)}) {
|
|
649
|
+
${partEl.setup} return (
|
|
650
|
+
<${partEl.jsxTag}${partEl.jsxAttrs}
|
|
651
|
+
className={${resolveCls([JSON.stringify(partCls), "className"])}}
|
|
652
|
+
${dataAttrLines(partAxes)}${partAxes.length ? "\n" : ""} {...props}
|
|
653
|
+
/>
|
|
579
654
|
);
|
|
580
655
|
}`;
|
|
581
656
|
})
|
package/package.json
CHANGED