@zvoove/unity-ui 2.23.1 → 2.24.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zvoove/unity-ui",
3
- "version": "2.23.1",
3
+ "version": "2.24.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+ssh://git@github.com/zvoove-org/unity-ui.git"
@@ -20,8 +20,8 @@
20
20
  "./config": "./src/config.ts"
21
21
  },
22
22
  "bin": {
23
- "unity-ui": "./bin/cli.mjs",
24
- "unity-ui-skills": "./bin/generate-skills.mjs"
23
+ "unity-ui": "./dist/bin/cli.mjs",
24
+ "unity-ui-skills": "./dist/bin/generate-skills.mjs"
25
25
  },
26
26
  "type": "module",
27
27
  "files": [
@@ -31,10 +31,11 @@
31
31
  "!docs",
32
32
  "!**/*.mdx",
33
33
  "!**/*.md",
34
- "bin"
34
+ "dist/bin/templates"
35
35
  ],
36
36
  "scripts": {
37
- "build": "tsc && vite build",
37
+ "build": "tsc && vite build && npm run build:cli",
38
+ "build:cli": "esbuild bin/cli.mjs bin/generate-skills.mjs --bundle --platform=node --format=esm --banner:js='#!/usr/bin/env node' --outdir=dist/bin",
38
39
  "test": "vitest",
39
40
  "storybook": "cross-env VITE_CJS_IGNORE_WARNING=true storybook dev -p 6006",
40
41
  "build-storybook": "storybook build",
@@ -50,17 +51,19 @@
50
51
  },
