handoff-app 1.1.3 → 1.2.0-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/config/config.template.js +21 -14
- package/config/config.template.ts +54 -0
- package/config/docs/system.md +2 -0
- package/dist/app-builder/build.js +7 -2
- package/dist/app-builder/config-diff/index.d.ts +4 -0
- package/dist/app-builder/config-diff/index.js +12 -0
- package/dist/app-builder/config-diff/registry.d.ts +9 -0
- package/dist/app-builder/config-diff/registry.js +30 -0
- package/dist/app-builder/config-diff/snapshot.d.ts +13 -0
- package/dist/app-builder/config-diff/snapshot.js +43 -0
- package/dist/app-builder/config-diff/strategies/pattern.d.ts +2 -0
- package/dist/app-builder/config-diff/strategies/pattern.js +93 -0
- package/dist/app-builder/config-diff/types.d.ts +19 -0
- package/dist/app-builder/config-diff/types.js +2 -0
- package/dist/app-builder/watchers/component.d.ts +15 -0
- package/dist/app-builder/watchers/component.js +69 -0
- package/dist/app-builder/watchers/utils.d.ts +14 -0
- package/dist/app-builder/watchers/utils.js +44 -0
- package/dist/app-builder/watchers.d.ts +10 -19
- package/dist/app-builder/watchers.js +56 -102
- package/dist/cache/build-cache.js +14 -5
- package/dist/cli/create.js +67 -23
- package/dist/cli/eject.js +48 -7
- package/dist/cli/make.js +29 -4
- package/dist/cli/scaffold.js +60 -19
- package/dist/cli.js +1 -1
- package/dist/config/helpers.d.ts +7 -0
- package/dist/config/helpers.js +31 -0
- package/dist/config/index.d.ts +2 -1
- package/dist/config/index.js +5 -1
- package/dist/config/loader.d.ts +10 -1
- package/dist/config/loader.js +92 -28
- package/dist/config/normalizers/declaration.d.ts +11 -0
- package/dist/config/normalizers/declaration.js +119 -0
- package/dist/config/normalizers/pattern.d.ts +9 -0
- package/dist/config/normalizers/pattern.js +38 -0
- package/dist/config/runtime.d.ts +9 -3
- package/dist/config/runtime.js +254 -33
- package/dist/declarations/helpers.d.ts +6 -0
- package/dist/declarations/helpers.js +19 -0
- package/dist/declarations/index.d.ts +2 -0
- package/dist/declarations/index.js +9 -0
- package/dist/declarations/types.d.ts +60 -0
- package/dist/declarations/types.js +2 -0
- package/dist/index.d.ts +12 -1
- package/dist/index.js +34 -5
- package/dist/pipeline/index.d.ts +1 -0
- package/dist/pipeline/index.js +3 -1
- package/dist/pipeline/patterns.d.ts +6 -0
- package/dist/pipeline/patterns.js +21 -0
- package/dist/transformers/docgen/index.js +12 -2
- package/dist/transformers/plugins/csf-render.js +170 -34
- package/dist/transformers/plugins/ssr-render.js +18 -2
- package/dist/transformers/preview/component/api.js +16 -7
- package/dist/transformers/preview/component/builder.js +9 -3
- package/dist/transformers/preview/component/css.d.ts +8 -0
- package/dist/transformers/preview/component/html.js +14 -5
- package/dist/transformers/preview/component/previews.d.ts +9 -0
- package/dist/transformers/preview/component/previews.js +10 -0
- package/dist/transformers/preview/pattern/api.d.ts +7 -0
- package/dist/transformers/preview/pattern/api.js +61 -0
- package/dist/transformers/preview/pattern/builder.d.ts +9 -0
- package/dist/transformers/preview/pattern/builder.js +86 -0
- package/dist/transformers/preview/pattern/html.d.ts +19 -0
- package/dist/transformers/preview/pattern/html.js +94 -0
- package/dist/transformers/preview/types.d.ts +54 -0
- package/dist/transformers/utils/csf.d.ts +16 -0
- package/dist/transformers/utils/csf.js +30 -0
- package/dist/transformers/utils/index.d.ts +1 -0
- package/dist/transformers/utils/index.js +2 -0
- package/dist/transformers/utils/module.d.ts +5 -0
- package/dist/transformers/utils/module.js +26 -13
- package/dist/transformers/utils/usage.d.ts +23 -0
- package/dist/transformers/utils/usage.js +294 -0
- package/dist/types/config.d.ts +32 -7
- package/dist/types/preview.d.ts +2 -0
- package/docs/cli.md +3 -3
- package/package.json +3 -1
- package/src/app/components/Component/PageSliceResolver.tsx +6 -6
- package/src/app/components/Component/Preview.tsx +17 -12
- package/src/app/components/Component/slices/ComponentDisplaySlice.tsx +16 -6
- package/src/app/components/Markdown/CodeHighlight.tsx +60 -11
- package/src/app/components/util/index.ts +71 -2
- package/src/app/pages/system/pattern/[pattern]/index.tsx +223 -0
- package/src/app/pages/system/pattern/index.tsx +14 -0
- package/src/app-builder/README.md +37 -9
- package/src/app-builder/build.ts +8 -2
- package/src/app-builder/config-diff/index.ts +4 -0
- package/src/app-builder/config-diff/registry.ts +23 -0
- package/src/app-builder/config-diff/snapshot.ts +51 -0
- package/src/app-builder/config-diff/strategies/pattern.ts +65 -0
- package/src/app-builder/config-diff/types.ts +21 -0
- package/src/app-builder/watchers/component.ts +72 -0
- package/src/app-builder/watchers/utils.ts +44 -0
- package/src/app-builder/watchers.ts +62 -80
- package/src/cache/build-cache.ts +14 -5
- package/src/cli/create.ts +86 -25
- package/src/cli/eject.ts +29 -7
- package/src/cli/make.ts +32 -5
- package/src/cli/scaffold.ts +68 -21
- package/src/cli.ts +1 -1
- package/src/config/README.md +10 -1
- package/src/config/helpers.ts +43 -0
- package/src/config/index.ts +4 -1
- package/src/config/loader.ts +110 -31
- package/src/config/normalizers/declaration.ts +176 -0
- package/src/config/normalizers/pattern.ts +63 -0
- package/src/config/runtime.ts +310 -35
- package/src/declarations/helpers.ts +36 -0
- package/src/declarations/index.ts +19 -0
- package/src/declarations/types.ts +70 -0
- package/src/index.ts +47 -6
- package/src/pipeline/index.ts +1 -0
- package/src/pipeline/patterns.ts +10 -0
- package/src/transformers/README.md +2 -2
- package/src/transformers/docgen/index.ts +13 -2
- package/src/transformers/plugins/csf-render.ts +216 -49
- package/src/transformers/plugins/ssr-render.ts +23 -7
- package/src/transformers/preview/component/api.ts +27 -7
- package/src/transformers/preview/component/builder.ts +21 -4
- package/src/transformers/preview/component/html.ts +18 -5
- package/src/transformers/preview/component/previews.ts +12 -0
- package/src/transformers/preview/pattern/api.ts +66 -0
- package/src/transformers/preview/pattern/builder.ts +94 -0
- package/src/transformers/preview/pattern/html.ts +107 -0
- package/src/transformers/preview/types.ts +58 -1
- package/src/transformers/utils/csf.ts +48 -0
- package/src/transformers/utils/index.ts +3 -0
- package/src/transformers/utils/module.ts +33 -18
- package/src/transformers/utils/usage.ts +319 -0
- package/src/types/README.md +1 -1
- package/src/types/config.ts +33 -7
- package/src/types/preview.ts +2 -0
- package/templates/default/components/button/Button.jsx.tpl +64 -0
- package/templates/default/components/button/button.react.handoff.js.tpl +31 -0
- package/templates/default/components/button/button.react.handoff.ts.tpl +31 -0
- package/templates/default/handoff.config.js.tpl +29 -16
- package/templates/default/handoff.config.ts.tpl +196 -0
- package/templates/default/handoff.config.with-examples.js.tpl +28 -17
- package/templates/default/handoff.config.with-examples.ts.tpl +195 -0
- package/templates/default/package.json.tpl +1 -3
- package/handoff.config.js +0 -90
- package/templates/default/components/button/button.js.tpl +0 -62
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.extractComponentName = extractComponentName;
|
|
7
|
+
exports.generateUsageSnippet = generateUsageSnippet;
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const component_1 = require("../preview/component");
|
|
10
|
+
const CHILDREN_PROP = 'children';
|
|
11
|
+
const INDENT = ' ';
|
|
12
|
+
function isValidIdentifier(key) {
|
|
13
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Formats a prop value as a JSX attribute string.
|
|
17
|
+
* Returns the right-hand side only (e.g. `"hello"`, `{42}`, `{true}`).
|
|
18
|
+
*/
|
|
19
|
+
function formatPropValue(value, type) {
|
|
20
|
+
if (value === undefined || value === null) {
|
|
21
|
+
return '{undefined}';
|
|
22
|
+
}
|
|
23
|
+
switch (type) {
|
|
24
|
+
case component_1.SlotType.BOOLEAN:
|
|
25
|
+
return typeof value === 'boolean' ? `{${value}}` : `{${Boolean(value)}}`;
|
|
26
|
+
case component_1.SlotType.NUMBER:
|
|
27
|
+
return `{${Number(value)}}`;
|
|
28
|
+
case component_1.SlotType.FUNCTION:
|
|
29
|
+
return '{() => { /* handler */ }}';
|
|
30
|
+
case component_1.SlotType.OBJECT:
|
|
31
|
+
return `{${formatObjectLiteral(value)}}`;
|
|
32
|
+
case component_1.SlotType.ARRAY:
|
|
33
|
+
return `{${formatArrayLiteral(value)}}`;
|
|
34
|
+
case component_1.SlotType.TEXT:
|
|
35
|
+
case component_1.SlotType.IMAGE:
|
|
36
|
+
case component_1.SlotType.BUTTON:
|
|
37
|
+
case component_1.SlotType.ENUM:
|
|
38
|
+
default:
|
|
39
|
+
if (typeof value === 'string') {
|
|
40
|
+
return `"${escapeJsxString(value)}"`;
|
|
41
|
+
}
|
|
42
|
+
if (typeof value === 'boolean') {
|
|
43
|
+
return `{${value}}`;
|
|
44
|
+
}
|
|
45
|
+
if (typeof value === 'number') {
|
|
46
|
+
return `{${value}}`;
|
|
47
|
+
}
|
|
48
|
+
if (typeof value === 'object') {
|
|
49
|
+
return `{${JSON.stringify(value, null, 2)}}`;
|
|
50
|
+
}
|
|
51
|
+
return `"${escapeJsxString(String(value))}"`;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function escapeJsxString(str) {
|
|
55
|
+
return str.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
56
|
+
}
|
|
57
|
+
function formatObjectLiteral(value) {
|
|
58
|
+
if (typeof value !== 'object' || value === null) {
|
|
59
|
+
return JSON.stringify(value);
|
|
60
|
+
}
|
|
61
|
+
const entries = Object.entries(value);
|
|
62
|
+
if (entries.length === 0)
|
|
63
|
+
return '{}';
|
|
64
|
+
if (entries.length <= 3) {
|
|
65
|
+
const inline = entries
|
|
66
|
+
.map(([k, v]) => `${isValidIdentifier(k) ? k : JSON.stringify(k)}: ${JSON.stringify(v)}`)
|
|
67
|
+
.join(', ');
|
|
68
|
+
if (inline.length <= 60)
|
|
69
|
+
return `{ ${inline} }`;
|
|
70
|
+
}
|
|
71
|
+
return JSON.stringify(value, null, 2);
|
|
72
|
+
}
|
|
73
|
+
function formatArrayLiteral(value) {
|
|
74
|
+
if (!Array.isArray(value))
|
|
75
|
+
return JSON.stringify(value);
|
|
76
|
+
if (value.length === 0)
|
|
77
|
+
return '[]';
|
|
78
|
+
const inline = JSON.stringify(value);
|
|
79
|
+
if (inline.length <= 60)
|
|
80
|
+
return inline;
|
|
81
|
+
return JSON.stringify(value, null, 2);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Determines whether a prop value matches its declared default and can be omitted.
|
|
85
|
+
*/
|
|
86
|
+
function matchesDefault(value, slotDefault) {
|
|
87
|
+
if (slotDefault === undefined || slotDefault === null)
|
|
88
|
+
return false;
|
|
89
|
+
if (typeof value === typeof slotDefault && value === slotDefault)
|
|
90
|
+
return true;
|
|
91
|
+
// SlotMetadata.default is typed as `string | undefined`, but runtime values
|
|
92
|
+
// may be numbers or booleans. Do a loose string comparison as a fallback.
|
|
93
|
+
if (String(value) === String(slotDefault))
|
|
94
|
+
return true;
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Extracts the PascalCase component name from a file path.
|
|
99
|
+
* e.g. `/path/to/Button.tsx` -> `Button`
|
|
100
|
+
*/
|
|
101
|
+
function extractComponentName(filePath) {
|
|
102
|
+
const basename = path_1.default.basename(filePath);
|
|
103
|
+
const ext = path_1.default.extname(basename);
|
|
104
|
+
return basename.slice(0, basename.length - ext.length);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Generates a JSX usage snippet showing how to consume a React component
|
|
108
|
+
* with the given preview values.
|
|
109
|
+
*
|
|
110
|
+
* Iterates over the union of preview values and schema properties so that
|
|
111
|
+
* props not present in the preview are still represented when they carry
|
|
112
|
+
* a meaningful default or are required.
|
|
113
|
+
*/
|
|
114
|
+
function generateUsageSnippet(options) {
|
|
115
|
+
var _a;
|
|
116
|
+
const { componentName, properties, previewValues, templateFileName, importStatement } = options;
|
|
117
|
+
const importPath = `./${templateFileName ? path_1.default.basename(templateFileName, path_1.default.extname(templateFileName)) : componentName}`;
|
|
118
|
+
const attrs = [];
|
|
119
|
+
let childrenValue = undefined;
|
|
120
|
+
let hasChildren = false;
|
|
121
|
+
// Merge all known prop keys: preview values first (preserve order), then
|
|
122
|
+
// any additional keys from the property schema that weren't in the preview.
|
|
123
|
+
const allKeys = new Set([...Object.keys(previewValues), ...Object.keys(properties)]);
|
|
124
|
+
for (const key of allKeys) {
|
|
125
|
+
const slot = properties[key];
|
|
126
|
+
const inPreview = key in previewValues;
|
|
127
|
+
const value = inPreview ? previewValues[key] : resolveSchemaValue(slot);
|
|
128
|
+
// Nothing to show for this prop
|
|
129
|
+
if (value === undefined || value === null) {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (key === CHILDREN_PROP) {
|
|
133
|
+
hasChildren = true;
|
|
134
|
+
childrenValue = value;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
// Skip function props that weren't explicitly provided in preview values
|
|
138
|
+
if (!inPreview && (slot === null || slot === void 0 ? void 0 : slot.type) === component_1.SlotType.FUNCTION) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
// Omit props that match their declared default
|
|
142
|
+
if (slot && matchesDefault(value, slot.default)) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const type = (_a = slot === null || slot === void 0 ? void 0 : slot.type) !== null && _a !== void 0 ? _a : inferSlotType(value);
|
|
146
|
+
// Boolean true → JSX shorthand: <Comp disabled />
|
|
147
|
+
if (type === component_1.SlotType.BOOLEAN && value === true) {
|
|
148
|
+
attrs.push(key);
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
// Boolean false → omit (implicit JSX default)
|
|
152
|
+
if (type === component_1.SlotType.BOOLEAN && value === false) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
attrs.push(`${key}=${formatPropValue(value, type)}`);
|
|
156
|
+
}
|
|
157
|
+
const lines = [];
|
|
158
|
+
lines.push(importStatement || `import ${componentName} from '${importPath}';`);
|
|
159
|
+
lines.push('');
|
|
160
|
+
if (hasChildren && childrenValue !== undefined && childrenValue !== null && childrenValue !== '') {
|
|
161
|
+
const openTag = attrs.length > 0 ? `<${componentName} ${attrs.join(' ')}>` : `<${componentName}>`;
|
|
162
|
+
const childStr = typeof childrenValue === 'string'
|
|
163
|
+
? childrenValue
|
|
164
|
+
: typeof childrenValue === 'number' || typeof childrenValue === 'boolean'
|
|
165
|
+
? String(childrenValue)
|
|
166
|
+
: '{/* children */}';
|
|
167
|
+
if (childStr.length <= 40 && !childStr.includes('\n')) {
|
|
168
|
+
lines.push(`${openTag}${childStr}</${componentName}>`);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
lines.push(openTag);
|
|
172
|
+
for (const childLine of childStr.split('\n')) {
|
|
173
|
+
lines.push(`${INDENT}${childLine}`);
|
|
174
|
+
}
|
|
175
|
+
lines.push(`</${componentName}>`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
if (attrs.length === 0) {
|
|
180
|
+
lines.push(`<${componentName} />`);
|
|
181
|
+
}
|
|
182
|
+
else if (attrs.length <= 3 && attrs.join(' ').length <= 60) {
|
|
183
|
+
lines.push(`<${componentName} ${attrs.join(' ')} />`);
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
lines.push(`<${componentName}`);
|
|
187
|
+
for (const attr of attrs) {
|
|
188
|
+
lines.push(`${INDENT}${attr}`);
|
|
189
|
+
}
|
|
190
|
+
lines.push('/>');
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return lines.join('\n');
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Resolves a display value for a schema-only property (not present in preview values).
|
|
197
|
+
*
|
|
198
|
+
* - Uses the declared default when available and non-trivial.
|
|
199
|
+
* - For required props without a default, generates a type-appropriate placeholder.
|
|
200
|
+
* - Returns `undefined` for optional props with no useful default (they are skipped).
|
|
201
|
+
*/
|
|
202
|
+
function resolveSchemaValue(slot) {
|
|
203
|
+
var _a;
|
|
204
|
+
if (!slot)
|
|
205
|
+
return undefined;
|
|
206
|
+
// Use explicit default if it's non-trivial
|
|
207
|
+
if (slot.default !== undefined && slot.default !== null && slot.default !== '') {
|
|
208
|
+
return coerceDefault(slot.default, slot.type);
|
|
209
|
+
}
|
|
210
|
+
// Required props must appear even without a default
|
|
211
|
+
if ((_a = slot.rules) === null || _a === void 0 ? void 0 : _a.required) {
|
|
212
|
+
return getPlaceholder(slot);
|
|
213
|
+
}
|
|
214
|
+
// Optional with no default → omit
|
|
215
|
+
return undefined;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Coerces a string default from SlotMetadata into the appropriate JS type
|
|
219
|
+
* for code-generation purposes.
|
|
220
|
+
*/
|
|
221
|
+
function coerceDefault(value, type) {
|
|
222
|
+
if (typeof value !== 'string')
|
|
223
|
+
return value;
|
|
224
|
+
switch (type) {
|
|
225
|
+
case component_1.SlotType.BOOLEAN:
|
|
226
|
+
return value === 'true';
|
|
227
|
+
case component_1.SlotType.NUMBER: {
|
|
228
|
+
const n = Number(value);
|
|
229
|
+
return Number.isNaN(n) ? value : n;
|
|
230
|
+
}
|
|
231
|
+
default:
|
|
232
|
+
return value;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Generates a type-appropriate placeholder value for required props
|
|
237
|
+
* that have no default and no preview value.
|
|
238
|
+
*/
|
|
239
|
+
function getPlaceholder(slot) {
|
|
240
|
+
switch (slot.type) {
|
|
241
|
+
case component_1.SlotType.ENUM: {
|
|
242
|
+
const member = extractFirstEnumMember(slot);
|
|
243
|
+
return member !== null && member !== void 0 ? member : 'value';
|
|
244
|
+
}
|
|
245
|
+
case component_1.SlotType.TEXT:
|
|
246
|
+
case component_1.SlotType.IMAGE:
|
|
247
|
+
return '...';
|
|
248
|
+
case component_1.SlotType.NUMBER:
|
|
249
|
+
return 0;
|
|
250
|
+
case component_1.SlotType.BOOLEAN:
|
|
251
|
+
return true;
|
|
252
|
+
case component_1.SlotType.ARRAY:
|
|
253
|
+
return [];
|
|
254
|
+
case component_1.SlotType.OBJECT:
|
|
255
|
+
return {};
|
|
256
|
+
case component_1.SlotType.FUNCTION:
|
|
257
|
+
return undefined; // handled separately
|
|
258
|
+
default:
|
|
259
|
+
return '...';
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Attempts to extract the first enum member from a SlotMetadata's deepType
|
|
264
|
+
* (e.g. `"primary"` from `'primary' | 'secondary' | 'danger'`).
|
|
265
|
+
*/
|
|
266
|
+
function extractFirstEnumMember(slot) {
|
|
267
|
+
var _a, _b;
|
|
268
|
+
// deepType.members contains the enum values when available
|
|
269
|
+
if (((_a = slot.deepType) === null || _a === void 0 ? void 0 : _a.kind) === 'enum' && ((_b = slot.deepType.members) === null || _b === void 0 ? void 0 : _b.length)) {
|
|
270
|
+
const raw = slot.deepType.members[0];
|
|
271
|
+
// Members are stored as quoted strings like `"primary"` — strip quotes
|
|
272
|
+
return typeof raw === 'string' ? raw.replace(/^["']|["']$/g, '') : undefined;
|
|
273
|
+
}
|
|
274
|
+
// Fall back to parsing the generic display string (e.g. `"primary" | "secondary"`)
|
|
275
|
+
if (slot.generic) {
|
|
276
|
+
const match = slot.generic.match(/["']([^"']+)["']/);
|
|
277
|
+
if (match)
|
|
278
|
+
return match[1];
|
|
279
|
+
}
|
|
280
|
+
return undefined;
|
|
281
|
+
}
|
|
282
|
+
function inferSlotType(value) {
|
|
283
|
+
if (typeof value === 'boolean')
|
|
284
|
+
return component_1.SlotType.BOOLEAN;
|
|
285
|
+
if (typeof value === 'number')
|
|
286
|
+
return component_1.SlotType.NUMBER;
|
|
287
|
+
if (typeof value === 'function')
|
|
288
|
+
return component_1.SlotType.FUNCTION;
|
|
289
|
+
if (Array.isArray(value))
|
|
290
|
+
return component_1.SlotType.ARRAY;
|
|
291
|
+
if (typeof value === 'object' && value !== null)
|
|
292
|
+
return component_1.SlotType.OBJECT;
|
|
293
|
+
return component_1.SlotType.TEXT;
|
|
294
|
+
}
|
package/dist/types/config.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type Handlebars from 'handlebars';
|
|
|
3
3
|
import { Types as HandoffTypes } from 'handoff-core';
|
|
4
4
|
import { InlineConfig } from 'vite';
|
|
5
5
|
import { SlotMetadata } from '../transformers/preview/component';
|
|
6
|
-
import { ComponentListObject, TransformComponentTokensResult } from '../transformers/preview/types';
|
|
6
|
+
import { ComponentListObject, PatternListObject, TransformComponentTokensResult } from '../transformers/preview/types';
|
|
7
7
|
import { ValidationResult } from './preview';
|
|
8
8
|
export interface ImageStyle {
|
|
9
9
|
name: string;
|
|
@@ -65,13 +65,19 @@ export interface NextAppConfig {
|
|
|
65
65
|
theme?: string;
|
|
66
66
|
title: string;
|
|
67
67
|
client: string;
|
|
68
|
-
google_tag_manager
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
google_tag_manager?: string | null | undefined;
|
|
69
|
+
googleTagManager?: string | null | undefined;
|
|
70
|
+
type_copy?: string;
|
|
71
|
+
typeCopy?: string;
|
|
72
|
+
type_sort?: string[];
|
|
73
|
+
typeSort?: string[];
|
|
74
|
+
color_sort?: string[];
|
|
75
|
+
colorSort?: string[];
|
|
72
76
|
breakpoints: Breakpoints;
|
|
73
|
-
component_sort
|
|
74
|
-
|
|
77
|
+
component_sort?: string[];
|
|
78
|
+
componentSort?: string[];
|
|
79
|
+
base_path?: string;
|
|
80
|
+
basePath?: string;
|
|
75
81
|
attribution: boolean;
|
|
76
82
|
ports?: {
|
|
77
83
|
app: number;
|
|
@@ -80,7 +86,9 @@ export interface NextAppConfig {
|
|
|
80
86
|
}
|
|
81
87
|
export interface Config {
|
|
82
88
|
dev_access_token?: string | null | undefined;
|
|
89
|
+
devAccessToken?: string | null | undefined;
|
|
83
90
|
figma_project_id?: string | null | undefined;
|
|
91
|
+
figmaProjectId?: string | null | undefined;
|
|
84
92
|
exportsOutputDirectory?: string;
|
|
85
93
|
sitesOutputDirectory?: string;
|
|
86
94
|
useVariables?: boolean;
|
|
@@ -123,6 +131,12 @@ export interface Config {
|
|
|
123
131
|
* @example ["components/button", "components/input"]
|
|
124
132
|
*/
|
|
125
133
|
components?: string[];
|
|
134
|
+
/**
|
|
135
|
+
* Array of pattern paths to be included in the build.
|
|
136
|
+
* Patterns compose multiple component previews into single-page views.
|
|
137
|
+
* @example ["patterns/hero-section", "patterns"]
|
|
138
|
+
*/
|
|
139
|
+
patterns?: string[];
|
|
126
140
|
};
|
|
127
141
|
/**
|
|
128
142
|
* Configuration for asset zip file download links
|
|
@@ -140,6 +154,10 @@ export interface Config {
|
|
|
140
154
|
*/
|
|
141
155
|
logos?: string;
|
|
142
156
|
};
|
|
157
|
+
assetsZipLinks?: {
|
|
158
|
+
icons?: string;
|
|
159
|
+
logos?: string;
|
|
160
|
+
};
|
|
143
161
|
/**
|
|
144
162
|
* Configuration hooks for extending functionality
|
|
145
163
|
*/
|
|
@@ -278,6 +296,10 @@ export interface RuntimeConfigComponentOptions {
|
|
|
278
296
|
};
|
|
279
297
|
};
|
|
280
298
|
}
|
|
299
|
+
export interface ConfigFileEntry {
|
|
300
|
+
kind: string;
|
|
301
|
+
entityId: string;
|
|
302
|
+
}
|
|
281
303
|
export interface RuntimeConfig {
|
|
282
304
|
entries?: {
|
|
283
305
|
scss?: string;
|
|
@@ -286,6 +308,9 @@ export interface RuntimeConfig {
|
|
|
286
308
|
components: {
|
|
287
309
|
[id: string]: ComponentListObject;
|
|
288
310
|
};
|
|
311
|
+
patterns: {
|
|
312
|
+
[id: string]: PatternListObject;
|
|
313
|
+
};
|
|
289
314
|
};
|
|
290
315
|
options: {
|
|
291
316
|
[key: string]: RuntimeConfigComponentOptions;
|
package/dist/types/preview.d.ts
CHANGED
|
@@ -54,6 +54,7 @@ export interface PreviewObject {
|
|
|
54
54
|
[key: string]: string;
|
|
55
55
|
};
|
|
56
56
|
url: string;
|
|
57
|
+
usage?: string;
|
|
57
58
|
};
|
|
58
59
|
};
|
|
59
60
|
properties?: {
|
|
@@ -61,6 +62,7 @@ export interface PreviewObject {
|
|
|
61
62
|
};
|
|
62
63
|
code: string;
|
|
63
64
|
html?: string;
|
|
65
|
+
usage?: string;
|
|
64
66
|
format: string;
|
|
65
67
|
variant?: Record<string, string>;
|
|
66
68
|
options?: {
|
package/docs/cli.md
CHANGED
|
@@ -14,9 +14,9 @@ The CLI will allow you to build the various kinds of configurations that you
|
|
|
14
14
|
will need for interacting with Figma. Handoff has sane configuration defaults
|
|
15
15
|
but the various kinds of configurations can be tailored or extended.
|
|
16
16
|
|
|
17
|
-
Handoff has 4 configuration
|
|
17
|
+
Handoff has 4 configuration areas -
|
|
18
18
|
|
|
19
|
-
`handoff.config
|
|
19
|
+
`handoff.config.*` - Defines the general handoff configuration. Supported files are `handoff.config.ts`, `handoff.config.js`, `handoff.config.cjs`, and `handoff.config.json` (resolved in that precedence order).
|
|
20
20
|
`pages` - Markdown files that will create or customize pages in the documentation
|
|
21
21
|
app
|
|
22
22
|
`exportables` - JSON schemas for each component in your figma file that you
|
|
@@ -28,7 +28,7 @@ The CLI exposes two ways to manage the config - `make` and `eject`.
|
|
|
28
28
|
|
|
29
29
|
__Eject__ commands will take the default configuration and eject them into
|
|
30
30
|
the current working directory. If you customize these configurations, then run
|
|
31
|
-
handoff commands in that directory, these configs will be executed.
|
|
31
|
+
handoff commands in that directory, these configs will be executed. `eject:config` now prompts for TypeScript or JavaScript config format.
|
|
32
32
|
__Make__ commands will generate a boilerplate configuration in the current
|
|
33
33
|
working directory. This is useful for extending handoff for different components
|
|
34
34
|
or integrations.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "handoff-app",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0-0",
|
|
4
4
|
"description": "Automated documentation toolchain for building client side documentation from figma",
|
|
5
5
|
"author ": {
|
|
6
6
|
"name": "Convertiv",
|
|
@@ -105,6 +105,8 @@
|
|
|
105
105
|
"prettier": "^3.4.2",
|
|
106
106
|
"react": "^19.1.0",
|
|
107
107
|
"react-dom": "^19.1.0",
|
|
108
|
+
"react-element-to-jsx-string": "^17.0.1",
|
|
109
|
+
"react-is": "^19.1.0",
|
|
108
110
|
"react-markdown": "^10.1.0",
|
|
109
111
|
"react-scroll": "^1.8.9",
|
|
110
112
|
"react-syntax-highlighter": "^16.1.0",
|
|
@@ -8,8 +8,8 @@ export interface PageSliceResolverProps {
|
|
|
8
8
|
preview: PreviewObject;
|
|
9
9
|
title: string;
|
|
10
10
|
height?: string;
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
currentPreviewUrl?: string;
|
|
12
|
+
onPreviewChange?: (previewUrl: string) => void;
|
|
13
13
|
bestPracticesCard?: boolean;
|
|
14
14
|
codeHighlight?: boolean;
|
|
15
15
|
properties?: boolean;
|
|
@@ -21,8 +21,8 @@ export const PageSliceResolver: React.FC<PageSliceResolverProps> = ({
|
|
|
21
21
|
preview,
|
|
22
22
|
title,
|
|
23
23
|
height,
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
currentPreviewUrl,
|
|
25
|
+
onPreviewChange,
|
|
26
26
|
bestPracticesCard = true,
|
|
27
27
|
codeHighlight = true,
|
|
28
28
|
properties = true,
|
|
@@ -39,8 +39,8 @@ export const PageSliceResolver: React.FC<PageSliceResolverProps> = ({
|
|
|
39
39
|
preview={preview}
|
|
40
40
|
title={title}
|
|
41
41
|
height={height}
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
currentPreviewUrl={currentPreviewUrl}
|
|
43
|
+
onPreviewChange={onPreviewChange}
|
|
44
44
|
showPreview={slice.showPreview}
|
|
45
45
|
showCodeHighlight={slice.showCodeHighlight}
|
|
46
46
|
defaultHeight={slice.defaultHeight}
|
|
@@ -55,8 +55,8 @@ export const ComponentDisplay: React.FC<{
|
|
|
55
55
|
component: PreviewObject | undefined;
|
|
56
56
|
defaultHeight?: string | undefined;
|
|
57
57
|
title?: string;
|
|
58
|
-
|
|
59
|
-
}> = ({ component, defaultHeight, title,
|
|
58
|
+
onPreviewChange?: (previewUrl: string) => void;
|
|
59
|
+
}> = ({ component, defaultHeight, title, onPreviewChange }) => {
|
|
60
60
|
const context = usePreviewContext();
|
|
61
61
|
const ref = React.useRef<HTMLIFrameElement>(null);
|
|
62
62
|
const [height, setHeight] = React.useState('100px');
|
|
@@ -131,11 +131,10 @@ export const ComponentDisplay: React.FC<{
|
|
|
131
131
|
if (keys.length === 0) {
|
|
132
132
|
return;
|
|
133
133
|
}
|
|
134
|
-
|
|
135
|
-
setPreviewUrl(
|
|
136
|
-
!!onValuesChange && onValuesChange(component.previews[keys[0]].values);
|
|
134
|
+
const firstPreview = component.previews[keys[0]];
|
|
135
|
+
setPreviewUrl(firstPreview.url);
|
|
137
136
|
}
|
|
138
|
-
}, [component
|
|
137
|
+
}, [component]);
|
|
139
138
|
|
|
140
139
|
React.useEffect(() => {
|
|
141
140
|
if (!component) return;
|
|
@@ -147,11 +146,17 @@ export const ComponentDisplay: React.FC<{
|
|
|
147
146
|
|
|
148
147
|
if (!!previewFilterResult && previewFilterResult.length > 0) {
|
|
149
148
|
setPreviewUrl(previewFilterResult[0].url);
|
|
150
|
-
!!onValuesChange && onValuesChange(previewFilterResult[0].values);
|
|
151
149
|
} else {
|
|
152
150
|
setPreviewUrl(null);
|
|
153
151
|
}
|
|
154
|
-
}, [context.variantFilter, component
|
|
152
|
+
}, [context.variantFilter, component]);
|
|
153
|
+
|
|
154
|
+
React.useEffect(() => {
|
|
155
|
+
if (!component?.previews || !previewUrl) return;
|
|
156
|
+
const selectedPreview = Object.values(component.previews).find((preview) => preview.url === previewUrl);
|
|
157
|
+
if (!selectedPreview) return;
|
|
158
|
+
!!onPreviewChange && onPreviewChange(selectedPreview.url);
|
|
159
|
+
}, [component?.previews, onPreviewChange, previewUrl]);
|
|
155
160
|
|
|
156
161
|
const { reloadCounter } = useContext(HotReloadContext);
|
|
157
162
|
|
|
@@ -376,7 +381,7 @@ export const ComponentPreview: React.FC<{
|
|
|
376
381
|
const context = usePreviewContext();
|
|
377
382
|
const [loaded, setLoaded] = React.useState(false);
|
|
378
383
|
const [preview, setPreview] = React.useState<PreviewObject | undefined>(defaultPreview);
|
|
379
|
-
const [
|
|
384
|
+
const [currentPreviewUrl, setCurrentPreviewUrl] = React.useState<string | undefined>();
|
|
380
385
|
React.useEffect(() => {
|
|
381
386
|
if (context.preview) {
|
|
382
387
|
setPreview(context.preview);
|
|
@@ -409,9 +414,9 @@ export const ComponentPreview: React.FC<{
|
|
|
409
414
|
preview={preview}
|
|
410
415
|
title={title}
|
|
411
416
|
height={height}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
417
|
+
currentPreviewUrl={currentPreviewUrl}
|
|
418
|
+
onPreviewChange={(previewUrl) => {
|
|
419
|
+
setCurrentPreviewUrl(previewUrl);
|
|
415
420
|
}}
|
|
416
421
|
bestPracticesCard={bestPracticesCard}
|
|
417
422
|
codeHighlight={codeHighlight}
|
|
@@ -8,8 +8,8 @@ export interface ComponentDisplaySliceProps {
|
|
|
8
8
|
preview: PreviewObject;
|
|
9
9
|
title: string;
|
|
10
10
|
height?: string;
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
currentPreviewUrl?: string;
|
|
12
|
+
onPreviewChange?: (previewUrl: string) => void;
|
|
13
13
|
showPreview?: boolean;
|
|
14
14
|
showCodeHighlight?: boolean;
|
|
15
15
|
defaultHeight?: string;
|
|
@@ -33,8 +33,8 @@ const ComponentDisplaySlice: React.FC<ComponentDisplaySliceProps> = ({
|
|
|
33
33
|
preview,
|
|
34
34
|
title,
|
|
35
35
|
height,
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
currentPreviewUrl,
|
|
37
|
+
onPreviewChange,
|
|
38
38
|
showPreview = true,
|
|
39
39
|
showCodeHighlight,
|
|
40
40
|
defaultHeight,
|
|
@@ -64,12 +64,22 @@ const ComponentDisplaySlice: React.FC<ComponentDisplaySliceProps> = ({
|
|
|
64
64
|
return (
|
|
65
65
|
<div id={filteredPreview.id}>
|
|
66
66
|
{showPreview && (
|
|
67
|
-
<ComponentDisplay
|
|
67
|
+
<ComponentDisplay
|
|
68
|
+
title={title}
|
|
69
|
+
component={filteredPreview}
|
|
70
|
+
defaultHeight={finalDefaultHeight}
|
|
71
|
+
onPreviewChange={onPreviewChange}
|
|
72
|
+
/>
|
|
68
73
|
)}
|
|
69
74
|
{finalShowCodeHighlight && (
|
|
70
75
|
<>
|
|
71
76
|
<a id="code-highlight" />
|
|
72
|
-
<CodeHighlight
|
|
77
|
+
<CodeHighlight
|
|
78
|
+
title={title}
|
|
79
|
+
data={filteredPreview}
|
|
80
|
+
collapsible={true}
|
|
81
|
+
currentPreviewUrl={currentPreviewUrl}
|
|
82
|
+
/>
|
|
73
83
|
</>
|
|
74
84
|
)}
|
|
75
85
|
</div>
|