@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 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,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "wangs-ui": {
4
+ "command": "npx",
5
+ "args": ["-y", "@wangs-ui/mcp@latest"]
6
+ }
7
+ }
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "wangs-ui": {
4
+ "command": "npx",
5
+ "args": ["-y", "@wangs-ui/mcp@latest"]
6
+ }
7
+ }
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "wangs-ui": {
4
+ "command": "npx",
5
+ "args": ["-y", "@wangs-ui/mcp@latest"]
6
+ }
7
+ }
8
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "$schema": "https://opencode.ai/config.json",
3
+ "mcp": {
4
+ "wangs-ui": {
5
+ "type": "local",
6
+ "command": ["npx", "-y", "@wangs-ui/mcp@latest"]
7
+ }
8
+ }
9
+ }
@@ -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,3 @@
1
+ import { oxfmtConfig } from '@fewangsit/oxlint-config-react';
2
+
3
+ export default oxfmtConfig;
@@ -0,0 +1,3 @@
1
+ import { oxlintConfig } from '@fewangsit/oxlint-config-react';
2
+
3
+ export default oxlintConfig;
@@ -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,5 @@
1
+ @import '@wangs-ui/react-presets/fixedasset/style.css';
2
+ @import '@wangs-ui/react-presets/theme.css';
3
+ @import '@wangs-ui/react-icons/style.css';
4
+
5
+ @source "../node_modules/@wangs-ui/react-presets";
@@ -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,4 @@
1
+ {
2
+ "files": [],
3
+ "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }]
4
+ }
@@ -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
+ }
@@ -0,0 +1,8 @@
1
+ import tailwindcss from '@tailwindcss/vite';
2
+ import react from '@vitejs/plugin-react';
3
+ import { defineConfig } from 'vite';
4
+
5
+ // https://vite.dev/config/
6
+ export default defineConfig({
7
+ plugins: [react(), tailwindcss()],
8
+ });