@wangs-ui/create-react-app 1.0.26-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +300 -0
- package/package.json +58 -0
- package/template/_gitignore +25 -0
- package/template/agents/AGENTS.md +49 -0
- package/template/agents/CLAUDE.md +22 -0
- package/template/agents/agents.mcp.json +8 -0
- package/template/agents/kilo.mcp.json +8 -0
- package/template/agents/mcp.json +8 -0
- package/template/agents/opencode.json +9 -0
- package/template/agents/skills/form-management/SKILL.md +95 -0
- package/template/agents/skills/i18n-standardization/SKILL.md +104 -0
- package/template/agents/skills/wangs-ui-components/SKILL.md +84 -0
- package/template/index.html +19 -0
- package/template/oxfmt.config.ts +3 -0
- package/template/oxlint.config.ts +3 -0
- package/template/package.json +38 -0
- package/template/src/App.tsx +58 -0
- package/template/src/index.css +5 -0
- package/template/src/main.tsx +19 -0
- package/template/src/vite-env.d.ts +1 -0
- package/template/tsconfig.app.json +26 -0
- package/template/tsconfig.json +4 -0
- package/template/tsconfig.node.json +24 -0
- package/template/vite.config.ts +8 -0
package/dist/bin.js
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import process$1 from "node:process";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import readline from "node:readline/promises";
|
|
7
|
+
//#region src/utils/fs.ts
|
|
8
|
+
/**
|
|
9
|
+
* Recursively copy a template directory into the target directory,
|
|
10
|
+
* customizing project name and presets in text files.
|
|
11
|
+
*/
|
|
12
|
+
function copyProjectTemplate(options) {
|
|
13
|
+
const { srcDir, destDir, projectName, preset, ignore = [] } = options;
|
|
14
|
+
if (!fs.existsSync(srcDir)) return;
|
|
15
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
16
|
+
const entries = fs.readdirSync(srcDir, { withFileTypes: true });
|
|
17
|
+
for (const entry of entries) {
|
|
18
|
+
if (ignore.includes(entry.name)) continue;
|
|
19
|
+
const srcPath = path.join(srcDir, entry.name);
|
|
20
|
+
let destName = entry.name;
|
|
21
|
+
if (destName === "_gitignore") destName = ".gitignore";
|
|
22
|
+
const destPath = path.join(destDir, destName);
|
|
23
|
+
if (entry.isDirectory()) copyProjectTemplate({
|
|
24
|
+
srcDir: srcPath,
|
|
25
|
+
destDir: destPath,
|
|
26
|
+
projectName,
|
|
27
|
+
preset,
|
|
28
|
+
ignore
|
|
29
|
+
});
|
|
30
|
+
else {
|
|
31
|
+
let content = fs.readFileSync(srcPath, "utf8");
|
|
32
|
+
if (destName === "package.json") content = content.replace(/"name":\s*"[^"]*"/, `"name": "${projectName}"`);
|
|
33
|
+
else if (destName === "index.html") content = content.replace(/<title>.*<\/title>/, `<title>${projectName}</title>`);
|
|
34
|
+
if (preset === "globalsettings") content = content.replace(/presetFixedAsset/g, "presetGlobalSettings").replace(/fixedasset/g, "globalsettings");
|
|
35
|
+
fs.writeFileSync(destPath, content, "utf8");
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/utils/mcpInstaller.ts
|
|
41
|
+
function copyDirRecursive(srcDir, destDir) {
|
|
42
|
+
if (!fs.existsSync(srcDir)) return;
|
|
43
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
44
|
+
const entries = fs.readdirSync(srcDir, { withFileTypes: true });
|
|
45
|
+
for (const entry of entries) {
|
|
46
|
+
const srcPath = path.join(srcDir, entry.name);
|
|
47
|
+
const destPath = path.join(destDir, entry.name);
|
|
48
|
+
if (entry.isDirectory()) copyDirRecursive(srcPath, destPath);
|
|
49
|
+
else fs.copyFileSync(srcPath, destPath);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function setupAgentConfigurations(options) {
|
|
53
|
+
const { targetDir, templateDir, agents } = options;
|
|
54
|
+
const configuredAgents = [];
|
|
55
|
+
const agentsTemplateDir = path.join(templateDir, "agents");
|
|
56
|
+
const agentsMdSrc = path.join(agentsTemplateDir, "AGENTS.md");
|
|
57
|
+
if (fs.existsSync(agentsMdSrc)) fs.copyFileSync(agentsMdSrc, path.join(targetDir, "AGENTS.md"));
|
|
58
|
+
if (agents.includes("claude")) {
|
|
59
|
+
const mcpSrc = path.join(agentsTemplateDir, "mcp.json");
|
|
60
|
+
const claudeMdSrc = path.join(agentsTemplateDir, "CLAUDE.md");
|
|
61
|
+
if (fs.existsSync(mcpSrc)) fs.copyFileSync(mcpSrc, path.join(targetDir, ".mcp.json"));
|
|
62
|
+
if (fs.existsSync(claudeMdSrc)) fs.copyFileSync(claudeMdSrc, path.join(targetDir, "CLAUDE.md"));
|
|
63
|
+
configuredAgents.push("Claude Code (.mcp.json, CLAUDE.md)");
|
|
64
|
+
}
|
|
65
|
+
if (agents.includes("opencode")) {
|
|
66
|
+
const opencodeSrc = path.join(agentsTemplateDir, "opencode.json");
|
|
67
|
+
const mcpSrc = path.join(agentsTemplateDir, "mcp.json");
|
|
68
|
+
if (fs.existsSync(opencodeSrc)) {
|
|
69
|
+
fs.copyFileSync(opencodeSrc, path.join(targetDir, "opencode.json"));
|
|
70
|
+
const dotOpencode = path.join(targetDir, ".opencode");
|
|
71
|
+
fs.mkdirSync(dotOpencode, { recursive: true });
|
|
72
|
+
fs.copyFileSync(opencodeSrc, path.join(dotOpencode, "mcp.json"));
|
|
73
|
+
}
|
|
74
|
+
if (fs.existsSync(mcpSrc) && !fs.existsSync(path.join(targetDir, ".mcp.json"))) fs.copyFileSync(mcpSrc, path.join(targetDir, ".mcp.json"));
|
|
75
|
+
configuredAgents.push("OpenCode (opencode.json, .opencode/mcp.json)");
|
|
76
|
+
}
|
|
77
|
+
if (agents.includes("kilo")) {
|
|
78
|
+
const kiloMcpSrc = path.join(agentsTemplateDir, "kilo.mcp.json");
|
|
79
|
+
if (fs.existsSync(kiloMcpSrc)) {
|
|
80
|
+
const dotKilo = path.join(targetDir, ".kilo");
|
|
81
|
+
fs.mkdirSync(dotKilo, { recursive: true });
|
|
82
|
+
fs.copyFileSync(kiloMcpSrc, path.join(dotKilo, "mcp.json"));
|
|
83
|
+
const dotVscode = path.join(targetDir, ".vscode");
|
|
84
|
+
fs.mkdirSync(dotVscode, { recursive: true });
|
|
85
|
+
fs.copyFileSync(kiloMcpSrc, path.join(dotVscode, "mcp.json"));
|
|
86
|
+
}
|
|
87
|
+
configuredAgents.push("Kilo (.kilo/mcp.json, .vscode/mcp.json)");
|
|
88
|
+
}
|
|
89
|
+
if (agents.includes("antigravity")) {
|
|
90
|
+
const agentsMcpSrc = path.join(agentsTemplateDir, "agents.mcp.json");
|
|
91
|
+
const skillsSrc = path.join(agentsTemplateDir, "skills");
|
|
92
|
+
const dotAgents = path.join(targetDir, ".agents");
|
|
93
|
+
fs.mkdirSync(dotAgents, { recursive: true });
|
|
94
|
+
if (fs.existsSync(agentsMcpSrc)) fs.copyFileSync(agentsMcpSrc, path.join(dotAgents, "mcp_config.json"));
|
|
95
|
+
if (fs.existsSync(skillsSrc)) copyDirRecursive(skillsSrc, path.join(dotAgents, "skills"));
|
|
96
|
+
configuredAgents.push("Antigravity IDE & CLI (.agents/mcp_config.json, .agents/skills)");
|
|
97
|
+
}
|
|
98
|
+
return configuredAgents;
|
|
99
|
+
}
|
|
100
|
+
//#endregion
|
|
101
|
+
//#region src/utils/packageManager.ts
|
|
102
|
+
function detectPackageManager() {
|
|
103
|
+
const userAgent = process.env.npm_config_user_agent;
|
|
104
|
+
if (userAgent) {
|
|
105
|
+
if (userAgent.startsWith("pnpm")) return "pnpm";
|
|
106
|
+
if (userAgent.startsWith("bun")) return "bun";
|
|
107
|
+
if (userAgent.startsWith("yarn")) return "yarn";
|
|
108
|
+
if (userAgent.startsWith("npm")) return "npm";
|
|
109
|
+
}
|
|
110
|
+
return "pnpm";
|
|
111
|
+
}
|
|
112
|
+
function getInstallCommand(pm) {
|
|
113
|
+
switch (pm) {
|
|
114
|
+
case "pnpm": return "pnpm install";
|
|
115
|
+
case "bun": return "bun install";
|
|
116
|
+
case "yarn": return "yarn";
|
|
117
|
+
default: return "npm install";
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function getRunCommand(pm, script) {
|
|
121
|
+
switch (pm) {
|
|
122
|
+
case "pnpm": return `pnpm ${script}`;
|
|
123
|
+
case "bun": return `bun ${script}`;
|
|
124
|
+
case "yarn": return `yarn ${script}`;
|
|
125
|
+
default: return `npm run ${script}`;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region src/index.ts
|
|
130
|
+
var __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
131
|
+
function generateProject(options) {
|
|
132
|
+
const { projectName, targetDir, preset, agents } = options;
|
|
133
|
+
let templateDir = path.resolve(__dirname, "../template");
|
|
134
|
+
if (!fs.existsSync(templateDir)) templateDir = path.resolve(__dirname, "template");
|
|
135
|
+
console.log(`\n\x1b[36m🚀 Scaffolding Wangs UI React Application in \x1b[1m${targetDir}\x1b[0m...`);
|
|
136
|
+
copyProjectTemplate({
|
|
137
|
+
srcDir: templateDir,
|
|
138
|
+
destDir: targetDir,
|
|
139
|
+
projectName,
|
|
140
|
+
preset,
|
|
141
|
+
ignore: [
|
|
142
|
+
"agents",
|
|
143
|
+
"node_modules",
|
|
144
|
+
"dist"
|
|
145
|
+
]
|
|
146
|
+
});
|
|
147
|
+
let configuredAgents = [];
|
|
148
|
+
if (agents.length > 0) configuredAgents = setupAgentConfigurations({
|
|
149
|
+
targetDir,
|
|
150
|
+
templateDir,
|
|
151
|
+
agents
|
|
152
|
+
});
|
|
153
|
+
const pm = detectPackageManager();
|
|
154
|
+
const installCmd = getInstallCommand(pm);
|
|
155
|
+
const devCmd = getRunCommand(pm, "dev");
|
|
156
|
+
const lintCmd = getRunCommand(pm, "lint");
|
|
157
|
+
console.log("\n\x1B[32m✨ Project created successfully!\x1B[0m\n");
|
|
158
|
+
if (configuredAgents.length > 0) {
|
|
159
|
+
console.log("\x1B[1m🤖 AI Agent MCP & Skills Configured:\x1B[0m");
|
|
160
|
+
for (const agent of configuredAgents) console.log(` \x1b[32m✔\x1b[0m ${agent}`);
|
|
161
|
+
console.log();
|
|
162
|
+
}
|
|
163
|
+
console.log("\x1B[1mNext steps:\x1B[0m");
|
|
164
|
+
if (!(targetDir === process.cwd())) {
|
|
165
|
+
const relPath = path.relative(process.cwd(), targetDir);
|
|
166
|
+
console.log(` \x1b[36mcd\x1b[0m ${relPath}`);
|
|
167
|
+
}
|
|
168
|
+
console.log(` \x1b[36m${installCmd}\x1b[0m`);
|
|
169
|
+
console.log(` \x1b[36m${devCmd}\x1b[0m`);
|
|
170
|
+
console.log(` \x1b[36m${lintCmd}\x1b[0m \x1b[2m(runs Oxlint)\x1b[0m\n`);
|
|
171
|
+
}
|
|
172
|
+
//#endregion
|
|
173
|
+
//#region src/prompts.ts
|
|
174
|
+
async function promptUser(initialOptions) {
|
|
175
|
+
const rl = readline.createInterface({
|
|
176
|
+
input: process$1.stdin,
|
|
177
|
+
output: process$1.stdout
|
|
178
|
+
});
|
|
179
|
+
try {
|
|
180
|
+
let { projectName, preset, agents } = initialOptions;
|
|
181
|
+
if (!projectName) projectName = (await rl.question("\x1B[36m?\x1B[0m \x1B[1mProject name:\x1B[0m (\x1B[2mmy-wangs-app\x1B[0m) ")).trim() || "my-wangs-app";
|
|
182
|
+
if (!preset) if (initialOptions.yes) preset = "fixedasset";
|
|
183
|
+
else {
|
|
184
|
+
console.log("\n\x1B[1mSelect a Wangs UI Design Preset:\x1B[0m");
|
|
185
|
+
console.log(" \x1B[32m1)\x1B[0m fixedasset \x1B[2m(Recommended - Standard Theme)\x1B[0m");
|
|
186
|
+
console.log(" \x1B[32m2)\x1B[0m globalsettings \x1B[2m(Global Settings Theme)\x1B[0m");
|
|
187
|
+
preset = (await rl.question("\x1B[36m?\x1B[0m \x1B[1mEnter choice [1-2]:\x1B[0m (1) ")).trim() === "2" ? "globalsettings" : "fixedasset";
|
|
188
|
+
}
|
|
189
|
+
if (!agents) if (initialOptions.yes) agents = [
|
|
190
|
+
"kilo",
|
|
191
|
+
"opencode",
|
|
192
|
+
"claude",
|
|
193
|
+
"antigravity"
|
|
194
|
+
];
|
|
195
|
+
else {
|
|
196
|
+
console.log("\n\x1B[1mAI Agent & MCP Tooling Setup:\x1B[0m");
|
|
197
|
+
console.log(" \x1B[32m1)\x1B[0m All Supported Agents \x1B[2m(Kilo, OpenCode, Claude Code, Antigravity)\x1B[0m");
|
|
198
|
+
console.log(" \x1B[32m2)\x1B[0m Kilo only");
|
|
199
|
+
console.log(" \x1B[32m3)\x1B[0m OpenCode only");
|
|
200
|
+
console.log(" \x1B[32m4)\x1B[0m Claude Code only");
|
|
201
|
+
console.log(" \x1B[32m5)\x1B[0m Antigravity IDE & CLI only");
|
|
202
|
+
console.log(" \x1B[32m6)\x1B[0m Skip Agent Setup");
|
|
203
|
+
switch ((await rl.question("\x1B[36m?\x1B[0m \x1B[1mEnter choice [1-6]:\x1B[0m (1) ")).trim()) {
|
|
204
|
+
case "2":
|
|
205
|
+
agents = ["kilo"];
|
|
206
|
+
break;
|
|
207
|
+
case "3":
|
|
208
|
+
agents = ["opencode"];
|
|
209
|
+
break;
|
|
210
|
+
case "4":
|
|
211
|
+
agents = ["claude"];
|
|
212
|
+
break;
|
|
213
|
+
case "5":
|
|
214
|
+
agents = ["antigravity"];
|
|
215
|
+
break;
|
|
216
|
+
case "6":
|
|
217
|
+
agents = [];
|
|
218
|
+
break;
|
|
219
|
+
default:
|
|
220
|
+
agents = [
|
|
221
|
+
"kilo",
|
|
222
|
+
"opencode",
|
|
223
|
+
"claude",
|
|
224
|
+
"antigravity"
|
|
225
|
+
];
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
projectName,
|
|
231
|
+
preset,
|
|
232
|
+
agents
|
|
233
|
+
};
|
|
234
|
+
} finally {
|
|
235
|
+
rl.close();
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
//#endregion
|
|
239
|
+
//#region bin.ts
|
|
240
|
+
async function main() {
|
|
241
|
+
const args = process$1.argv.slice(2);
|
|
242
|
+
const options = {};
|
|
243
|
+
for (let i = 0; i < args.length; i++) {
|
|
244
|
+
const arg = args[i];
|
|
245
|
+
if (arg === "-y" || arg === "--yes") options.yes = true;
|
|
246
|
+
else if (arg.startsWith("--preset=")) {
|
|
247
|
+
const val = arg.split("=")[1];
|
|
248
|
+
if (val === "fixedasset" || val === "globalsettings") options.preset = val;
|
|
249
|
+
} else if (arg === "--preset" && args[i + 1]) {
|
|
250
|
+
const val = args[++i];
|
|
251
|
+
if (val === "fixedasset" || val === "globalsettings") options.preset = val;
|
|
252
|
+
} else if (arg.startsWith("--agent=") || arg.startsWith("--agents=")) {
|
|
253
|
+
const val = arg.split("=")[1];
|
|
254
|
+
if (val === "all") options.agents = [
|
|
255
|
+
"kilo",
|
|
256
|
+
"opencode",
|
|
257
|
+
"claude",
|
|
258
|
+
"antigravity"
|
|
259
|
+
];
|
|
260
|
+
else if (val === "none") options.agents = [];
|
|
261
|
+
else options.agents = val.split(",").filter((a) => [
|
|
262
|
+
"kilo",
|
|
263
|
+
"opencode",
|
|
264
|
+
"claude",
|
|
265
|
+
"antigravity"
|
|
266
|
+
].includes(a));
|
|
267
|
+
} else if ((arg === "--agent" || arg === "--agents") && args[i + 1]) {
|
|
268
|
+
const val = args[++i];
|
|
269
|
+
if (val === "all") options.agents = [
|
|
270
|
+
"kilo",
|
|
271
|
+
"opencode",
|
|
272
|
+
"claude",
|
|
273
|
+
"antigravity"
|
|
274
|
+
];
|
|
275
|
+
else if (val === "none") options.agents = [];
|
|
276
|
+
else options.agents = val.split(",").filter((a) => [
|
|
277
|
+
"kilo",
|
|
278
|
+
"opencode",
|
|
279
|
+
"claude",
|
|
280
|
+
"antigravity"
|
|
281
|
+
].includes(a));
|
|
282
|
+
} else if (!arg.startsWith("-") && !options.projectName) options.projectName = arg;
|
|
283
|
+
}
|
|
284
|
+
const { projectName, preset, agents } = await promptUser(options);
|
|
285
|
+
const targetDir = path.resolve(process$1.cwd(), projectName);
|
|
286
|
+
generateProject({
|
|
287
|
+
projectName: path.basename(targetDir),
|
|
288
|
+
targetDir,
|
|
289
|
+
preset,
|
|
290
|
+
agents
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
try {
|
|
294
|
+
await main();
|
|
295
|
+
} catch (err) {
|
|
296
|
+
console.error("\x1B[31mError during project creation:\x1B[0m", err);
|
|
297
|
+
process$1.exit(1);
|
|
298
|
+
}
|
|
299
|
+
//#endregion
|
|
300
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wangs-ui/create-react-app",
|
|
3
|
+
"version": "1.0.26-alpha.2",
|
|
4
|
+
"description": "Scaffold a modern React app with Wangs UI, Vite 8, Oxlint, Oxfmt, and AI Agent MCP integration",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"antigravity",
|
|
7
|
+
"claude-code",
|
|
8
|
+
"create-react-app",
|
|
9
|
+
"kilo",
|
|
10
|
+
"mcp",
|
|
11
|
+
"opencode",
|
|
12
|
+
"oxfmt",
|
|
13
|
+
"oxlint",
|
|
14
|
+
"react",
|
|
15
|
+
"vite",
|
|
16
|
+
"wangs-ui"
|
|
17
|
+
],
|
|
18
|
+
"homepage": "https://github.com/fewangsit/wangs-ui-react",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"author": "Wangsit FE Developer",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/fewangsit/wangs-ui-react.git",
|
|
24
|
+
"directory": "packages/create-react-app"
|
|
25
|
+
},
|
|
26
|
+
"bin": {
|
|
27
|
+
"create-react-app": "./dist/bin.js",
|
|
28
|
+
"create-wangs-app": "./dist/bin.js",
|
|
29
|
+
"create-wangs-ui-react-app": "./dist/bin.js"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist",
|
|
33
|
+
"template/index.html",
|
|
34
|
+
"template/package.json",
|
|
35
|
+
"template/oxfmt.config.ts",
|
|
36
|
+
"template/oxlint.config.ts",
|
|
37
|
+
"template/tsconfig.json",
|
|
38
|
+
"template/tsconfig.app.json",
|
|
39
|
+
"template/tsconfig.node.json",
|
|
40
|
+
"template/vite.config.ts",
|
|
41
|
+
"template/_gitignore",
|
|
42
|
+
"template/.npmrc",
|
|
43
|
+
"template/src",
|
|
44
|
+
"template/agents"
|
|
45
|
+
],
|
|
46
|
+
"type": "module",
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public",
|
|
49
|
+
"registry": "https://registry.npmjs.org/"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {},
|
|
52
|
+
"devDependencies": {},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "vite build",
|
|
55
|
+
"check:publint": "publint",
|
|
56
|
+
"check:attw": "attw --pack ."
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Logs
|
|
2
|
+
logs
|
|
3
|
+
*.log
|
|
4
|
+
npm-debug.log*
|
|
5
|
+
yarn-debug.log*
|
|
6
|
+
yarn-error.log*
|
|
7
|
+
pnpm-debug.log*
|
|
8
|
+
lerna-debug.log*
|
|
9
|
+
|
|
10
|
+
node_modules
|
|
11
|
+
dist
|
|
12
|
+
dist-ssr
|
|
13
|
+
*.local
|
|
14
|
+
|
|
15
|
+
# Editor directories and files
|
|
16
|
+
.vscode/*
|
|
17
|
+
!.vscode/extensions.json
|
|
18
|
+
!.vscode/mcp.json
|
|
19
|
+
.idea
|
|
20
|
+
.DS_Store
|
|
21
|
+
*.suo
|
|
22
|
+
*.ntvs*
|
|
23
|
+
*.njsproj
|
|
24
|
+
*.sln
|
|
25
|
+
*.sw?
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# AI Agent Instructions for Wangs UI
|
|
2
|
+
|
|
3
|
+
This application is built with **Wangs UI** (`@wangs-ui/react-core`, `@wangs-ui/react-icons`, `@wangs-ui/react-presets`, `@wangs-ui/react-i18n`, `@wangs-ui/form`), Tailwind CSS v4, and Vite 8.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Core Principles & Strict Rules
|
|
8
|
+
|
|
9
|
+
1. **Primitive Component Rule (Mandatory)**:
|
|
10
|
+
- Always use components from `@wangs-ui/react-core` (`Button`, `Card`, `Dialog`, `Modal`, `Form`, `Field`, `InputText`, `Select`, `DataTable`, etc.).
|
|
11
|
+
- Do NOT write unstyled raw HTML controls (`<button>`, `<input>`, `<select>`, `<form>`).
|
|
12
|
+
|
|
13
|
+
2. **Internationalization (`@wangs-ui/react-i18n`)**:
|
|
14
|
+
- All text visible to users MUST be wrapped in `t()` from `useI18n()`.
|
|
15
|
+
- Never hardcode raw string literals inside JSX components.
|
|
16
|
+
- For formatting dates, times, currencies, or numbers, use `useLocaleFormatter()`:
|
|
17
|
+
```tsx
|
|
18
|
+
import { useI18n, useLocaleFormatter } from '@wangs-ui/react-i18n';
|
|
19
|
+
|
|
20
|
+
const { t } = useI18n();
|
|
21
|
+
const { formatDate, formatCurrency } = useLocaleFormatter();
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
3. **Typography & Styling Tokens**:
|
|
25
|
+
- Typeface: **Manrope** (geometric sans-serif).
|
|
26
|
+
- Use semantic heading classes (`.heading-1` to `.heading-6`, `.p`) for typography hierarchy.
|
|
27
|
+
- Use predefined color and spacing tokens (`bg-primary-500`, `text-secondary-900`, `gap-md`, `p-l`, `rounded-m`).
|
|
28
|
+
|
|
29
|
+
4. **MCP Component Inspection**:
|
|
30
|
+
- The `@wangs-ui/mcp` server is configured in this workspace.
|
|
31
|
+
- Query the MCP server (`get-documentation`, `list-all-documentation`) to inspect component schemas, props, and design specifications before writing components.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 2. Specialized Skills Reference
|
|
36
|
+
|
|
37
|
+
When performing specific tasks, consult the detailed skill files in `.agents/skills/`:
|
|
38
|
+
|
|
39
|
+
- **`i18n-standardization`** (`.agents/skills/i18n-standardization/SKILL.md`): Rules for JIT translation, named variables `{count}`, ICU plurals, and locale formatters.
|
|
40
|
+
- **`wangs-ui-components`** (`.agents/skills/wangs-ui-components/SKILL.md`): Component composition, iconography from `@wangs-ui/react-icons`, and token usage.
|
|
41
|
+
- **`form-management`** (`.agents/skills/form-management/SKILL.md`): Form implementation, schema typing, and validation with `@wangs-ui/form`.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 3. Code Quality & Toolchain
|
|
46
|
+
|
|
47
|
+
- **Linter**: `oxlint` (run `pnpm lint`)
|
|
48
|
+
- **Formatter**: `oxfmt` (run `pnpm format`)
|
|
49
|
+
- **Type Checker**: TypeScript with `tsc -b` (run `pnpm build`)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Claude Code Project Guidelines
|
|
2
|
+
|
|
3
|
+
This project uses **Wangs UI**, React 19, and Vite 8.
|
|
4
|
+
|
|
5
|
+
## MCP Tools
|
|
6
|
+
|
|
7
|
+
The `@wangs-ui/mcp` server is configured in `.mcp.json`. Use it to inspect Wangs UI component documentation and story specifications before implementing new views.
|
|
8
|
+
|
|
9
|
+
## Development Commands
|
|
10
|
+
|
|
11
|
+
- `pnpm dev`: Start local development server
|
|
12
|
+
- `pnpm build`: Type check and build production bundle
|
|
13
|
+
- `pnpm lint`: Run Oxlint linter
|
|
14
|
+
- `pnpm format`: Run Oxfmt formatter
|
|
15
|
+
|
|
16
|
+
## Key Coding Rules
|
|
17
|
+
|
|
18
|
+
- Use `@wangs-ui/react-core` for all UI elements (do not use unstyled raw HTML inputs/buttons).
|
|
19
|
+
- Use `@wangs-ui/react-icons` for iconography.
|
|
20
|
+
- Wrap all UI strings in `t('...')` from `useI18n()`.
|
|
21
|
+
- Use `useLocaleFormatter()` for date, number, and currency formatting.
|
|
22
|
+
- See `AGENTS.md` and `.agents/skills/` for in-depth design system standards.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: form-management
|
|
3
|
+
description: Guidelines and best practices for creating, validating, and submitting forms using Wangs UI Form and Field components.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Skill: Form Management & Validation Guidelines
|
|
7
|
+
|
|
8
|
+
This skill defines the standards for form implementation with `@wangs-ui/form`, `useFormControl`, and `@wangs-ui/react-core` `Form` / `Field` components.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 1. Core Architecture
|
|
13
|
+
|
|
14
|
+
Wangs UI provides strongly typed form controllers:
|
|
15
|
+
|
|
16
|
+
- `useFormControl({ type: 'json' })`: For standard JSON payload forms.
|
|
17
|
+
- `useFormControl({ type: 'formdata' })`: For multipart forms (file uploads).
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 2. Standard Form Pattern
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import React, { useState } from 'react';
|
|
25
|
+
import { Button, Card, Form, Field, InputText, InputPassword } from '@wangs-ui/react-core';
|
|
26
|
+
import { useFormControl } from '@wangs-ui/form';
|
|
27
|
+
import { useI18n } from '@wangs-ui/react-i18n';
|
|
28
|
+
|
|
29
|
+
interface LoginFormValues {
|
|
30
|
+
email: string;
|
|
31
|
+
password: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default function LoginForm(): React.ReactElement {
|
|
35
|
+
const { t } = useI18n();
|
|
36
|
+
const formControl = useFormControl<LoginFormValues>({ type: 'json' });
|
|
37
|
+
const [loading, setLoading] = useState(false);
|
|
38
|
+
|
|
39
|
+
const handleSubmit = async (values: LoginFormValues) => {
|
|
40
|
+
setLoading(true);
|
|
41
|
+
try {
|
|
42
|
+
console.log('Submitted values:', values);
|
|
43
|
+
} finally {
|
|
44
|
+
setLoading(false);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<Card className="w-full max-w-md p-6">
|
|
50
|
+
<Form control={formControl} onSubmit={handleSubmit} className="flex flex-col gap-4">
|
|
51
|
+
<h2 className="heading-2">{t('Sign In')}</h2>
|
|
52
|
+
|
|
53
|
+
<Field<string>
|
|
54
|
+
name="email"
|
|
55
|
+
label={t('Email Address')}
|
|
56
|
+
required
|
|
57
|
+
rules={{
|
|
58
|
+
pattern: {
|
|
59
|
+
value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
|
|
60
|
+
message: t('Invalid email address'),
|
|
61
|
+
},
|
|
62
|
+
}}
|
|
63
|
+
>
|
|
64
|
+
{(field) => (
|
|
65
|
+
<InputText {...field} placeholder={t('Enter your email')} value={field.value || ''} />
|
|
66
|
+
)}
|
|
67
|
+
</Field>
|
|
68
|
+
|
|
69
|
+
<Field<string> name="password" label={t('Password')} required>
|
|
70
|
+
{(field) => (
|
|
71
|
+
<InputPassword
|
|
72
|
+
{...field}
|
|
73
|
+
placeholder={t('Enter your password')}
|
|
74
|
+
value={field.value || ''}
|
|
75
|
+
/>
|
|
76
|
+
)}
|
|
77
|
+
</Field>
|
|
78
|
+
|
|
79
|
+
<Button type="submit" label={t('Submit')} severity="primary" loading={loading} />
|
|
80
|
+
</Form>
|
|
81
|
+
</Card>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## 3. Mandatory Rules
|
|
89
|
+
|
|
90
|
+
1. **Always Use `<Form>` and `<Field>`**:
|
|
91
|
+
Never use raw HTML `<form>` or uncontrolled `<input>`.
|
|
92
|
+
2. **Translate Labels and Error Messages**:
|
|
93
|
+
Always wrap `label`, `placeholder`, and validation `message` in `t()`.
|
|
94
|
+
3. **Strongly Typed Models**:
|
|
95
|
+
Define a TypeScript interface for form values and pass it to `useFormControl<Model>()`.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: i18n-standardization
|
|
3
|
+
description: Guidelines and rules for implementing and using the JIT i18n translation functions (t) and locale formatters in this Wangs UI project.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Skill: JIT i18n Implementation & Usage Guidelines
|
|
7
|
+
|
|
8
|
+
This skill defines the coding standards, rules, and best practices for writing translation strings and formatting dates/numbers using the JIT i18n system.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 1. Core Principles
|
|
13
|
+
|
|
14
|
+
The localization system uses a **Just-In-Time (JIT)** workflow. All user-facing English strings act directly as keys. The `t()` function resolves these keys against local memory, the network cache, or triggers a real-time background backend AI translation without blocking UI rendering.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 2. Mandatory Rules
|
|
19
|
+
|
|
20
|
+
### A. Translate at Consumer Level for `ReactNode` Props
|
|
21
|
+
|
|
22
|
+
All user-facing text props in Wangs UI components (`label`, `placeholder`, `emptyMessage`, `header`, `tooltip`, etc.) are typed as `ReactNode`. Components render them as-is and **do NOT call `t()` internally**. Translation MUST be called at the consumer level:
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
// DO
|
|
26
|
+
const { t } = useI18n();
|
|
27
|
+
|
|
28
|
+
<Button label={t('Save Changes')} />
|
|
29
|
+
<Select placeholder={t('Select category...')} />
|
|
30
|
+
<DataTable emptyMessage={t('No data available')} />
|
|
31
|
+
|
|
32
|
+
// DO NOT: pass raw English strings without t()
|
|
33
|
+
<Button label="Save Changes" />
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### B. Named Variable Interpolation (Enforced)
|
|
37
|
+
|
|
38
|
+
Always use descriptive named variables (`{count}`, `{userName}`, `{fileName}`) passed via an object argument. This provides necessary semantic context for AI translation engines:
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
// DO
|
|
42
|
+
t('Upload {count} files to {folderName}', { count: 5, folderName: 'Documents' });
|
|
43
|
+
|
|
44
|
+
// DO NOT: use positional indices for multi-variable strings
|
|
45
|
+
t('Upload {0} files to {1}', 5, 'Documents');
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### C. ICU Pluralization & Zero-State (`=0`)
|
|
49
|
+
|
|
50
|
+
Write ICU conditional plural/select structures directly inside the text keys. Always include `=0` branches to handle zero values within ICU syntax instead of creating separate JavaScript `if` or ternary branches:
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
// DO
|
|
54
|
+
t('{count, plural, =0 {No files uploaded} one {Uploaded 1 file} other {Uploaded {count} files}}', {
|
|
55
|
+
count,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// DO NOT: write manual JS conditionals for pluralization
|
|
59
|
+
count === 0 ? t('No files') : t('{count} files', { count });
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### D. Formatting Dates, Numbers & Currencies
|
|
63
|
+
|
|
64
|
+
Always format dates, numbers, currencies, and relative time using `useLocaleFormatter()` **before** passing them into `t()`:
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
import { useI18n, useLocaleFormatter } from '@wangs-ui/react-i18n';
|
|
68
|
+
|
|
69
|
+
const { t } = useI18n();
|
|
70
|
+
const { formatDate, formatRelativeTime, formatCurrency, formatNumber } = useLocaleFormatter();
|
|
71
|
+
|
|
72
|
+
// Date formatting
|
|
73
|
+
const dateLabel = t('Created on {date}', {
|
|
74
|
+
date: formatDate(new Date(), 'dd MMMM yyyy'),
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Relative time formatting
|
|
78
|
+
const timeLabel = t('Updated {time}', {
|
|
79
|
+
time: formatRelativeTime(updatedAt),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Currency formatting
|
|
83
|
+
const priceText = formatCurrency(150000, 'IDR');
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## 3. Strict Prohibitions (MUST NOT)
|
|
89
|
+
|
|
90
|
+
1. **NO Dynamic String Concatenation**:
|
|
91
|
+
Never use string concatenation or template literals inside `t()`:
|
|
92
|
+
```tsx
|
|
93
|
+
// FORBIDDEN
|
|
94
|
+
t('Hello ' + user.name);
|
|
95
|
+
t(`Upload to ${folder}`);
|
|
96
|
+
```
|
|
97
|
+
2. **NO Hardcoded Strings in JSX**:
|
|
98
|
+
Never render raw text nodes without `t()`:
|
|
99
|
+
```tsx
|
|
100
|
+
// FORBIDDEN
|
|
101
|
+
<h1>Dashboard Overview</h1>
|
|
102
|
+
```
|
|
103
|
+
3. **NO Static Translation JSON Files**:
|
|
104
|
+
Never create static `.json` dictionaries (e.g. `id.json`, `en.json`). Localization is completely dynamic via JIT translation.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wangs-ui-components
|
|
3
|
+
description: Guidelines and rules for using and composing Wangs UI Design System components and icons in this React application.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Skill: Wangs UI Component Usage & Design System Standards
|
|
7
|
+
|
|
8
|
+
This skill defines the rules for building user interfaces with `@wangs-ui/react-core`, `@wangs-ui/react-icons`, and `@wangs-ui/react-presets`.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 1. Subpath Import Style (Mandatory)
|
|
13
|
+
|
|
14
|
+
Components and providers are exported via clean, modular subpaths:
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
// Provider / Contexts
|
|
18
|
+
import { WangsUiProvider } from '@wangs-ui/react-core/api';
|
|
19
|
+
|
|
20
|
+
// Primitive Components
|
|
21
|
+
import Button from '@wangs-ui/react-core/primitive/button';
|
|
22
|
+
import Card from '@wangs-ui/react-core/primitive/card';
|
|
23
|
+
import InputText from '@wangs-ui/react-core/primitive/inputtext';
|
|
24
|
+
import Select from '@wangs-ui/react-core/primitive/select';
|
|
25
|
+
import Dialog from '@wangs-ui/react-core/primitive/dialog';
|
|
26
|
+
import Modal from '@wangs-ui/react-core/primitive/modal';
|
|
27
|
+
import DataTable from '@wangs-ui/react-core/primitive/datatable';
|
|
28
|
+
|
|
29
|
+
// Icons
|
|
30
|
+
import { AddLine, SearchLine, DeleteBin6Line } from '@wangs-ui/react-icons';
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 2. Primitive Component Rule (Strictly Enforced)
|
|
36
|
+
|
|
37
|
+
Never use unstyled raw HTML controls when a corresponding Wangs UI primitive component exists:
|
|
38
|
+
|
|
39
|
+
| Raw HTML Control (Forbidden) | Wangs UI Component (Mandatory) | Subpath Import |
|
|
40
|
+
| :--------------------------- | :----------------------------- | :--------------------------------------------- |
|
|
41
|
+
| `<button>` | `Button` | `@wangs-ui/react-core/primitive/button` |
|
|
42
|
+
| `<input type="text">` | `InputText` | `@wangs-ui/react-core/primitive/inputtext` |
|
|
43
|
+
| `<input type="number">` | `InputNumber` | `@wangs-ui/react-core/primitive/inputnumber` |
|
|
44
|
+
| `<input type="password">` | `InputPassword` | `@wangs-ui/react-core/primitive/inputpassword` |
|
|
45
|
+
| `<input type="checkbox">` | `Checkbox` | `@wangs-ui/react-core/primitive/checkbox` |
|
|
46
|
+
| `<select>` / dropdown | `Select` / `Dropdown` | `@wangs-ui/react-core/primitive/select` |
|
|
47
|
+
| `<dialog>` / modal | `Dialog` / `Modal` | `@wangs-ui/react-core/primitive/dialog` |
|
|
48
|
+
| `<table>` | `DataTable` | `@wangs-ui/react-core/primitive/datatable` |
|
|
49
|
+
| Container box | `Card` | `@wangs-ui/react-core/primitive/card` |
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 3. Typography & Spacing Scale
|
|
54
|
+
|
|
55
|
+
### Typography (Typeface: Manrope)
|
|
56
|
+
|
|
57
|
+
Use semantic heading classes rather than arbitrary text size utilities:
|
|
58
|
+
|
|
59
|
+
- `.heading-1` (22px, 600 weight) — Page title
|
|
60
|
+
- `.heading-2` (18px, 600 weight) — Section title
|
|
61
|
+
- `.heading-3` (16px, 500 weight) — Card / Container header
|
|
62
|
+
- `.heading-4` (14px, 500 weight) — Sub-headers / Field labels
|
|
63
|
+
- `.heading-5` (12px, 600 weight) — Small titles
|
|
64
|
+
- `.p` (12px, 500 weight) — Body text
|
|
65
|
+
|
|
66
|
+
### Color Tokens
|
|
67
|
+
|
|
68
|
+
- Primary: `bg-primary-500`, `text-primary-500`, `border-primary-500`
|
|
69
|
+
- Secondary / Text: `text-secondary-900` (main text), `text-secondary-500` (muted/caption), `border-secondary-200`
|
|
70
|
+
- Semantic: `success-500`, `danger-500`, `warning-500`, `info-500`
|
|
71
|
+
|
|
72
|
+
### Spacing Tokens (4px Grid)
|
|
73
|
+
|
|
74
|
+
- `gap-xs` (4px), `gap-s` (6px), `gap-md` (8px), `gap-m` (12px), `gap-l` (16px), `gap-xl` (20px), `gap-xxl` (24px)
|
|
75
|
+
- `p-xs`, `p-s`, `p-md`, `p-m`, `p-l`, `p-xl`, `p-xxl`
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## 4. MCP Component Inspection
|
|
80
|
+
|
|
81
|
+
When creating new views or composing complex components:
|
|
82
|
+
|
|
83
|
+
- Use the `@wangs-ui/mcp` server tools (`get-documentation`, `list-all-documentation`) to inspect component props, types, and story examples.
|
|
84
|
+
- Do NOT guess prop names or event handlers.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
7
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
8
|
+
<link
|
|
9
|
+
href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700&display=swap"
|
|
10
|
+
rel="stylesheet"
|
|
11
|
+
/>
|
|
12
|
+
<title>Wangs UI React App</title>
|
|
13
|
+
</head>
|
|
14
|
+
|
|
15
|
+
<body class="bg-secondary-50 text-secondary-900 min-h-screen antialiased">
|
|
16
|
+
<div id="root"></div>
|
|
17
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
18
|
+
</body>
|
|
19
|
+
</html>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "wangs-ui-react-app",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "tsc -b && vite build",
|
|
9
|
+
"preview": "vite preview",
|
|
10
|
+
"lint": "oxlint -c oxlint.config.ts",
|
|
11
|
+
"lint:fix": "oxlint -c oxlint.config.ts --fix",
|
|
12
|
+
"format": "oxfmt --write",
|
|
13
|
+
"format:check": "oxfmt --check"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@wangs-ui/react-core": "^1.0.26-alpha.2",
|
|
17
|
+
"@wangs-ui/react-i18n": "^1.0.26-alpha.2",
|
|
18
|
+
"@wangs-ui/react-icons": "^1.0.26-alpha.2",
|
|
19
|
+
"@wangs-ui/react-presets": "^1.0.26-alpha.2",
|
|
20
|
+
"clsx": "^2.1.1",
|
|
21
|
+
"react": "^19.2.7",
|
|
22
|
+
"react-dom": "^19.2.7",
|
|
23
|
+
"tailwind-merge": "^3.6.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@fewangsit/oxlint-config-react": "^1.0.13",
|
|
27
|
+
"@tailwindcss/vite": "^4.3.0",
|
|
28
|
+
"@types/node": "^25.9.2",
|
|
29
|
+
"@types/react": "^19.2.17",
|
|
30
|
+
"@types/react-dom": "^19.2.3",
|
|
31
|
+
"@vitejs/plugin-react": "^6.0.4",
|
|
32
|
+
"oxfmt": "^0.60.0",
|
|
33
|
+
"oxlint": "^1.75.0",
|
|
34
|
+
"tailwindcss": "^4.3.0",
|
|
35
|
+
"typescript": "^6.0.3",
|
|
36
|
+
"vite": "^8.0.16"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import Button from '@wangs-ui/react-core/primitive/button';
|
|
2
|
+
import Card from '@wangs-ui/react-core/primitive/card';
|
|
3
|
+
import { useI18n, useLocaleFormatter } from '@wangs-ui/react-i18n';
|
|
4
|
+
import { AddLine, RefreshLine } from '@wangs-ui/react-icons';
|
|
5
|
+
import React, { useState } from 'react';
|
|
6
|
+
|
|
7
|
+
export default function App(): React.ReactElement {
|
|
8
|
+
const { t, locale, setLocale } = useI18n();
|
|
9
|
+
const { formatDate } = useLocaleFormatter();
|
|
10
|
+
const [count, setCount] = useState<number>(0);
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div className="flex min-h-screen flex-col items-center justify-center p-6">
|
|
14
|
+
<Card className="flex w-full max-w-md flex-col gap-6 p-6 shadow-lg">
|
|
15
|
+
<div className="flex flex-col gap-1">
|
|
16
|
+
<h1 className="heading-1 text-secondary-900 font-semibold">{t('Welcome to Wangs UI')}</h1>
|
|
17
|
+
<p className="text-secondary-500 text-xs">
|
|
18
|
+
{t('Today is {0}', formatDate(new Date(), 'dd MMMM yyyy'))}
|
|
19
|
+
</p>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div className="rounded-m border-secondary-200 bg-secondary-50 flex items-center justify-between border p-4">
|
|
23
|
+
<span className="text-secondary-700 text-sm font-medium">{t('Current count:')}</span>
|
|
24
|
+
<span className="text-primary-500 font-mono text-lg font-bold">{count}</span>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<div className="flex gap-3">
|
|
28
|
+
<Button
|
|
29
|
+
className="flex-1"
|
|
30
|
+
icon={<AddLine />}
|
|
31
|
+
label={t('Increment')}
|
|
32
|
+
severity="primary"
|
|
33
|
+
variant="filled"
|
|
34
|
+
onClick={() => setCount((c) => c + 1)}
|
|
35
|
+
/>
|
|
36
|
+
<Button
|
|
37
|
+
icon={<RefreshLine />}
|
|
38
|
+
label={t('Reset')}
|
|
39
|
+
severity="secondary"
|
|
40
|
+
variant="outline"
|
|
41
|
+
onClick={() => setCount(0)}
|
|
42
|
+
/>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<div className="border-secondary-200 text-secondary-400 flex items-center justify-between border-t pt-4 text-xs">
|
|
46
|
+
<span>{t('Language: {0}', locale.toUpperCase())}</span>
|
|
47
|
+
<button
|
|
48
|
+
type="button"
|
|
49
|
+
className="hover:text-primary-500 font-medium underline"
|
|
50
|
+
onClick={() => setLocale(locale === 'en' ? 'id' : 'en')}
|
|
51
|
+
>
|
|
52
|
+
{t('Switch to {0}', locale === 'en' ? 'Bahasa Indonesia' : 'English')}
|
|
53
|
+
</button>
|
|
54
|
+
</div>
|
|
55
|
+
</Card>
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { WangsUiProvider } from '@wangs-ui/react-core/api';
|
|
2
|
+
import { WangsUiI18nProvider } from '@wangs-ui/react-i18n';
|
|
3
|
+
import presetFixedAsset from '@wangs-ui/react-presets/fixedasset';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import ReactDOM from 'react-dom/client';
|
|
6
|
+
|
|
7
|
+
import App from './App';
|
|
8
|
+
|
|
9
|
+
import './index.css';
|
|
10
|
+
|
|
11
|
+
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
|
12
|
+
<React.StrictMode>
|
|
13
|
+
<WangsUiI18nProvider defaultLocale="en">
|
|
14
|
+
<WangsUiProvider configOptions={{ preset: presetFixedAsset }}>
|
|
15
|
+
<App />
|
|
16
|
+
</WangsUiProvider>
|
|
17
|
+
</WangsUiI18nProvider>
|
|
18
|
+
</React.StrictMode>,
|
|
19
|
+
);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
4
|
+
"target": "ES2022",
|
|
5
|
+
"useDefineForClassFields": true,
|
|
6
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
7
|
+
"module": "ESNext",
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
|
|
10
|
+
/* Bundler mode */
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"allowImportingTsExtensions": false,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"moduleDetection": "force",
|
|
15
|
+
"noEmit": true,
|
|
16
|
+
"jsx": "react-jsx",
|
|
17
|
+
|
|
18
|
+
/* Linting */
|
|
19
|
+
"strict": true,
|
|
20
|
+
"noUnusedLocals": true,
|
|
21
|
+
"noUnusedParameters": true,
|
|
22
|
+
"noFallthroughCasesInSwitch": true,
|
|
23
|
+
"noUncheckedSideEffectImports": true
|
|
24
|
+
},
|
|
25
|
+
"include": ["src"]
|
|
26
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
|
4
|
+
"target": "ES2022",
|
|
5
|
+
"lib": ["ES2022"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
|
|
9
|
+
/* Bundler mode */
|
|
10
|
+
"moduleResolution": "bundler",
|
|
11
|
+
"allowImportingTsExtensions": false,
|
|
12
|
+
"isolatedModules": true,
|
|
13
|
+
"moduleDetection": "force",
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
|
|
16
|
+
/* Linting */
|
|
17
|
+
"strict": true,
|
|
18
|
+
"noUnusedLocals": true,
|
|
19
|
+
"noUnusedParameters": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true,
|
|
21
|
+
"noUncheckedSideEffectImports": true
|
|
22
|
+
},
|
|
23
|
+
"include": ["vite.config.ts", "oxlint.config.ts", "oxfmt.config.ts"]
|
|
24
|
+
}
|