handoff-app 1.0.4 → 1.0.5
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/Changelog.md +6 -0
- package/README.md +93 -57
- package/dist/cli.js +1 -1
- package/dist/commands/index.d.ts +1 -1
- package/dist/commands/index.js +2 -0
- package/dist/commands/init/index.d.ts +5 -0
- package/dist/commands/init/index.js +26 -0
- package/dist/create.d.ts +5 -0
- package/dist/create.js +293 -0
- package/package.json +1 -1
- package/src/cli.ts +1 -1
- package/src/commands/index.ts +2 -0
- package/src/commands/init/index.ts +17 -0
- package/src/create.ts +302 -0
- package/templates/default/README.md.tpl +15 -0
- package/templates/default/components/button/Button.tsx.tpl +92 -0
- package/templates/default/components/button/button.js.tpl +62 -0
- package/templates/default/env.tpl +2 -0
- package/templates/default/gitignore.tpl +7 -0
- package/templates/default/handoff.config.js.tpl +181 -0
- package/templates/default/handoff.config.with-examples.js.tpl +182 -0
- package/templates/default/package.json.tpl +20 -0
- package/templates/default/tsconfig.json.tpl +24 -0
package/src/create.ts
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { spawn } from 'child_process';
|
|
4
|
+
import * as fs from 'fs';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Get the current handoff-app version from package.json
|
|
9
|
+
*/
|
|
10
|
+
const getVersion = (): string => {
|
|
11
|
+
try {
|
|
12
|
+
const packageJsonPath = path.resolve(__dirname, '../package.json');
|
|
13
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
14
|
+
return packageJson.version || 'unknown';
|
|
15
|
+
} catch {
|
|
16
|
+
return 'unknown';
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
interface CreateOptions {
|
|
21
|
+
projectName: string;
|
|
22
|
+
figmaProjectId: string;
|
|
23
|
+
figmaAccessToken: string;
|
|
24
|
+
[key: string]: string; // Allow dynamic access for template replacement
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
class CreateError extends Error {
|
|
28
|
+
constructor(message: string) {
|
|
29
|
+
super(message);
|
|
30
|
+
this.name = 'CreateError';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Check if directory exists
|
|
36
|
+
*/
|
|
37
|
+
const directoryExists = (dirPath: string): boolean => {
|
|
38
|
+
try {
|
|
39
|
+
return fs.existsSync(dirPath);
|
|
40
|
+
} catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Create directory recursively
|
|
47
|
+
*/
|
|
48
|
+
const createDirectory = (dirPath: string): void => {
|
|
49
|
+
try {
|
|
50
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
51
|
+
} catch (error) {
|
|
52
|
+
throw new CreateError(`Failed to create directory: ${error}`);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
interface TemplateConfig {
|
|
57
|
+
source: string;
|
|
58
|
+
destination: string;
|
|
59
|
+
isExample?: boolean; // Flag to indicate if this is an example file
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get mandatory templates based on whether examples are included
|
|
64
|
+
* Uses different handoff config templates for blank vs. with-examples projects
|
|
65
|
+
*/
|
|
66
|
+
const getMandatoryTemplates = (includeExamples: boolean): TemplateConfig[] => [
|
|
67
|
+
{ source: 'package.json.tpl', destination: 'package.json' },
|
|
68
|
+
{
|
|
69
|
+
source: includeExamples ? 'handoff.config.with-examples.js.tpl' : 'handoff.config.js.tpl',
|
|
70
|
+
destination: 'handoff.config.js'
|
|
71
|
+
},
|
|
72
|
+
{ source: 'tsconfig.json.tpl', destination: 'tsconfig.json' },
|
|
73
|
+
{ source: 'gitignore.tpl', destination: '.gitignore' },
|
|
74
|
+
{ source: 'env.tpl', destination: '.env' },
|
|
75
|
+
{ source: 'README.md.tpl', destination: 'README.md' },
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
// Example component files (only included when user opts for examples)
|
|
79
|
+
const exampleTemplates: TemplateConfig[] = [
|
|
80
|
+
{ source: 'components/button/Button.tsx.tpl', destination: 'components/button/Button.tsx', isExample: true },
|
|
81
|
+
{ source: 'components/button/button.js.tpl', destination: 'components/button/button.js', isExample: true },
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Render a template string by replacing placeholders with values
|
|
86
|
+
*/
|
|
87
|
+
const renderTemplate = (content: string, data: CreateOptions): string => {
|
|
88
|
+
return content.replace(/\{\{(\w+)\}\}/g, (_, key) => {
|
|
89
|
+
if (!(key in data)) {
|
|
90
|
+
console.warn(chalk.yellow(`Warning: Unknown template placeholder '{{${key}}}'`));
|
|
91
|
+
}
|
|
92
|
+
return data[key] || '';
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Execute npm install
|
|
98
|
+
*/
|
|
99
|
+
const runNpmInstall = async (projectPath: string): Promise<void> => {
|
|
100
|
+
const spinner = p.spinner();
|
|
101
|
+
spinner.start('Installing dependencies...');
|
|
102
|
+
|
|
103
|
+
return new Promise((resolve, reject) => {
|
|
104
|
+
const npmProcess = spawn('npm', ['install'], {
|
|
105
|
+
cwd: projectPath,
|
|
106
|
+
stdio: 'pipe',
|
|
107
|
+
shell: true,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
let stderr = '';
|
|
111
|
+
|
|
112
|
+
npmProcess.stderr?.on('data', (data) => {
|
|
113
|
+
stderr += data.toString();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
npmProcess.on('close', (code) => {
|
|
117
|
+
if (code === 0) {
|
|
118
|
+
spinner.stop('Dependencies installed successfully');
|
|
119
|
+
resolve();
|
|
120
|
+
} else {
|
|
121
|
+
spinner.stop('Failed to install dependencies');
|
|
122
|
+
reject(new CreateError(`npm install failed: ${stderr}`));
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
npmProcess.on('error', (error) => {
|
|
127
|
+
spinner.stop('Failed to install dependencies');
|
|
128
|
+
reject(new CreateError(`Failed to run npm install: ${error.message}`));
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Validate project name
|
|
135
|
+
*/
|
|
136
|
+
const validateProjectName = (name: string): boolean => {
|
|
137
|
+
const validNameRegex = /^[a-zA-Z0-9-_]+$/;
|
|
138
|
+
return validNameRegex.test(name) && name.length > 0;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Main create function
|
|
143
|
+
*/
|
|
144
|
+
const create = async (): Promise<void> => {
|
|
145
|
+
const version = getVersion();
|
|
146
|
+
p.intro(chalk.bgCyan.black(` Handoff App Creator (v${version}) `));
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
// Get project name
|
|
150
|
+
const projectName = await p.text({
|
|
151
|
+
message: 'Enter project name:',
|
|
152
|
+
placeholder: 'my-handoff-project',
|
|
153
|
+
validate: (value) => {
|
|
154
|
+
if (!value.trim()) return 'Project name is required';
|
|
155
|
+
if (!validateProjectName(value)) {
|
|
156
|
+
return 'Invalid project name. Use only letters, numbers, hyphens, and underscores.';
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
if (p.isCancel(projectName)) {
|
|
162
|
+
p.cancel('Project creation cancelled.');
|
|
163
|
+
process.exit(0);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const projectPath = path.resolve(process.cwd(), projectName as string);
|
|
167
|
+
|
|
168
|
+
// Check if directory exists
|
|
169
|
+
if (directoryExists(projectPath)) {
|
|
170
|
+
const overwrite = await p.confirm({
|
|
171
|
+
message: `Directory "${projectName}" already exists. Do you want to overwrite it?`,
|
|
172
|
+
initialValue: false,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
if (p.isCancel(overwrite) || !overwrite) {
|
|
176
|
+
p.cancel('Project creation cancelled.');
|
|
177
|
+
process.exit(0);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Remove existing directory
|
|
181
|
+
p.log.info('Removing existing directory...');
|
|
182
|
+
fs.rmSync(projectPath, { recursive: true, force: true });
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Create project directory
|
|
186
|
+
p.log.info('Creating project directory...');
|
|
187
|
+
createDirectory(projectPath);
|
|
188
|
+
|
|
189
|
+
// Ask if user wants a blank project or one with examples
|
|
190
|
+
p.log.step(chalk.blue('Project Configuration'));
|
|
191
|
+
const projectType = await p.select({
|
|
192
|
+
message: 'What type of project would you like to create?',
|
|
193
|
+
options: [
|
|
194
|
+
{ value: 'with-examples', label: 'Project with sample components', hint: 'Includes example components to get started' },
|
|
195
|
+
{ value: 'blank', label: 'Blank project', hint: 'Only mandatory files, no examples' },
|
|
196
|
+
],
|
|
197
|
+
initialValue: 'with-examples',
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
if (p.isCancel(projectType)) {
|
|
201
|
+
p.cancel('Project creation cancelled.');
|
|
202
|
+
process.exit(0);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const includeExamples = projectType === 'with-examples';
|
|
206
|
+
|
|
207
|
+
// Get Figma project ID
|
|
208
|
+
p.log.step(chalk.blue('Figma Configuration'));
|
|
209
|
+
const figmaProjectId = await p.text({
|
|
210
|
+
message: 'Enter Figma project ID:',
|
|
211
|
+
placeholder: 'Optional - can be configured later',
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
if (p.isCancel(figmaProjectId)) {
|
|
215
|
+
p.cancel('Project creation cancelled.');
|
|
216
|
+
process.exit(0);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Get Figma access token
|
|
220
|
+
let figmaAccessToken: string | boolean | symbol = '';
|
|
221
|
+
|
|
222
|
+
if (figmaProjectId) {
|
|
223
|
+
figmaAccessToken = await p.password({
|
|
224
|
+
message: 'Enter Figma developer access token:',
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
if (p.isCancel(figmaAccessToken)) {
|
|
228
|
+
p.cancel('Project creation cancelled.');
|
|
229
|
+
process.exit(0);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const options: CreateOptions = {
|
|
234
|
+
projectName: projectName as string,
|
|
235
|
+
figmaProjectId: ((figmaProjectId as string) || '').trim(),
|
|
236
|
+
figmaAccessToken: ((figmaAccessToken as string) || '').trim(),
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
// Create files
|
|
240
|
+
const spinner = p.spinner();
|
|
241
|
+
spinner.start('Creating project files...');
|
|
242
|
+
|
|
243
|
+
const templateDir = path.resolve(__dirname, '../templates/default');
|
|
244
|
+
|
|
245
|
+
if (!fs.existsSync(templateDir)) {
|
|
246
|
+
throw new CreateError(`Template directory not found at ${templateDir}. This may indicate a packaging issue.`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Combine mandatory templates with example templates based on user choice
|
|
250
|
+
const templates = [
|
|
251
|
+
...getMandatoryTemplates(includeExamples),
|
|
252
|
+
...(includeExamples ? exampleTemplates : []),
|
|
253
|
+
];
|
|
254
|
+
|
|
255
|
+
try {
|
|
256
|
+
for (const template of templates) {
|
|
257
|
+
const templatePath = path.join(templateDir, template.source);
|
|
258
|
+
const destinationPath = path.join(projectPath, template.destination);
|
|
259
|
+
|
|
260
|
+
try {
|
|
261
|
+
const content = fs.readFileSync(templatePath, 'utf8');
|
|
262
|
+
const rendered = renderTemplate(content, options);
|
|
263
|
+
fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
|
|
264
|
+
fs.writeFileSync(destinationPath, rendered);
|
|
265
|
+
} catch (err) {
|
|
266
|
+
throw new CreateError(`Failed to create file ${template.destination}: ${err}`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
spinner.stop('Project files created');
|
|
270
|
+
} catch (err) {
|
|
271
|
+
spinner.stop('Failed to create project files');
|
|
272
|
+
throw err;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Install dependencies
|
|
276
|
+
await runNpmInstall(projectPath);
|
|
277
|
+
|
|
278
|
+
// Success message
|
|
279
|
+
const nextSteps = [`cd ${projectName}`];
|
|
280
|
+
if (figmaProjectId && figmaAccessToken) {
|
|
281
|
+
nextSteps.push('npm run fetch');
|
|
282
|
+
}
|
|
283
|
+
nextSteps.push('npm run start');
|
|
284
|
+
|
|
285
|
+
p.note(nextSteps.join('\n'), 'Next steps');
|
|
286
|
+
|
|
287
|
+
const outroMessage = includeExamples
|
|
288
|
+
? 'Project created successfully with sample components! First, fetch your Figma data (optional), then start the documentation site.'
|
|
289
|
+
: 'Blank project created successfully! First, fetch your Figma data, then start the documentation site.';
|
|
290
|
+
|
|
291
|
+
p.outro(chalk.green(outroMessage));
|
|
292
|
+
} catch (error) {
|
|
293
|
+
if (error instanceof CreateError) {
|
|
294
|
+
p.cancel(chalk.red(`Error: ${error.message}`));
|
|
295
|
+
} else {
|
|
296
|
+
p.cancel(chalk.red(`Unexpected error: ${error}`));
|
|
297
|
+
}
|
|
298
|
+
process.exit(1);
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
export default create;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# {{projectName}}
|
|
2
|
+
|
|
3
|
+
A design system documentation site powered by Handoff App.
|
|
4
|
+
|
|
5
|
+
## Getting Started
|
|
6
|
+
|
|
7
|
+
1. Configure your Figma credentials in `.env`
|
|
8
|
+
2. Run `npm run fetch` to pull design tokens
|
|
9
|
+
3. Run `npm run start` to launch the documentation site
|
|
10
|
+
|
|
11
|
+
## Scripts
|
|
12
|
+
|
|
13
|
+
- `npm run fetch` - Fetch design data from Figma
|
|
14
|
+
- `npm run start` - Start the documentation server
|
|
15
|
+
- `npm run build` - Build for production
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
|
|
3
|
+
interface ButtonProps {
|
|
4
|
+
type?: 'primary' | 'secondary';
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
className?: string;
|
|
8
|
+
showCounter?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const Button: React.FC<ButtonProps> = ({
|
|
12
|
+
type = 'primary',
|
|
13
|
+
children,
|
|
14
|
+
disabled = false,
|
|
15
|
+
className = '',
|
|
16
|
+
showCounter = true
|
|
17
|
+
}) => {
|
|
18
|
+
const [clickCount, setClickCount] = useState(0);
|
|
19
|
+
|
|
20
|
+
const baseColor = type === 'primary' ? '#4f46e5' : '#dc2626';
|
|
21
|
+
const hoverColor = type === 'primary' ? '#4338ca' : '#b91c1c';
|
|
22
|
+
|
|
23
|
+
const handleClick = () => {
|
|
24
|
+
if (!disabled) {
|
|
25
|
+
setClickCount(prev => prev + 1);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const variantStyle: React.CSSProperties = {
|
|
30
|
+
backgroundColor: baseColor,
|
|
31
|
+
color: '#ffffff',
|
|
32
|
+
border: 'none',
|
|
33
|
+
borderRadius: '100px',
|
|
34
|
+
fontSize: '1rem',
|
|
35
|
+
padding: '0.625rem 1rem',
|
|
36
|
+
outline: 'none',
|
|
37
|
+
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
38
|
+
opacity: disabled ? 0.6 : 1,
|
|
39
|
+
transition: 'all 0.15s ease-in-out',
|
|
40
|
+
fontWeight: 500,
|
|
41
|
+
display: 'inline-flex',
|
|
42
|
+
alignItems: 'center',
|
|
43
|
+
gap: '8px'
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const wrapperStyle: React.CSSProperties = {
|
|
47
|
+
padding: '8px 0'
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const counterBadgeStyle: React.CSSProperties = {
|
|
51
|
+
backgroundColor: 'rgba(255, 255, 255, 0.3)',
|
|
52
|
+
borderRadius: '12px',
|
|
53
|
+
padding: '2px 8px',
|
|
54
|
+
fontSize: '0.875rem',
|
|
55
|
+
fontWeight: 600,
|
|
56
|
+
minWidth: '24px',
|
|
57
|
+
textAlign: 'center'
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<div style={wrapperStyle}>
|
|
62
|
+
<button
|
|
63
|
+
type="button"
|
|
64
|
+
disabled={disabled}
|
|
65
|
+
style={variantStyle}
|
|
66
|
+
className={`btn btn-${type} ${className}`.trim()}
|
|
67
|
+
onClick={handleClick}
|
|
68
|
+
onMouseEnter={(e) => {
|
|
69
|
+
if (!disabled) {
|
|
70
|
+
e.currentTarget.style.backgroundColor = hoverColor;
|
|
71
|
+
e.currentTarget.style.transform = 'translateY(-1px)';
|
|
72
|
+
}
|
|
73
|
+
}}
|
|
74
|
+
onMouseLeave={(e) => {
|
|
75
|
+
if (!disabled) {
|
|
76
|
+
e.currentTarget.style.backgroundColor = baseColor;
|
|
77
|
+
e.currentTarget.style.transform = 'translateY(0)';
|
|
78
|
+
}
|
|
79
|
+
}}
|
|
80
|
+
>
|
|
81
|
+
<span>{children}</span>
|
|
82
|
+
{showCounter && (
|
|
83
|
+
<span style={counterBadgeStyle}>
|
|
84
|
+
{clickCount}
|
|
85
|
+
</span>
|
|
86
|
+
)}
|
|
87
|
+
</button>
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export default Button;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/** @type {import('handoff-app').Component} */
|
|
2
|
+
module.exports = {
|
|
3
|
+
title: "Button",
|
|
4
|
+
description: "Interactive buttons that trigger actions and allow users to complete tasks, such as submitting a form or confirming a choice. This component includes click tracking to demonstrate stateful component behavior.",
|
|
5
|
+
group: "Atomic Elements",
|
|
6
|
+
type: "element",
|
|
7
|
+
categories: [
|
|
8
|
+
"functionality",
|
|
9
|
+
"interactive"
|
|
10
|
+
],
|
|
11
|
+
entries: {
|
|
12
|
+
template: "Button.tsx",
|
|
13
|
+
// schema: "Schema.ts"
|
|
14
|
+
},
|
|
15
|
+
// Intentionally omit figmaComponentId so docs variants are not sourced from Figma.
|
|
16
|
+
previews: {
|
|
17
|
+
primary: {
|
|
18
|
+
title: "Primary Button",
|
|
19
|
+
values: {
|
|
20
|
+
type: "primary",
|
|
21
|
+
children: "Click me!",
|
|
22
|
+
showCounter: true
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
secondary: {
|
|
26
|
+
title: "Secondary Button",
|
|
27
|
+
values: {
|
|
28
|
+
type: "secondary",
|
|
29
|
+
children: "Click me too!",
|
|
30
|
+
showCounter: true
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
primaryDisabled: {
|
|
34
|
+
title: "Disabled State",
|
|
35
|
+
values: {
|
|
36
|
+
type: "primary",
|
|
37
|
+
children: "Disabled",
|
|
38
|
+
disabled: true,
|
|
39
|
+
showCounter: true
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
withoutCounter: {
|
|
43
|
+
title: "Without Counter",
|
|
44
|
+
values: {
|
|
45
|
+
type: "primary",
|
|
46
|
+
children: "Submit",
|
|
47
|
+
showCounter: false
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
should_do: [
|
|
52
|
+
"Keep one primary button per section so main action is clear.",
|
|
53
|
+
"Use the secondary variant for less important actions.",
|
|
54
|
+
"Provide clear, action-oriented labels that describe what will happen.",
|
|
55
|
+
"Show visual feedback on hover and click interactions."
|
|
56
|
+
],
|
|
57
|
+
should_not_do: [
|
|
58
|
+
"Do not use vague labels like Click here or OK when the action can be specific.",
|
|
59
|
+
"Do not mix primary and secondary styles inconsistently.",
|
|
60
|
+
"Do not disable buttons without providing alternative guidance or feedback."
|
|
61
|
+
]
|
|
62
|
+
};
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
/** @type {import('handoff-app').Config} */
|
|
4
|
+
module.exports = {
|
|
5
|
+
app: {
|
|
6
|
+
theme: "default",
|
|
7
|
+
title: "{{projectName}} Design System",
|
|
8
|
+
client: "{{projectName}}",
|
|
9
|
+
google_tag_manager: null,
|
|
10
|
+
attribution: true,
|
|
11
|
+
type_copy: "Almost before we knew it, we had left the ground.",
|
|
12
|
+
type_sort: [
|
|
13
|
+
"Heading 1",
|
|
14
|
+
"Heading 2",
|
|
15
|
+
"Heading 3",
|
|
16
|
+
"Heading 4",
|
|
17
|
+
"Heading 5",
|
|
18
|
+
"Heading 6",
|
|
19
|
+
"Paragraph",
|
|
20
|
+
"Subheading",
|
|
21
|
+
"Blockquote",
|
|
22
|
+
"Input Labels",
|
|
23
|
+
"Link"
|
|
24
|
+
],
|
|
25
|
+
color_sort: [
|
|
26
|
+
"primary",
|
|
27
|
+
"secondary",
|
|
28
|
+
"extra",
|
|
29
|
+
"system"
|
|
30
|
+
],
|
|
31
|
+
component_sort: [
|
|
32
|
+
"primary",
|
|
33
|
+
"secondary",
|
|
34
|
+
"transparent"
|
|
35
|
+
],
|
|
36
|
+
base_path: "",
|
|
37
|
+
breakpoints: {
|
|
38
|
+
sm: { size: 576, name: "Small" },
|
|
39
|
+
md: { size: 768, name: "Medium" },
|
|
40
|
+
lg: { size: 992, name: "Large" },
|
|
41
|
+
xl: { size: 1200, name: "Extra Large" }
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
// entries: {
|
|
46
|
+
// /**
|
|
47
|
+
// * Array of component paths to be included in the build
|
|
48
|
+
// * Add your component directories here
|
|
49
|
+
// */
|
|
50
|
+
// components: ["components/button"]
|
|
51
|
+
// },
|
|
52
|
+
|
|
53
|
+
hooks: {
|
|
54
|
+
/**
|
|
55
|
+
* Optional validation callback for components
|
|
56
|
+
* @param component - The component instance to validate
|
|
57
|
+
* @returns A record of validation results where keys are validation types and values are detailed validation results
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* validateComponent: async (component) => ({
|
|
61
|
+
* a11y: {
|
|
62
|
+
* description: 'Accessibility validation check',
|
|
63
|
+
* passed: true,
|
|
64
|
+
* messages: ['No accessibility issues found']
|
|
65
|
+
* },
|
|
66
|
+
* responsive: {
|
|
67
|
+
* description: 'Responsive design validation',
|
|
68
|
+
* passed: false,
|
|
69
|
+
* messages: ['Component breaks at mobile breakpoint']
|
|
70
|
+
* }
|
|
71
|
+
* })
|
|
72
|
+
*/
|
|
73
|
+
// validateComponent: async (component) => {
|
|
74
|
+
// // Add your custom validation logic here
|
|
75
|
+
// return {};
|
|
76
|
+
// },
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Optional hook to override the SSR build configuration used in the ssrRenderPlugin
|
|
80
|
+
* @param config - The default esbuild configuration
|
|
81
|
+
* @returns Modified esbuild configuration
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ssrBuildConfig: (config) => {
|
|
85
|
+
* // Modify the esbuild config as needed
|
|
86
|
+
* return config;
|
|
87
|
+
* }
|
|
88
|
+
*/
|
|
89
|
+
// ssrBuildConfig: (config) => {
|
|
90
|
+
// // Add your custom SSR build configuration here
|
|
91
|
+
// return config;
|
|
92
|
+
// },
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Optional hook to override the client-side build configuration used in the ssrRenderPlugin
|
|
96
|
+
* @param config - The default esbuild configuration
|
|
97
|
+
* @returns Modified esbuild configuration
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* clientBuildConfig: (config) => {
|
|
101
|
+
* // Modify the esbuild config as needed
|
|
102
|
+
* return config;
|
|
103
|
+
* }
|
|
104
|
+
*/
|
|
105
|
+
// clientBuildConfig: (config) => {
|
|
106
|
+
// // Add your custom client build configuration here
|
|
107
|
+
// return config;
|
|
108
|
+
// },
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Optional hook to specify which export property contains the schema
|
|
112
|
+
* @param exports - The module exports object containing the schema
|
|
113
|
+
* @returns The schema object from the exports
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* getSchemaFromExports: (exports) => exports.customSchema || exports.default
|
|
117
|
+
*/
|
|
118
|
+
// getSchemaFromExports: (exports) => {
|
|
119
|
+
// // Add your custom schema extraction logic here
|
|
120
|
+
// return exports.default;
|
|
121
|
+
// },
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Optional hook to transform the schema into properties
|
|
125
|
+
* @param schema - The schema object to transform
|
|
126
|
+
* @returns The transformed properties object
|
|
127
|
+
*/
|
|
128
|
+
// schemaToProperties: (schema) => {
|
|
129
|
+
// // Add your custom schema to properties transformation here
|
|
130
|
+
// return {};
|
|
131
|
+
// },
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Optional hook to override the JavaScript Vite configuration
|
|
135
|
+
* @param config - The default Vite configuration
|
|
136
|
+
* @returns Modified Vite configuration
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* jsBuildConfig: (config) => {
|
|
140
|
+
* // Modify the Vite config as needed
|
|
141
|
+
* return config;
|
|
142
|
+
* }
|
|
143
|
+
*/
|
|
144
|
+
// jsBuildConfig: (config) => {
|
|
145
|
+
// // Add your custom JavaScript build configuration here
|
|
146
|
+
// return config;
|
|
147
|
+
// },
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Optional hook to override the CSS Vite configuration
|
|
151
|
+
* @param config - The default Vite configuration
|
|
152
|
+
* @returns Modified Vite configuration
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* cssBuildConfig: (config) => {
|
|
156
|
+
* // Modify the Vite config as needed
|
|
157
|
+
* return config;
|
|
158
|
+
* }
|
|
159
|
+
*/
|
|
160
|
+
// cssBuildConfig: (config) => {
|
|
161
|
+
// // Add your custom CSS build configuration here
|
|
162
|
+
// return config;
|
|
163
|
+
// },
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Optional hook to override the HTML Vite configuration
|
|
167
|
+
* @param config - The default Vite configuration
|
|
168
|
+
* @returns Modified Vite configuration
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* htmlBuildConfig: (config) => {
|
|
172
|
+
* // Modify the Vite config as needed
|
|
173
|
+
* return config;
|
|
174
|
+
* }
|
|
175
|
+
*/
|
|
176
|
+
// htmlBuildConfig: (config) => {
|
|
177
|
+
// // Add your custom HTML build configuration here
|
|
178
|
+
// return config;
|
|
179
|
+
// },
|
|
180
|
+
},
|
|
181
|
+
};
|