51
52
  "devDependencies": {
52
53
  "@chromatic-com/storybook": "^5.0.1",
54
+ "commander": "^14.0.3",
55
+ "esbuild": "0.25.0",
53
56
  "@commitlint/cli": "^20.4.3",
54
57
  "@commitlint/config-conventional": "^20.5.0",
55
- "@eslint/compat": "^2.0.3",
58
+ "@eslint/compat": "^2.0.4",
56
59
  "@eslint/eslintrc": "^3.3.5",
57
60
  "@eslint/js": "^9.39.4",
58
61
  "@phosphor-icons/react": "^2.1.10",
59
- "@storybook/addon-a11y": "^10.3.3",
60
- "@storybook/addon-docs": "^10.3.3",
61
- "@storybook/addon-links": "^10.3.3",
62
- "@storybook/addon-themes": "^10.3.3",
63
- "@storybook/react-vite": "^10.3.3",
62
+ "@storybook/addon-a11y": "^10.3.5",
63
+ "@storybook/addon-docs": "^10.3.5",
64
+ "@storybook/addon-links": "^10.3.5",
65
+ "@storybook/addon-themes": "^10.3.5",
66
+ "@storybook/react-vite": "^10.3.5",
64
67
  "@storybook/testing-library": "^0.2.2",
65
68
  "@tailwindcss/postcss": "^4.2.1",
66
69
  "@tailwindcss/vite": "^4.2.2",
@@ -78,8 +81,7 @@
78
81
  "@vitejs/plugin-react": "^5.1.4",
79
82
  "@vitest/coverage-v8": "^4.0.18",
80
83
  "@vitest/ui": "^4.0.7",
81
- "chromatic": "^16.0.0",
82
- "commander": "^14.0.3",
84
+ "chromatic": "^16.3.0",
83
85
  "commitizen": "^4.3.1",
84
86
  "cross-env": "^10.1.0",
85
87
  "cz-conventional-changelog": "^3.3.0",
@@ -99,7 +101,7 @@
99
101
  "react-dom": "^19.2.4",
100
102
  "resize-observer-polyfill": "^1.5.1",
101
103
  "rollup-plugin-visualizer": "^7.0.1",
102
- "storybook": "^10.3.3",
104
+ "storybook": "^10.3.5",
103
105
  "tailwind-merge": "^3.5.0",
104
106
  "tailwind-variants": "^3.2.2",
105
107
  "tailwindcss": "^4.2.1",
package/bin/cli.mjs DELETED
@@ -1,49 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Unity UI — Unified CLI
5
- *
6
- * Usage:
7
- * npx unity-ui init Initialize unity-ui.config.mjs
8
- * npx unity-ui skills [-o .claude] Generate Agent Skills
9
- * npx unity-ui create <ComponentName> Scaffold a new component
10
- * npx unity-ui rules [-o .] Generate AI rules (.cursorrules + CLAUDE.md)
11
- * npx unity-ui --help Show all commands
12
- */
13
- import { runCreate } from './commands/create.mjs';
14
- import { runInit } from './commands/init.mjs';
15
- import { runRules } from './commands/rules.mjs';
16
- import { runSkills } from './commands/skills.mjs';
17
- import { Command } from 'commander';
18
-
19
- const program = new Command();
20
-
21
- program
22
- .name('unity-ui')
23
- .description('Unity UI Design System CLI')
24
- .version('1.0.0');
25
-
26
- program
27
- .command('init')
28
- .description('Initialize unity-ui.config.mjs with project settings')
29
- .action(() => runInit());
30
-
31
- program
32
- .command('skills')
33
- .description('Generate Agent Skills files from llms.txt')
34
- .option('-o, --output <dir>', 'Base directory for skills output')
35
- .action((opts) => runSkills(opts));
36
-
37
- program
38
- .command('create')
39
- .description('Scaffold a new component')
40
- .argument('<name>', 'Component name (PascalCase)')
41
- .action((name) => runCreate(name));
42
-
43
- program
44
- .command('rules')
45
- .description('Generate AI rules files (.cursorrules + CLAUDE.md)')
46
- .option('-o, --output <dir>', 'Output directory')
47
- .action((opts) => runRules(opts));
48
-
49
- program.parse();
@@ -1,68 +0,0 @@
1
- /**
2
- * Unity UI — Config Loader
3
- *
4
- * Looks for unity-ui.config.mjs in process.cwd().
5
- * If found, merges with defaults. If not, returns defaults.
6
- */
7
- import fs from 'fs';
8
- import path from 'path';
9
- import { pathToFileURL } from 'url';
10
-
11
- export const DEFAULTS = {
12
- components: {
13
- directory: 'src/components',
14
- indexFile: 'src/index.ts',
15
- },
16
- ai: {
17
- skills: {
18
- output: '.claude',
19
- },
20
- rules: {
21
- output: '.',
22
- targets: ['cursorrules', 'claude'],
23
- },
24
- },
25
- };
26
-
27
- function deepMerge(target, source) {
28
- const result = { ...target };
29
- for (const key of Object.keys(source)) {
30
- if (
31
- source[key] &&
32
- typeof source[key] === 'object' &&
33
- !Array.isArray(source[key]) &&
34
- target[key] &&
35
- typeof target[key] === 'object' &&
36
- !Array.isArray(target[key])
37
- ) {
38
- result[key] = deepMerge(target[key], source[key]);
39
- } else {
40
- result[key] = source[key];
41
- }
42
- }
43
- return result;
44
- }
45
-
46
- /**
47
- * Loads unity-ui.config.mjs from process.cwd() and merges with defaults.
48
- * Returns defaults if no config file is found.
49
- */
50
- export async function loadConfig() {
51
- const configPath = path.resolve(process.cwd(), 'unity-ui.config.mjs');
52
-
53
- if (!fs.existsSync(configPath)) {
54
- return DEFAULTS;
55
- }
56
-
57
- try {
58
- const configUrl = pathToFileURL(configPath).href;
59
- const mod = await import(configUrl);
60
- const userConfig = mod.default || {};
61
- return deepMerge(DEFAULTS, userConfig);
62
- } catch (err) {
63
- console.warn(
64
- `\x1b[33m⚠️ Failed to load unity-ui.config.mjs: ${err.message}\x1b[0m`
65
- );
66
- return DEFAULTS;
67
- }
68
- }
@@ -1,163 +0,0 @@
1
- /**
2
- * Unity UI — Component Scaffolder (core logic)
3
- *
4
- * Creates a new component folder with all required files
5
- * from templates.
6
- */
7
- import { loadConfig } from './config.mjs';
8
- import { execSync } from 'child_process';
9
- import fs from 'fs';
10
- import path from 'path';
11
- import { fileURLToPath } from 'url';
12
-
13
- const __filename = fileURLToPath(import.meta.url);
14
- const __dirname = path.dirname(__filename);
15
-
16
- const lowerFirstLetter = (str) => {
17
- if (!str) return '';
18
- return str.charAt(0).toLowerCase() + str.slice(1);
19
- };
20
-
21
- const checkComponentName = (componentName) => {
22
- const componentNameRegex = /^[A-Z][a-zA-Z]*$/;
23
-
24
- if (!componentNameRegex.test(componentName)) {
25
- console.error(
26
- '❌ Component name must start with an uppercase letter and contain only letters'
27
- );
28
- return false;
29
- }
30
-
31
- if (componentName.includes(' ')) {
32
- console.error('❌ Component name must not contain spaces');
33
- return false;
34
- }
35
-
36
- return true;
37
- };
38
-
39
- export async function runCreate(componentName) {
40
- if (!checkComponentName(componentName)) {
41
- process.exit(1);
42
- }
43
-
44
- const config = await loadConfig();
45
-
46
- const componentDir = path.join(
47
- process.cwd(),
48
- config.components.directory,
49
- componentName
50
- );
51
-
52
- const templatesDir = path.resolve(__dirname, '..', 'templates');
53
-
54
- const templateMap = {
55
- [`{name}.tsx`]: 'component.tsx',
56
- [`{name}.styled.ts`]: 'styled.ts',
57
- [`{name}.stories.tsx`]: 'stories.tsx',
58
- [`{name}.test.tsx`]: 'test.tsx',
59
- [`{name}.types.ts`]: 'types.ts',
60
- [`{name}.mdx`]: 'doc.mdx',
61
- [`index.ts`]: 'index.ts',
62
- };
63
-
64
- // Load template files
65
- const loadTemplate = (templateFile, name) => {
66
- const filePath = path.join(templatesDir, templateFile);
67
-
68
- if (!fs.existsSync(filePath)) {
69
- console.error(`❌ Template file not found: \x1b[33m${filePath}\x1b[0m`);
70
- return;
71
- }
72
-
73
- const content = fs.readFileSync(filePath, 'utf8');
74
-
75
- return content
76
- .replace(/__COMPONENT_NAME__/g, name)
77
- .replace(/__STYLES__FUNCTION__/g, lowerFirstLetter(name));
78
- };
79
-
80
- // Create component folder
81
- if (!fs.existsSync(componentDir)) {
82
- fs.mkdirSync(componentDir);
83
- console.log(`✅ Created folder: \x1b[34m${componentName}\x1b[0m`);
84
- } else {
85
- console.log(`⚠️ Folder \x1b[33m${componentName}\x1b[0m already exists`);
86
- }
87
-
88
- // Create files
89
- Object.entries(templateMap).forEach(([fileNamePattern, templateFile]) => {
90
- const fileName = fileNamePattern.replace('{name}', componentName);
91
- const fullPath = path.join(componentDir, fileName);
92
-
93
- if (!fs.existsSync(fullPath)) {
94
- const content = loadTemplate(templateFile, componentName);
95
- fs.writeFileSync(fullPath, content, 'utf8');
96
- console.log(`✅ Created file: \x1b[32m${fileName}\x1b[0m`);
97
- } else {
98
- console.log(`⚠️ File \x1b[33m${fileName}\x1b[0m already exists`);
99
- }
100
- });
101
-
102
- const indexFile = path.join(process.cwd(), config.components.indexFile);
103
- const exportLine = `export * from './components/${componentName}';`;
104
-
105
- // Add export to index.ts
106
- if (fs.existsSync(indexFile)) {
107
- const content = fs.readFileSync(indexFile, 'utf8');
108
- const lines = content.split('\n').filter((line) => line.trim());
109
-
110
- // Separate theme import from component exports
111
- const themeImport = lines.find((line) =>
112
- line.includes("import './theme.css'")
113
- );
114
- const componentExports = lines.filter(
115
- (line) => line !== themeImport && line.startsWith('export * from')
116
- );
117
-
118
- if (!componentExports.includes(exportLine)) {
119
- // Add new export and sort alphabetically
120
- componentExports.push(exportLine);
121
- const sortedExports = componentExports.sort((a, b) => {
122
- const getComponentName = (line) => {
123
- const match = line.match(/from '\.\/components\/([^']+)'/);
124
- return match ? match[1] : '';
125
- };
126
- return getComponentName(a).localeCompare(getComponentName(b));
127
- });
128
-
129
- const finalContent =
130
- [themeImport, '', ...sortedExports].filter(Boolean).join('\n') + '\n';
131
-
132
- const finalContentWithEmptyLine = finalContent.replace(
133
- /(import '\.\/theme\.css';\n)(\n*)/,
134
- '$1\n'
135
- );
136
-
137
- fs.writeFileSync(indexFile, finalContentWithEmptyLine, 'utf8');
138
- console.log(
139
- `✅ Added export to \x1b[34m./src/index.ts\x1b[0m (sorted alphabetically)`
140
- );
141
- } else {
142
- console.log(`⚠️ Export already exists in \x1b[33m./src/index.ts\x1b[0m`);
143
- }
144
- } else {
145
- console.log(`❌ \x1b[33m./src/index.ts\x1b[0m does not exist.`);
146
- }
147
-
148
- console.log(
149
- `🎉 Component \x1b[32m<${componentName} />\x1b[0m structure created successfully!`
150
- );
151
-
152
- // Format the newly created component folder
153
- try {
154
- execSync(
155
- `npx prettier --write "${componentDir}" --ignore-glob "**/*.md" --ignore-glob "**/*.mdx"`,
156
- { stdio: 'inherit' }
157
- );
158
-
159
- console.log(`✅ Prettier formatted: \x1b[32m<${componentName} />\x1b[0m`);
160
- } catch (err) {
161
- console.error(`❌ Prettier formatting failed:`, err);
162
- }
163
- }
@@ -1,158 +0,0 @@
1
- /**
2
- * Unity UI — Init Command
3
- *
4
- * Interactive questionnaire that generates unity-ui.config.mjs.
5
- */
6
- import { DEFAULTS } from './config.mjs';
7
- import fs from 'fs';
8
- import path from 'path';
9
- import readline from 'readline';
10
-
11
- function createRL() {
12
- return readline.createInterface({
13
- input: process.stdin,
14
- output: process.stdout,
15
- });
16
- }
17
-
18
- function ask(rl, question, defaultValue) {
19
- return new Promise((resolve) => {
20
- rl.question(
21
- ` \x1b[36m?\x1b[0m ${question} \x1b[90m(${defaultValue})\x1b[0m `,
22
- (answer) => {
23
- resolve(answer.trim() || defaultValue);
24
- }
25
- );
26
- });
27
- }
28
-
29
- function askChoice(rl, question, options, defaultValue) {
30
- const optionsList = options.map((o) => o.label).join(' / ');
31
- return new Promise((resolve) => {
32
- rl.question(
33
- ` \x1b[36m?\x1b[0m ${question} \x1b[90m(${defaultValue})\x1b[0m [${optionsList}] `,
34
- (answer) => {
35
- const trimmed = answer.trim().toLowerCase();
36
- const match = options.find(
37
- (o) => o.label.toLowerCase() === trimmed || o.alias === trimmed
38
- );
39
- if (match) {
40
- resolve(match.value);
41
- } else {
42
- // Default
43
- const defaultOption = options.find(
44
- (o) =>
45
- o.label.toLowerCase() === defaultValue.toLowerCase() ||
46
- o.alias === defaultValue.toLowerCase()
47
- );
48
- resolve(defaultOption ? defaultOption.value : options[0].value);
49
- }
50
- }
51
- );
52
- });
53
- }
54
-
55
- function generateConfigContent(config) {
56
- const targets = config.ai.rules.targets;
57
- const targetsStr = targets.map((t) => `'${t}'`).join(', ');
58
-
59
- return `// unity-ui.config.mjs — generated by \`npx unity-ui init\`
60
- import { defineConfig } from '@zvoove/unity-ui/config';
61
-
62
- export default defineConfig({
63
- components: {
64
- directory: '${config.components.directory}',
65
- indexFile: '${config.components.indexFile}',
66
- },
67
- ai: {
68
- skills: {
69
- output: '${config.ai.skills.output}',
70
- },
71
- rules: {
72
- output: '${config.ai.rules.output}',
73
- targets: [${targetsStr}],
74
- },
75
- },
76
- });
77
- `;
78
- }
79
-
80
- export async function runInit() {
81
- const configPath = path.resolve(process.cwd(), 'unity-ui.config.mjs');
82
-
83
- if (fs.existsSync(configPath)) {
84
- console.log(
85
- `\x1b[33m⚠️ unity-ui.config.mjs already exists. Delete it first to re-initialize.\x1b[0m`
86
- );
87
- return;
88
- }
89
-
90
- console.log(`\n \x1b[1m🔧 Unity UI — Project Setup\x1b[0m\n`);
91
-
92
- const rl = createRL();
93
-
94
- const componentsDir = await ask(
95
- rl,
96
- 'Where do your components live?',
97
- DEFAULTS.components.directory
98
- );
99
-
100
- const indexFile = await ask(
101
- rl,
102
- 'Where is your main index/barrel file?',
103
- DEFAULTS.components.indexFile
104
- );
105
-
106
- const rulesTargets = await askChoice(
107
- rl,
108
- 'Generate AI rules for which tools?',
109
- [
110
- { label: 'Both', alias: 'b', value: ['cursorrules', 'claude'] },
111
- { label: 'Claude', alias: 'c', value: ['claude'] },
112
- { label: 'Cursor', alias: 'u', value: ['cursorrules'] },
113
- { label: 'None', alias: 'n', value: [] },
114
- ],
115
- 'Both'
116
- );
117
-
118
- const skillsOutput = await ask(
119
- rl,
120
- 'Where should Agent Skills be generated?',
121
- DEFAULTS.ai.skills.output
122
- );
123
-
124
- const rulesOutput = await ask(
125
- rl,
126
- 'Where should rules files be generated?',
127
- DEFAULTS.ai.rules.output
128
- );
129
-
130
- rl.close();
131
-
132
- const config = {
133
- components: {
134
- directory: componentsDir,
135
- indexFile: indexFile,
136
- },
137
- ai: {
138
- skills: { output: skillsOutput },
139
- rules: { output: rulesOutput, targets: rulesTargets },
140
- },
141
- };
142
-
143
- const content = generateConfigContent(config);
144
- fs.writeFileSync(configPath, content, 'utf8');
145
-
146
- console.log(`\n \x1b[32m✅ Created unity-ui.config.mjs\x1b[0m\n`);
147
- console.log(` Components: ${config.components.directory}/`);
148
- console.log(` Index file: ${config.components.indexFile}`);
149
- console.log(` Skills dir: ${config.ai.skills.output}/skills/`);
150
- if (config.ai.rules.targets.length > 0) {
151
- console.log(
152
- ` Rules: ${config.ai.rules.targets.join(', ')} → ${config.ai.rules.output}/`
153
- );
154
- } else {
155
- console.log(` Rules: disabled`);
156
- }
157
- console.log('');
158
- }
@@ -1,100 +0,0 @@
1
- /**
2
- * Unity UI — AI Rules Generator
3
- *
4
- * Generates .cursorrules and CLAUDE.md files for consuming projects.
5
- * These files help AI coding assistants understand and correctly use
6
- * Unity UI components.
7
- */
8
- import { loadConfig } from './config.mjs';
9
- import { findLlmsTxt } from './skills.mjs';
10
- import fs from 'fs';
11
- import path from 'path';
12
-
13
- function generateRulesContent(llmsContent) {
14
- return `# Unity UI — AI Rules
15
-
16
- > Auto-generated by \`npx unity-ui rules\`. Do not edit manually.
17
- > Source: @zvoove/unity-ui llms.txt
18
-
19
- ## General Rules
20
-
21
- - Always import components from \`@zvoove/unity-ui\`
22
- - Never recreate components that exist in this library
23
- - Use responsive props where applicable (single value or breakpoint object)
24
- - Use design tokens from theme.css — never hardcode colors, spacing, or shadows
25
- - Use \`tv()\` from tailwind-variants for component styling — never inline conditional classes
26
- - Dark mode uses \`data-theme="dark"\` attribute, not \`className="dark"\`
27
- - Default/placeholder texts should be in German
28
- - Font (Source Sans 3) is bundled via \`@font-face\` in theme.css — no extra setup needed
29
-
30
- ## Setup
31
-
32
- \`\`\`bash
33
- npm install @zvoove/unity-ui
34
- \`\`\`
35
-
36
- \`\`\`css
37
- @import '@zvoove/unity-ui/theme.css';
38
- @import '@zvoove/unity-ui/unity-ui.css';
39
- \`\`\`
40
-
41
- ## Responsive Props
42
-
43
- Breakpoints: minimum (0px), mobile (320px), tablet (768px), laptop (1024px), desktop (1440px).
44
-
45
- \`\`\`tsx
46
- <Button size="md" />
47
- <Button size={{ mobile: 'sm', tablet: 'md', desktop: 'lg' }} />
48
- \`\`\`
49
-
50
- ## Component Reference
51
-
52
- ${llmsContent}
53
- `;
54
- }
55
-
56
- export async function runRules({ output: outputDir } = {}) {
57
- const config = await loadConfig();
58
- const resolvedDir = path.resolve(
59
- process.cwd(),
60
- outputDir || config.ai.rules.output
61
- );
62
- const targets = config.ai.rules.targets;
63
-
64
- const llmsPath = findLlmsTxt();
65
- if (!llmsPath) {
66
- console.error(
67
- 'Could not find llms.txt. Make sure @zvoove/unity-ui is installed and built.'
68
- );
69
- process.exit(1);
70
- }
71
-
72
- const llmsContent = fs.readFileSync(llmsPath, 'utf8');
73
- const rulesContent = generateRulesContent(llmsContent);
74
-
75
- fs.mkdirSync(resolvedDir, { recursive: true });
76
-
77
- if (targets.includes('cursorrules')) {
78
- const cursorRulesPath = path.join(resolvedDir, '.cursorrules');
79
- fs.writeFileSync(cursorRulesPath, rulesContent, 'utf8');
80
- console.log(
81
- `✅ Generated ${path.relative(process.cwd(), cursorRulesPath)}`
82
- );
83
- }
84
-
85
- if (targets.includes('claude')) {
86
- const claudeMdPath = path.join(resolvedDir, 'CLAUDE.md');
87
- fs.writeFileSync(claudeMdPath, rulesContent, 'utf8');
88
- console.log(`✅ Generated ${path.relative(process.cwd(), claudeMdPath)}`);
89
- }
90
-
91
- if (targets.length > 0) {
92
- console.log(
93
- `\n Your AI assistant now knows how to use Unity UI components.\n`
94
- );
95
- } else {
96
- console.log(
97
- `\n No rule targets configured. Set targets in unity-ui.config.mjs.\n`
98
- );
99
- }
100
- }
@@ -1,32 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * @deprecated Use `npx unity-ui skills` instead.
5
- *
6
- * This shim exists for backward compatibility only.
7
- */
8
- import { runSkills } from './commands/skills.mjs';
9
-
10
- console.warn(
11
- '\x1b[33m⚠️ "unity-ui-skills" is deprecated. Use "npx unity-ui skills" instead.\x1b[0m\n'
12
- );
13
-
14
- // Parse legacy args
15
- const args = process.argv.slice(2);
16
- let output = '.claude';
17
- for (let i = 0; i < args.length; i++) {
18
- if ((args[i] === '--output' || args[i] === '-o') && args[i + 1]) {
19
- output = args[i + 1];
20
- i++;
21
- }
22
- if (args[i] === '--help' || args[i] === '-h') {
23
- console.log(`Usage: npx unity-ui skills [options]
24
-
25
- This command has moved. Please use:
26
- npx unity-ui skills [-o <dir>]
27
- `);
28
- process.exit(0);
29
- }
30
- }
31
-
32
- runSkills({ output });
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes