handoff-app 1.1.3 → 1.2.0-1
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 +31 -12
- 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 +106 -0
- package/dist/app-builder/config-diff/types.d.ts +33 -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 +65 -79
- package/dist/app-builder/websocket.js +10 -4
- 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 +8 -0
- package/dist/config/normalizers/pattern.js +35 -0
- package/dist/config/runtime.d.ts +9 -3
- package/dist/config/runtime.js +260 -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 +8 -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 +61 -8
- package/dist/transformers/preview/component/css.d.ts +8 -0
- package/dist/transformers/preview/component/css.js +6 -4
- package/dist/transformers/preview/component/html.js +14 -5
- package/dist/transformers/preview/component/javascript.d.ts +0 -1
- package/dist/transformers/preview/component/javascript.js +25 -14
- 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 +11 -0
- package/dist/transformers/preview/pattern/api.js +79 -0
- package/dist/transformers/preview/pattern/builder.d.ts +17 -0
- package/dist/transformers/preview/pattern/builder.js +167 -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/transformers/utils/vite-logger.js +4 -4
- package/dist/transformers/vite-config.js +2 -0
- package/dist/types/config.d.ts +32 -7
- package/dist/types/preview.d.ts +2 -0
- package/dist/utils/duration.d.ts +2 -0
- package/dist/utils/duration.js +6 -0
- package/dist/utils/logger.d.ts +42 -5
- package/dist/utils/logger.js +135 -15
- 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 +346 -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 +34 -12
- 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 +80 -0
- package/src/app-builder/config-diff/types.ts +36 -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 +74 -80
- package/src/app-builder/websocket.ts +16 -5
- 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 +56 -0
- package/src/config/runtime.ts +317 -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 +12 -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 +82 -9
- package/src/transformers/preview/component/css.ts +6 -5
- package/src/transformers/preview/component/html.ts +18 -5
- package/src/transformers/preview/component/javascript.ts +29 -19
- package/src/transformers/preview/component/previews.ts +12 -0
- package/src/transformers/preview/pattern/api.ts +83 -0
- package/src/transformers/preview/pattern/builder.ts +192 -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/transformers/utils/vite-logger.ts +4 -4
- package/src/transformers/vite-config.ts +2 -0
- package/src/types/README.md +1 -1
- package/src/types/config.ts +33 -7
- package/src/types/preview.ts +2 -0
- package/src/utils/duration.ts +3 -0
- package/src/utils/logger.ts +143 -16
- 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
package/dist/utils/logger.js
CHANGED
|
@@ -4,43 +4,163 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.Logger = void 0;
|
|
7
|
+
const readline_1 = __importDefault(require("readline"));
|
|
7
8
|
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
/**
|
|
10
|
+
* Env (e.g. project `.env` via `dotenv/config`):
|
|
11
|
+
* - `HANDOFF_LOG_LEVEL` — `debug` | `info` | `warn` | `error` | `silent` (default: `info`).
|
|
12
|
+
* Controls minimum severity: at `warn`, info/success/debug lines are hidden; at `error`, only errors.
|
|
13
|
+
* - `HANDOFF_LOG_SCOPES` — comma-separated allowlist: `handoff`, `vite`, `next`. Defaults to all three if unset or empty.
|
|
14
|
+
*
|
|
15
|
+
* Programmatic `Logger.init({ debug: true })` (Handoff `debug` flag) forces debug-level verbosity unless overridden.
|
|
16
|
+
*/
|
|
8
17
|
class Logger {
|
|
9
18
|
static init(options) {
|
|
19
|
+
this.applyEnvConfig();
|
|
10
20
|
if ((options === null || options === void 0 ? void 0 : options.debug) !== undefined) {
|
|
11
21
|
this.debugMode = options.debug;
|
|
12
22
|
}
|
|
23
|
+
if ((options === null || options === void 0 ? void 0 : options.debug) === true) {
|
|
24
|
+
this.severityThreshold = 0;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** Re-read `process.env` (useful if env is loaded after first `init`). */
|
|
28
|
+
static applyEnvConfig() {
|
|
29
|
+
var _a, _b;
|
|
30
|
+
this.severityThreshold = 1;
|
|
31
|
+
const allScopes = ['handoff', 'vite', 'next'];
|
|
32
|
+
this.scopesFilter = new Set(allScopes);
|
|
33
|
+
const levelRaw = (_a = process.env.HANDOFF_LOG_LEVEL) === null || _a === void 0 ? void 0 : _a.trim().toLowerCase();
|
|
34
|
+
const levelMap = {
|
|
35
|
+
debug: 0,
|
|
36
|
+
info: 1,
|
|
37
|
+
warn: 2,
|
|
38
|
+
error: 3,
|
|
39
|
+
silent: 99,
|
|
40
|
+
};
|
|
41
|
+
if (levelRaw && levelRaw in levelMap) {
|
|
42
|
+
this.severityThreshold = levelMap[levelRaw];
|
|
43
|
+
}
|
|
44
|
+
const scopesRaw = (_b = process.env.HANDOFF_LOG_SCOPES) === null || _b === void 0 ? void 0 : _b.trim();
|
|
45
|
+
if (scopesRaw) {
|
|
46
|
+
const set = new Set();
|
|
47
|
+
for (const part of scopesRaw.split(',')) {
|
|
48
|
+
const s = part.trim().toLowerCase();
|
|
49
|
+
if (s === 'handoff' || s === 'vite' || s === 'next') {
|
|
50
|
+
set.add(s);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (set.size > 0) {
|
|
54
|
+
this.scopesFilter = set;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
static scopeEnabled(scope) {
|
|
59
|
+
return this.scopesFilter.has(scope);
|
|
60
|
+
}
|
|
61
|
+
/** Message severities: debug=0, info-tier=1, warn=2, error=3 */
|
|
62
|
+
static shouldEmit(severity) {
|
|
63
|
+
return severity >= this.severityThreshold;
|
|
13
64
|
}
|
|
14
65
|
static getTimestamp() {
|
|
15
66
|
const now = new Date();
|
|
16
67
|
return chalk_1.default.gray(`[${now.toISOString()}]`);
|
|
17
68
|
}
|
|
18
|
-
|
|
19
|
-
|
|
69
|
+
/** Short label shown after the timestamp so you can scan for handoff vs vite vs next. */
|
|
70
|
+
static scopeTag(scope) {
|
|
71
|
+
const tags = {
|
|
72
|
+
handoff: chalk_1.default.blue.bold('[handoff]'),
|
|
73
|
+
vite: chalk_1.default.magenta.bold('[vite]'),
|
|
74
|
+
next: chalk_1.default.green.bold('[next]'),
|
|
75
|
+
};
|
|
76
|
+
return tags[scope];
|
|
20
77
|
}
|
|
21
|
-
static
|
|
22
|
-
|
|
78
|
+
static basePrefix(scope) {
|
|
79
|
+
return `${this.getTimestamp()} ${this.scopeTag(scope)}`;
|
|
23
80
|
}
|
|
24
|
-
static
|
|
25
|
-
|
|
81
|
+
static log(message, scope = 'handoff') {
|
|
82
|
+
if (!this.scopeEnabled(scope) || !this.shouldEmit(1))
|
|
83
|
+
return;
|
|
84
|
+
console.log(`${this.basePrefix(scope)} ${message}`);
|
|
26
85
|
}
|
|
27
|
-
|
|
28
|
-
|
|
86
|
+
/**
|
|
87
|
+
* Vite often passes strings that already contain ANSI; avoid wrapping those in chalk.cyan so colors stay correct.
|
|
88
|
+
*/
|
|
89
|
+
static info(message, scope = 'handoff') {
|
|
90
|
+
if (!this.scopeEnabled(scope) || !this.shouldEmit(1))
|
|
91
|
+
return;
|
|
92
|
+
const body = scope === 'vite' ? message : chalk_1.default.cyan(message);
|
|
93
|
+
console.log(`${this.basePrefix(scope)} ${body}`);
|
|
29
94
|
}
|
|
30
|
-
static
|
|
31
|
-
|
|
95
|
+
static success(message, scope = 'handoff') {
|
|
96
|
+
if (!this.scopeEnabled(scope) || !this.shouldEmit(1))
|
|
97
|
+
return;
|
|
98
|
+
console.log(`${this.basePrefix(scope)} ${chalk_1.default.green(message)}`);
|
|
99
|
+
}
|
|
100
|
+
static warn(message, scope = 'handoff') {
|
|
101
|
+
if (!this.scopeEnabled(scope) || !this.shouldEmit(2))
|
|
102
|
+
return;
|
|
103
|
+
const body = scope === 'vite' ? message : chalk_1.default.yellow(message);
|
|
104
|
+
console.warn(`${this.basePrefix(scope)} ${body}`);
|
|
105
|
+
}
|
|
106
|
+
static error(message, error, scope = 'handoff') {
|
|
107
|
+
if (!this.scopeEnabled(scope) || !this.shouldEmit(3))
|
|
108
|
+
return;
|
|
109
|
+
const body = scope === 'vite' ? message : chalk_1.default.red(message);
|
|
110
|
+
console.error(`${this.basePrefix(scope)} ${body}`);
|
|
32
111
|
if (error) {
|
|
33
112
|
console.error(error);
|
|
34
113
|
}
|
|
35
114
|
}
|
|
36
115
|
static debug(message, data) {
|
|
37
|
-
if (this.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
116
|
+
if (this.severityThreshold >= 99)
|
|
117
|
+
return;
|
|
118
|
+
if (!this.scopeEnabled('handoff'))
|
|
119
|
+
return;
|
|
120
|
+
const allowDebug = this.debugMode || this.severityThreshold === 0;
|
|
121
|
+
if (!allowDebug)
|
|
122
|
+
return;
|
|
123
|
+
console.log(`${this.basePrefix('handoff')} ${chalk_1.default.gray(`[DEBUG] ${message}`)}`);
|
|
124
|
+
if (data) {
|
|
125
|
+
console.log(data);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Log one line from a child process with timestamp + scope tag. Child ANSI is preserved (no extra chalk on the line).
|
|
130
|
+
*/
|
|
131
|
+
static childProcessLine(line, scope = 'next') {
|
|
132
|
+
if (!this.scopeEnabled(scope) || !this.shouldEmit(1))
|
|
133
|
+
return;
|
|
134
|
+
console.log(`${this.basePrefix(scope)} ${line}`);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Pipe stdout/stderr from a spawned child through {@link childProcessLine}. Use with
|
|
138
|
+
* `stdio: ['inherit' | 'ignore', 'pipe', 'pipe']`.
|
|
139
|
+
*/
|
|
140
|
+
static pipeChildStreams(stdout, stderr, scope = 'next') {
|
|
141
|
+
for (const stream of [stdout, stderr]) {
|
|
142
|
+
if (!stream)
|
|
143
|
+
continue;
|
|
144
|
+
const rl = readline_1.default.createInterface({ input: stream, crlfDelay: Infinity });
|
|
145
|
+
rl.on('line', (line) => {
|
|
146
|
+
this.childProcessLine(line, scope);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/** Forward a completed child-process buffer (e.g. from `spawn.sync` with stdio pipe) line-by-line. */
|
|
151
|
+
static childProcessBuffer(buffer, scope = 'next') {
|
|
152
|
+
if (!(buffer === null || buffer === void 0 ? void 0 : buffer.length))
|
|
153
|
+
return;
|
|
154
|
+
const text = buffer.toString('utf8');
|
|
155
|
+
for (const line of text.split(/\r?\n/)) {
|
|
156
|
+
this.childProcessLine(line, scope);
|
|
42
157
|
}
|
|
43
158
|
}
|
|
44
159
|
}
|
|
45
160
|
exports.Logger = Logger;
|
|
161
|
+
/** From Handoff constructor; enables debug lines together with env. */
|
|
46
162
|
Logger.debugMode = false;
|
|
163
|
+
/** Minimum message severity to print: 0=debug, 1=info, 2=warn, 3=error. `silent` uses 99. */
|
|
164
|
+
Logger.severityThreshold = 1;
|
|
165
|
+
/** Enabled scopes; defaults to all of `handoff`, `vite`, `next`. */
|
|
166
|
+
Logger.scopesFilter = new Set(['handoff', 'vite', 'next']);
|
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.1
|
|
3
|
+
"version": "1.2.0-1",
|
|
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>
|
|
@@ -2,7 +2,7 @@ import { PreviewObject } from '@handoff/types/preview';
|
|
|
2
2
|
// @ts-ignore
|
|
3
3
|
import { CollapsibleTrigger } from '@radix-ui/react-collapsible';
|
|
4
4
|
import { Select } from '@radix-ui/react-select';
|
|
5
|
-
import { useEffect, useState } from 'react';
|
|
5
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
6
6
|
import handlebars from 'react-syntax-highlighter/dist/esm/languages/prism/handlebars';
|
|
7
7
|
import js from 'react-syntax-highlighter/dist/esm/languages/prism/javascript';
|
|
8
8
|
import json from 'react-syntax-highlighter/dist/esm/languages/prism/json';
|
|
@@ -47,8 +47,8 @@ export const CodeHighlight: React.FC<{
|
|
|
47
47
|
title?: string;
|
|
48
48
|
language?: string;
|
|
49
49
|
height?: string;
|
|
50
|
-
|
|
51
|
-
}> = ({ data, collapsible, type, title, dark, height,
|
|
50
|
+
currentPreviewUrl?: string;
|
|
51
|
+
}> = ({ data, collapsible, type, title, dark, height, currentPreviewUrl }) => {
|
|
52
52
|
const [isOpen, setIsOpen] = useState<boolean>(true);
|
|
53
53
|
|
|
54
54
|
if (!data) {
|
|
@@ -69,7 +69,7 @@ export const CodeHighlight: React.FC<{
|
|
|
69
69
|
format: 'html',
|
|
70
70
|
};
|
|
71
71
|
} else if (typeof data === 'string') {
|
|
72
|
-
data =
|
|
72
|
+
data = {
|
|
73
73
|
id: '',
|
|
74
74
|
title: '',
|
|
75
75
|
categories: [],
|
|
@@ -91,13 +91,26 @@ export const CodeHighlight: React.FC<{
|
|
|
91
91
|
const metadataKeys = [
|
|
92
92
|
'id', 'preview', 'image', 'categories', 'title', 'format',
|
|
93
93
|
'description', 'type', 'group', 'tags', 'previews', 'properties',
|
|
94
|
-
'should_do', 'should_not_do', 'figma',
|
|
94
|
+
'should_do', 'should_not_do', 'figma', 'usage',
|
|
95
95
|
];
|
|
96
96
|
|
|
97
97
|
const states = Object.keys(data)
|
|
98
98
|
.filter((key) => !metadataKeys.includes(key) && !!(data as Record<string, any>)[key]);
|
|
99
|
-
const
|
|
99
|
+
const hasPerPreviewUsage =
|
|
100
|
+
typeof data === 'object' &&
|
|
101
|
+
!!data.previews &&
|
|
102
|
+
Object.values(data.previews).some((preview) => !!preview?.usage);
|
|
103
|
+
const hasUsageState = (typeof data === 'object' && !!data.usage) || hasPerPreviewUsage;
|
|
104
|
+
const selectableStates = hasUsageState && !states.includes('usage') ? ['usage', ...states] : states;
|
|
105
|
+
const preferredState = selectableStates.includes('usage') ? 'usage' : selectableStates[0];
|
|
106
|
+
const [activeState, setActiveState] = useState<string>(preferredState);
|
|
100
107
|
const [code, setCode] = useState<string>(data.html);
|
|
108
|
+
const stateResetKey = useMemo(() => {
|
|
109
|
+
if (typeof data === 'string') {
|
|
110
|
+
return `string:${type || 'html'}`;
|
|
111
|
+
}
|
|
112
|
+
return `${data.id || ''}|${data.title || ''}|${data.format || ''}|${Object.keys(data.previews || {}).join('|')}`;
|
|
113
|
+
}, [data, type]);
|
|
101
114
|
const theme = dark ? oneDark : oneLight;
|
|
102
115
|
theme['pre[class*="language-"]'].overflow = 'auto';
|
|
103
116
|
theme['pre[class*="language-"]'].maxHeight = height ?? '450px';
|
|
@@ -118,6 +131,7 @@ export const CodeHighlight: React.FC<{
|
|
|
118
131
|
sass: 'SASS',
|
|
119
132
|
scss: 'SCSS',
|
|
120
133
|
sharedStyles: 'Shared CSS',
|
|
134
|
+
usage: 'Usage',
|
|
121
135
|
json: 'JSON',
|
|
122
136
|
markdown: 'Markdown',
|
|
123
137
|
};
|
|
@@ -167,6 +181,7 @@ export const CodeHighlight: React.FC<{
|
|
|
167
181
|
css: 'css',
|
|
168
182
|
sass: 'scss',
|
|
169
183
|
sharedStyles: 'css',
|
|
184
|
+
usage: 'tsx',
|
|
170
185
|
json: 'json',
|
|
171
186
|
handlebars: 'handlebars',
|
|
172
187
|
hbs: 'handlebars',
|
|
@@ -195,12 +210,43 @@ export const CodeHighlight: React.FC<{
|
|
|
195
210
|
return jsxPatterns.some(pattern => pattern.test(code));
|
|
196
211
|
};
|
|
197
212
|
|
|
213
|
+
const resolveUsageCode = (): string => {
|
|
214
|
+
if (typeof data !== 'object') {
|
|
215
|
+
return '';
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (currentPreviewUrl && data.previews) {
|
|
219
|
+
const activePreview = Object.values(data.previews).find((preview) => preview.url === currentPreviewUrl);
|
|
220
|
+
if (activePreview?.usage) {
|
|
221
|
+
return activePreview.usage;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const firstPreviewWithUsage = data.previews ? Object.values(data.previews).find((preview) => !!preview.usage) : undefined;
|
|
226
|
+
if (firstPreviewWithUsage?.usage) {
|
|
227
|
+
return firstPreviewWithUsage.usage;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return data.usage || '';
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// Always default to usage for newly viewed component/code payloads.
|
|
234
|
+
// Users can still manually switch tabs after this initial selection.
|
|
235
|
+
useEffect(() => {
|
|
236
|
+
setActiveState(preferredState);
|
|
237
|
+
}, [preferredState, stateResetKey]);
|
|
238
|
+
|
|
198
239
|
useEffect(() => {
|
|
199
240
|
if (typeof data === 'string') {
|
|
200
241
|
setCode(data);
|
|
201
242
|
return;
|
|
202
243
|
}
|
|
203
244
|
|
|
245
|
+
if (activeState === 'usage') {
|
|
246
|
+
setCode(resolveUsageCode());
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
204
250
|
if (activeState === 'code' && 'code' in data && !!data.code) {
|
|
205
251
|
setCode(data.code);
|
|
206
252
|
return;
|
|
@@ -209,7 +255,7 @@ export const CodeHighlight: React.FC<{
|
|
|
209
255
|
if (activeState in data && !!(data as Record<string, any>)[activeState]) {
|
|
210
256
|
setCode((data as Record<string, any>)[activeState]);
|
|
211
257
|
}
|
|
212
|
-
}, [activeState,
|
|
258
|
+
}, [activeState, currentPreviewUrl, data]);
|
|
213
259
|
|
|
214
260
|
return (
|
|
215
261
|
<Collapsible id="code-samples" className="mt-4 space-y-2" style={{ maxWidth: '71vw' }} open={isOpen} onOpenChange={setIsOpen}>
|
|
@@ -219,14 +265,17 @@ export const CodeHighlight: React.FC<{
|
|
|
219
265
|
>
|
|
220
266
|
{title && <div>{title}</div>}
|
|
221
267
|
<div className="flex items-center gap-2">
|
|
222
|
-
{
|
|
268
|
+
{selectableStates.length > 1 && (
|
|
223
269
|
<Select
|
|
224
|
-
|
|
270
|
+
value={activeState}
|
|
225
271
|
onValueChange={(key) => {
|
|
226
272
|
setActiveState(key);
|
|
227
273
|
if (typeof data === 'string') {
|
|
228
274
|
setCode(data);
|
|
229
275
|
return;
|
|
276
|
+
} else if (key === 'usage') {
|
|
277
|
+
setCode(resolveUsageCode());
|
|
278
|
+
return;
|
|
230
279
|
} else {
|
|
231
280
|
setCode(data[key]);
|
|
232
281
|
}
|
|
@@ -236,8 +285,8 @@ export const CodeHighlight: React.FC<{
|
|
|
236
285
|
<SelectValue placeholder="Code View" />
|
|
237
286
|
</SelectTrigger>
|
|
238
287
|
<SelectContent>
|
|
239
|
-
{
|
|
240
|
-
.filter((value) => ['code', 'html', 'css', 'js', 'sass', 'sharedStyles'].includes(value))
|
|
288
|
+
{selectableStates
|
|
289
|
+
.filter((value) => ['usage', 'code', 'html', 'css', 'js', 'sass', 'sharedStyles'].includes(value))
|
|
241
290
|
.map((state) => {
|
|
242
291
|
return (
|
|
243
292
|
<SelectItem key={state} value={state}>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ComponentListObject, ComponentType } from '@handoff/transformers/preview/types';
|
|
1
|
+
import { ComponentListObject, ComponentType, PatternListObject } from '@handoff/transformers/preview/types';
|
|
2
2
|
import { ClientConfig, RuntimeConfig } from '@handoff/types/config';
|
|
3
3
|
import { ComponentDocumentationOptions, PreviewObject } from '@handoff/types/preview';
|
|
4
4
|
import * as fs from 'fs-extra';
|
|
@@ -85,7 +85,7 @@ export const knownPaths = [
|
|
|
85
85
|
'assets/logos',
|
|
86
86
|
'foundations',
|
|
87
87
|
'foundations/colors',
|
|
88
|
-
'foundations/icons',
|
|
88
|
+
'foundations/icons',
|
|
89
89
|
'foundations/effects',
|
|
90
90
|
'foundations/logos',
|
|
91
91
|
'foundations/logo',
|
|
@@ -98,6 +98,7 @@ export const knownPaths = [
|
|
|
98
98
|
'system/tokens/foundations/effects',
|
|
99
99
|
'system/tokens/foundations/typography',
|
|
100
100
|
'system/tokens/components',
|
|
101
|
+
'system/pattern',
|
|
101
102
|
];
|
|
102
103
|
|
|
103
104
|
/**
|
|
@@ -266,6 +267,16 @@ export const staticBuildMenu = () => {
|
|
|
266
267
|
menu: staticBuildTokensMenu(),
|
|
267
268
|
};
|
|
268
269
|
}
|
|
270
|
+
if (sub.patterns) {
|
|
271
|
+
const patternMenu = staticBuildPatternMenu();
|
|
272
|
+
if (patternMenu.length > 0) {
|
|
273
|
+
return {
|
|
274
|
+
title: sub.title || 'Patterns',
|
|
275
|
+
menu: patternMenu,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
return undefined;
|
|
279
|
+
}
|
|
269
280
|
if (sub.enabled !== false) {
|
|
270
281
|
return sub;
|
|
271
282
|
}
|
|
@@ -413,6 +424,64 @@ const staticBuildTokenMenu = () => {
|
|
|
413
424
|
return subSections;
|
|
414
425
|
};
|
|
415
426
|
|
|
427
|
+
/**
|
|
428
|
+
* Fetch patterns from the patterns.json API file
|
|
429
|
+
*/
|
|
430
|
+
export const fetchPatterns = (): { id: string; title: string; description: string; group: string }[] => {
|
|
431
|
+
const patternsFilePath = path.resolve(
|
|
432
|
+
process.env.HANDOFF_MODULE_PATH ?? '',
|
|
433
|
+
'.handoff',
|
|
434
|
+
`${process.env.HANDOFF_PROJECT_ID}`,
|
|
435
|
+
'public',
|
|
436
|
+
'api',
|
|
437
|
+
'patterns.json'
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
if (!fs.existsSync(patternsFilePath)) {
|
|
441
|
+
return [];
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
try {
|
|
445
|
+
const patternList = JSON.parse(
|
|
446
|
+
fs.readFileSync(patternsFilePath, 'utf-8')
|
|
447
|
+
) as PatternListObject[];
|
|
448
|
+
|
|
449
|
+
return patternList.map((p) => ({
|
|
450
|
+
id: p.id,
|
|
451
|
+
title: p.title || '',
|
|
452
|
+
description: p.description || '',
|
|
453
|
+
group: p.group || '',
|
|
454
|
+
}));
|
|
455
|
+
} catch {
|
|
456
|
+
return [];
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
const staticBuildPatternMenu = () => {
|
|
461
|
+
const basePath = buildBasePath();
|
|
462
|
+
const patterns = fetchPatterns();
|
|
463
|
+
|
|
464
|
+
if (patterns.length === 0) return [];
|
|
465
|
+
|
|
466
|
+
const grouped = groupBy(patterns, (p) => p.group ?? '');
|
|
467
|
+
let menu: { title: string; menu: { path: string; title: string }[] }[] = [];
|
|
468
|
+
|
|
469
|
+
Object.keys(grouped).forEach((group) => {
|
|
470
|
+
const menuGroup = { title: group || 'Uncategorized', menu: [] as { path: string; title: string }[] };
|
|
471
|
+
grouped[group].forEach((pattern) => {
|
|
472
|
+
menuGroup.menu.push({
|
|
473
|
+
path: `${basePath}system/pattern/${pattern.id}`,
|
|
474
|
+
title: pattern.title || startCase(pattern.id),
|
|
475
|
+
});
|
|
476
|
+
});
|
|
477
|
+
menuGroup.menu.sort((a, b) => a.title.localeCompare(b.title));
|
|
478
|
+
menu.push(menuGroup);
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
menu.sort((a, b) => a.title.localeCompare(b.title));
|
|
482
|
+
return menu;
|
|
483
|
+
};
|
|
484
|
+
|
|
416
485
|
/**
|
|
417
486
|
* Filter the menus by the current path
|
|
418
487
|
* @param menu
|