@waveinno/ui 0.7.0 → 0.9.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/CHANGELOG.md +37 -0
- package/LICENSE +1 -1
- package/README.md +20 -7
- package/bin/starter-files.d.mts +8 -0
- package/bin/starter-files.mjs +136 -0
- package/bin/waveinno-ui.mjs +466 -0
- package/dist/components/ui/bd-address.d.ts +30 -0
- package/dist/components/ui/bd-address.js +1 -0
- package/dist/components/ui/checkbox-card.d.ts +18 -0
- package/dist/components/ui/checkbox-card.js +1 -0
- package/dist/components/ui/context-menu.d.ts +32 -0
- package/dist/components/ui/context-menu.js +1 -0
- package/dist/components/ui/index.d.ts +9 -0
- package/dist/components/ui/index.js +1 -1
- package/dist/components/ui/input.d.ts +5 -0
- package/dist/components/ui/input.js +1 -1
- package/dist/components/ui/multi-select.d.ts +39 -0
- package/dist/components/ui/multi-select.js +1 -0
- package/dist/components/ui/nid-input.d.ts +23 -0
- package/dist/components/ui/nid-input.js +1 -0
- package/dist/components/ui/payment-method-picker.d.ts +34 -0
- package/dist/components/ui/payment-method-picker.js +1 -0
- package/dist/components/ui/phone-input.d.ts +26 -0
- package/dist/components/ui/phone-input.js +1 -0
- package/dist/components/ui/plate-input.d.ts +32 -0
- package/dist/components/ui/plate-input.js +1 -0
- package/dist/components/ui/sheet.d.ts +8 -3
- package/dist/components/ui/sheet.js +1 -1
- package/dist/components/ui/taka-input.d.ts +26 -0
- package/dist/components/ui/taka-input.js +1 -0
- package/dist/lib/bangla.d.ts +50 -0
- package/dist/lib/bangla.js +1 -0
- package/dist/lib/bd-geo.d.ts +39 -0
- package/dist/lib/bd-geo.js +1 -0
- package/dist/lib/index.d.ts +2 -0
- package/dist/lib/index.js +1 -1
- package/dist/styles.css +11 -0
- package/package.json +20 -3
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* npx @waveinno/ui init set up WAVEINNO UI in a Next.js or React (Vite) app
|
|
4
|
+
* npx @waveinno/ui doctor check a setup and say what is missing
|
|
5
|
+
*
|
|
6
|
+
* init asks what you are building (Next.js or React + Vite), creates the app
|
|
7
|
+
* if there is none, asks the currency and the package manager, shows every
|
|
8
|
+
* change it will make, and only then makes them: the package and Tailwind's
|
|
9
|
+
* plugin, the CSS, the providers, the layout or entry, exactly as the
|
|
10
|
+
* installation guides show.
|
|
11
|
+
*
|
|
12
|
+
* It never overwrites your own code: a file is replaced only while it is still
|
|
13
|
+
* the starter's default (create-next-app, create-vite), and then the old one is
|
|
14
|
+
* kept as <file>.bak. Anything else is left alone and you get the line to add.
|
|
15
|
+
*
|
|
16
|
+
* --yes takes the defaults without asking (for scripts and CI); every question
|
|
17
|
+
* also has a flag: --framework next|vite, --currency BDT, --pm npm|pnpm|yarn|bun,
|
|
18
|
+
* --name my-app. --dry-run shows the plan and stops. --skip-install, --cwd <dir>.
|
|
19
|
+
*/
|
|
20
|
+
import { spawnSync } from "node:child_process";
|
|
21
|
+
import fs from "node:fs";
|
|
22
|
+
import path from "node:path";
|
|
23
|
+
import readline from "node:readline";
|
|
24
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
25
|
+
|
|
26
|
+
import { FONTS_HREF, NEXT_GLOBALS_CSS, NEXT_LAYOUT, NEXT_PROVIDERS, VITE_CONFIG, VITE_CSS } from "./starter-files.mjs";
|
|
27
|
+
|
|
28
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
29
|
+
const PKG = JSON.parse(fs.readFileSync(path.join(HERE, "..", "package.json"), "utf8"));
|
|
30
|
+
const DOCS = "https://www.npmjs.com/package/@waveinno/ui";
|
|
31
|
+
|
|
32
|
+
/* ── output ─────────────────────────────────────────────────────────────── */
|
|
33
|
+
|
|
34
|
+
const color = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
35
|
+
const c = (code) => (s) => (color ? `\x1b[${code}m${s}\x1b[0m` : String(s));
|
|
36
|
+
const bold = c(1);
|
|
37
|
+
const dim = c(2);
|
|
38
|
+
const green = c(32);
|
|
39
|
+
const yellow = c(33);
|
|
40
|
+
const red = c(31);
|
|
41
|
+
const blue = c(34);
|
|
42
|
+
const cyan = c(36);
|
|
43
|
+
const magenta = c(35);
|
|
44
|
+
const say = (s = "") => console.log(s);
|
|
45
|
+
const done = (s) => say(` ${green("✓")} ${s}`);
|
|
46
|
+
const skip = (s) => say(` ${dim("·")} ${dim(s)}`);
|
|
47
|
+
const warn = (s) => say(` ${yellow("!")} ${s}`);
|
|
48
|
+
const heading = (s) => say(`\n ${bold(s)}`);
|
|
49
|
+
const fail = (s) => {
|
|
50
|
+
say(`\n ${red("✗")} ${s}\n`);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/* ── args ───────────────────────────────────────────────────────────────── */
|
|
55
|
+
|
|
56
|
+
const argv = process.argv.slice(2);
|
|
57
|
+
const flag = (name) => argv.includes(`--${name}`);
|
|
58
|
+
const opt = (name) => {
|
|
59
|
+
const i = argv.indexOf(`--${name}`);
|
|
60
|
+
return i >= 0 ? argv[i + 1] : undefined;
|
|
61
|
+
};
|
|
62
|
+
// The first word that is neither a flag nor the value of a flag that takes one.
|
|
63
|
+
const VALUED = new Set(["--cwd", "--pm", "--currency", "--framework", "--name"]);
|
|
64
|
+
const command = argv.find((a, i) => !a.startsWith("-") && !VALUED.has(argv[i - 1] ?? "")) ?? "help";
|
|
65
|
+
const interactive = process.stdin.isTTY && process.stdout.isTTY && !flag("yes");
|
|
66
|
+
let cwd = path.resolve(opt("cwd") ?? process.cwd());
|
|
67
|
+
/** While planning, nothing is written or installed; the same steps print what they would do. */
|
|
68
|
+
let dry = true;
|
|
69
|
+
|
|
70
|
+
/* ── prompts (no dependencies: readline in raw mode) ────────────────────── */
|
|
71
|
+
|
|
72
|
+
/** An arrow-key list. Returns the chosen value. */
|
|
73
|
+
function select(question, choices, initial = 0) {
|
|
74
|
+
return new Promise((resolve) => {
|
|
75
|
+
const out = process.stdout;
|
|
76
|
+
const inp = process.stdin;
|
|
77
|
+
let i = Math.max(0, initial);
|
|
78
|
+
const n = choices.length;
|
|
79
|
+
const line = (ch, on) => `\x1b[2K ${on ? cyan("❯") : " "} ${on ? bold(ch.label) : ch.label}${ch.hint ? ` ${dim(ch.hint)}` : ""}`;
|
|
80
|
+
out.write(`\n ${green("?")} ${bold(question)} ${dim("↑ ↓ to move, Enter to choose")}\n`);
|
|
81
|
+
out.write(`${choices.map((ch, k) => line(ch, k === i)).join("\n")}\n`);
|
|
82
|
+
out.write("\x1b[?25l");
|
|
83
|
+
readline.emitKeypressEvents(inp);
|
|
84
|
+
inp.setRawMode(true);
|
|
85
|
+
inp.resume();
|
|
86
|
+
const onKey = (_, key = {}) => {
|
|
87
|
+
if (key.ctrl && key.name === "c") {
|
|
88
|
+
inp.setRawMode(false);
|
|
89
|
+
out.write("\x1b[?25h\n");
|
|
90
|
+
process.exit(130);
|
|
91
|
+
}
|
|
92
|
+
if (key.name === "up" || key.name === "k") i = (i - 1 + n) % n;
|
|
93
|
+
else if (key.name === "down" || key.name === "j" || key.name === "tab") i = (i + 1) % n;
|
|
94
|
+
else if (key.name === "return") {
|
|
95
|
+
inp.off("keypress", onKey);
|
|
96
|
+
inp.setRawMode(false);
|
|
97
|
+
inp.pause();
|
|
98
|
+
// Fold the list into one answered line.
|
|
99
|
+
out.write(`\x1b[${n + 1}A\x1b[0J ${green("✔")} ${bold(question)} ${cyan(choices[i].label)}\n\x1b[?25h`);
|
|
100
|
+
resolve(choices[i].value);
|
|
101
|
+
return;
|
|
102
|
+
} else return;
|
|
103
|
+
out.write(`\x1b[${n}A${choices.map((ch, k) => line(ch, k === i)).join("\n")}\n`);
|
|
104
|
+
};
|
|
105
|
+
inp.on("keypress", onKey);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** A line of text, with a default. */
|
|
110
|
+
function ask(question, fallback) {
|
|
111
|
+
return new Promise((resolve) => {
|
|
112
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
113
|
+
rl.question(`\n ${green("?")} ${bold(question)} ${dim(`(${fallback})`)} `, (a) => {
|
|
114
|
+
rl.close();
|
|
115
|
+
resolve(a.trim() || fallback);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* ── the project ────────────────────────────────────────────────────────── */
|
|
121
|
+
|
|
122
|
+
const rel = (p) => path.relative(cwd, p).split(path.sep).join("/");
|
|
123
|
+
const exists = (p) => fs.existsSync(path.join(cwd, p));
|
|
124
|
+
const read = (p) => fs.readFileSync(path.join(cwd, p), "utf8");
|
|
125
|
+
const first = (...ps) => ps.find(exists);
|
|
126
|
+
|
|
127
|
+
function write(p, content, why = "wrote", note = "") {
|
|
128
|
+
if (dry) return say(` ${blue("+")} ${why} ${bold(p)} ${dim(note)}`);
|
|
129
|
+
fs.mkdirSync(path.dirname(path.join(cwd, p)), { recursive: true });
|
|
130
|
+
fs.writeFileSync(path.join(cwd, p), content);
|
|
131
|
+
done(`${why} ${bold(p)} ${dim(note)}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Replace a starter file, keeping the old one as .bak. */
|
|
135
|
+
function replace(p, content, why) {
|
|
136
|
+
if (!dry) fs.copyFileSync(path.join(cwd, p), path.join(cwd, `${p}.bak`));
|
|
137
|
+
write(p, content, why, `(the old one is kept as ${p}.bak)`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const readPackage = () => (exists("package.json") ? JSON.parse(read("package.json")) : null);
|
|
141
|
+
const deps = (pkg) => ({ ...pkg?.dependencies, ...pkg?.devDependencies });
|
|
142
|
+
const detect = (pkg) => (deps(pkg).next ? "next" : deps(pkg).vite ? "vite" : null);
|
|
143
|
+
|
|
144
|
+
function lockfilePm() {
|
|
145
|
+
if (exists("pnpm-lock.yaml")) return "pnpm";
|
|
146
|
+
if (exists("yarn.lock")) return "yarn";
|
|
147
|
+
if (exists("bun.lockb") || exists("bun.lock")) return "bun";
|
|
148
|
+
if (exists("package-lock.json")) return "npm";
|
|
149
|
+
const agent = process.env.npm_config_user_agent ?? "";
|
|
150
|
+
for (const m of ["pnpm", "yarn", "bun"]) if (agent.startsWith(m)) return m;
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function run(cmd, args, where = cwd) {
|
|
155
|
+
say(dim(` $ ${cmd} ${args.join(" ")}`));
|
|
156
|
+
// On Windows npm is a .cmd, which only runs through a shell; the arguments are names and flags, so one string is safe.
|
|
157
|
+
const r = process.platform === "win32" ? spawnSync(`${cmd} ${args.join(" ")}`, { cwd: where, stdio: "inherit", shell: true }) : spawnSync(cmd, args, { cwd: where, stdio: "inherit" });
|
|
158
|
+
return r.status === 0;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function install(pm, pkgs, dev) {
|
|
162
|
+
if (!pkgs.length) return;
|
|
163
|
+
const args = [pm === "npm" ? "install" : "add", ...(dev ? [pm === "bun" ? "-d" : "-D"] : []), ...pkgs];
|
|
164
|
+
if (dry) return say(` ${blue("+")} install ${bold(pkgs.join(", "))}${dev ? dim(" (dev)") : ""}`);
|
|
165
|
+
if (flag("skip-install")) return warn(`Run yourself: ${bold(`${pm} ${args.join(" ")}`)}`);
|
|
166
|
+
if (!run(pm, args)) fail(`${pm} ${args.join(" ")} failed. Run it yourself, then run init again.`);
|
|
167
|
+
done(`installed ${pkgs.join(", ")}`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** The @source line, relative to the CSS file. */
|
|
171
|
+
function sourceLine(cssFile) {
|
|
172
|
+
const from = path.dirname(path.join(cwd, cssFile));
|
|
173
|
+
const to = path.join(cwd, "node_modules", "@waveinno", "ui", "dist");
|
|
174
|
+
return `@source "${path.relative(from, to).split(path.sep).join("/")}";`;
|
|
175
|
+
}
|
|
176
|
+
const withSource = (css, cssFile) => css.replace(/@source "[^"]+";/, sourceLine(cssFile));
|
|
177
|
+
|
|
178
|
+
/** Add the currency line after the imports of a providers or entry file. */
|
|
179
|
+
function withCurrency(src, currency, importLine) {
|
|
180
|
+
if (!currency) return src;
|
|
181
|
+
const lines = src.split("\n");
|
|
182
|
+
let last = -1;
|
|
183
|
+
lines.forEach((l, k) => /^import /.test(l) && (last = k));
|
|
184
|
+
lines.splice(last + 1, 0, "", `// Amounts show in ${currency}. Use another ISO code, or remove this line to follow the app shell.`, `useSettings.setState({ overrideCode: "${currency}" });`);
|
|
185
|
+
return lines.join("\n").replace(importLine, importLine.replace("TooltipProvider }", "TooltipProvider, useSettings }"));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/* ── the steps (the same code plans and applies) ────────────────────────── */
|
|
189
|
+
|
|
190
|
+
function setupCss(file, starter, create) {
|
|
191
|
+
if (!file) return write(create, `${withSource(starter, create)}\n`, "write", "(there was no global stylesheet)");
|
|
192
|
+
const css = read(file);
|
|
193
|
+
if (css.includes("@waveinno/ui/styles.css")) {
|
|
194
|
+
if (!css.includes("@source")) warn(`${file} imports the styles but has no @source line; add: ${sourceLine(file)}`);
|
|
195
|
+
return skip(`${file} already imports @waveinno/ui`);
|
|
196
|
+
}
|
|
197
|
+
// A starter stylesheet (create-next-app, create-vite, old and new) sets fonts, colours and body
|
|
198
|
+
// layout that would fight the design system; the newer Vite one even reuses token names.
|
|
199
|
+
const STARTER = /Arial, Helvetica|--font-geist|#646cff|#aa3bff|--text-h:|--social-bg|place-items: center|^\s*@import "tailwindcss";\s*$/m;
|
|
200
|
+
const isStarter = STARTER.test(css) && css.length < 8000;
|
|
201
|
+
const next = withSource(starter, file);
|
|
202
|
+
if (isStarter || !css.trim()) return replace(file, `${next}\n`, "set up");
|
|
203
|
+
write(file, `${next}\n\n/* ── Your styles ── */\n${css.replace(/^@import ["']tailwindcss["'];\s*/m, "")}`, "add WAVEINNO UI to the top of");
|
|
204
|
+
// Your own variables with a token's name would recolour every component.
|
|
205
|
+
const clash = [...new Set([...css.matchAll(/(--(?:text|body|muted|border2?|primary|surface|canvas|raised|hover|accent|ink|brand-[12]|success|warning|danger|info)):/g)].map((m) => m[1]))];
|
|
206
|
+
if (clash.length) warn(`${file} sets ${clash.join(", ")}: the same names as WAVEINNO UI's tokens, so components change colour. Rename yours.`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function stepsNext(pm, currency) {
|
|
210
|
+
const appDir = exists("src/app") ? "src/app" : exists("app") ? "app" : null;
|
|
211
|
+
if (!appDir) fail("This Next.js app has no app/ directory. WAVEINNO UI's setup is for the App Router.");
|
|
212
|
+
const d = deps(readPackage());
|
|
213
|
+
|
|
214
|
+
heading("Packages");
|
|
215
|
+
if (d["@waveinno/ui"]) skip(`@waveinno/ui ${d["@waveinno/ui"]} is installed`);
|
|
216
|
+
install(pm, d["@waveinno/ui"] ? [] : [`@waveinno/ui@^${PKG.version}`], false);
|
|
217
|
+
install(pm, ["tailwindcss", "@tailwindcss/postcss"].filter((x) => !d[x]), true);
|
|
218
|
+
|
|
219
|
+
heading("PostCSS");
|
|
220
|
+
const postcss = first("postcss.config.mjs", "postcss.config.js", "postcss.config.cjs", "postcss.config.ts");
|
|
221
|
+
if (!postcss) write("postcss.config.mjs", `export default { plugins: { "@tailwindcss/postcss": {} } };\n`, "write");
|
|
222
|
+
else if (read(postcss).includes("@tailwindcss/postcss")) skip(`${postcss} loads Tailwind`);
|
|
223
|
+
else warn(`${postcss} does not load Tailwind 4. Add: plugins: { "@tailwindcss/postcss": {} }`);
|
|
224
|
+
|
|
225
|
+
heading("CSS");
|
|
226
|
+
setupCss(first(`${appDir}/globals.css`, `${appDir}/global.css`), NEXT_GLOBALS_CSS, `${appDir}/globals.css`);
|
|
227
|
+
|
|
228
|
+
heading("Providers");
|
|
229
|
+
const providers = first(`${appDir}/providers.tsx`, `${appDir}/providers.jsx`);
|
|
230
|
+
if (!providers) write(`${appDir}/providers.tsx`, `${withCurrency(NEXT_PROVIDERS, currency, 'import { Toaster, TooltipProvider } from "@waveinno/ui";')}\n`, "write", "router, tooltips, toasts" + (currency ? `, amounts in ${currency}` : ""));
|
|
231
|
+
else if (read(providers).includes("@waveinno/ui")) skip(`${providers} already uses @waveinno/ui`);
|
|
232
|
+
else warn(`${providers} is your own: wrap its children in <NextRouterProvider> and <TooltipProvider>, and add <Toaster />.`);
|
|
233
|
+
|
|
234
|
+
heading("Layout");
|
|
235
|
+
const layout = first(`${appDir}/layout.tsx`, `${appDir}/layout.jsx`);
|
|
236
|
+
if (!layout) return write(`${appDir}/layout.tsx`, `${NEXT_LAYOUT}\n`, "write");
|
|
237
|
+
const src = read(layout);
|
|
238
|
+
if (src.includes("themeBootScript") && src.includes("Providers")) return skip(`${layout} is set up`);
|
|
239
|
+
if (/Geist|Create Next App/.test(src) && src.length < 2000) {
|
|
240
|
+
const title = /title:\s*["']([^"']+)["']/.exec(src)?.[1];
|
|
241
|
+
return replace(layout, `${NEXT_LAYOUT.replace('"My app"', JSON.stringify(title && title !== "Create Next App" ? title : "My app"))}\n`, "set up");
|
|
242
|
+
}
|
|
243
|
+
warn(`${layout} is your own, so it stays as it is. Add three things:`);
|
|
244
|
+
say(dim(` import { themeBootScript } from "@waveinno/ui"; import { Providers } from "./providers";`));
|
|
245
|
+
say(dim(` <html suppressHydrationWarning> and <head><script dangerouslySetInnerHTML={{ __html: themeBootScript() }} /></head>`));
|
|
246
|
+
say(dim(` <body><Providers>{children}</Providers></body>`));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async function stepsVite(pm, currency) {
|
|
250
|
+
const d = deps(readPackage());
|
|
251
|
+
|
|
252
|
+
heading("Packages");
|
|
253
|
+
if (d["@waveinno/ui"]) skip(`@waveinno/ui ${d["@waveinno/ui"]} is installed`);
|
|
254
|
+
install(pm, d["@waveinno/ui"] ? [] : [`@waveinno/ui@^${PKG.version}`], false);
|
|
255
|
+
install(pm, ["tailwindcss", "@tailwindcss/vite"].filter((x) => !d[x]), true);
|
|
256
|
+
|
|
257
|
+
heading("Vite");
|
|
258
|
+
const config = first("vite.config.ts", "vite.config.js", "vite.config.mts", "vite.config.mjs");
|
|
259
|
+
if (!config) write("vite.config.ts", `${VITE_CONFIG}\n`, "write");
|
|
260
|
+
else {
|
|
261
|
+
const src = read(config);
|
|
262
|
+
if (src.includes("@tailwindcss/vite")) skip(`${config} loads Tailwind`);
|
|
263
|
+
else if (/plugins:\s*\[react\(\)\]/.test(src)) write(config, `import tailwindcss from "@tailwindcss/vite";\n${src.replace(/plugins:\s*\[react\(\)\]/, "plugins: [react(), tailwindcss()]")}`, "add Tailwind to");
|
|
264
|
+
else warn(`Add tailwindcss() from "@tailwindcss/vite" to the plugins in ${config}.`);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
heading("CSS");
|
|
268
|
+
setupCss(first("src/index.css", "src/main.css", "src/styles.css"), VITE_CSS, "src/index.css");
|
|
269
|
+
const entry = first("src/main.tsx", "src/main.jsx");
|
|
270
|
+
if (entry && !/import ["']\.\/(index|main|styles)\.css["']/.test(read(entry))) warn(`Import the stylesheet once in ${entry}: import "./index.css";`);
|
|
271
|
+
|
|
272
|
+
heading("index.html");
|
|
273
|
+
if (!exists("index.html")) warn("No index.html: add the fonts and the theme script from the guide to your page.");
|
|
274
|
+
else {
|
|
275
|
+
let html = read("index.html");
|
|
276
|
+
if (html.includes("waveinno.theme") || html.includes("Noto+Sans+Bengali")) skip("index.html is set up");
|
|
277
|
+
else {
|
|
278
|
+
// The same script as themeBootScript(), from the package itself.
|
|
279
|
+
const { themeBootScript } = await import(pathToFileURL(path.join(HERE, "..", "dist", "lib", "boot-script.js")).href);
|
|
280
|
+
const head = ` <link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
281
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
282
|
+
<link rel="stylesheet" href="${FONTS_HREF}" />
|
|
283
|
+
<script>${themeBootScript()}</script>
|
|
284
|
+
</head>`;
|
|
285
|
+
html = html.replace(/\s*<\/head>/, `\n${head}`);
|
|
286
|
+
if (/<body(?![^>]*class=)/.test(html)) html = html.replace(/<body/, '<body class="bg-canvas text-body antialiased"');
|
|
287
|
+
write("index.html", html, "add the fonts and the theme script to");
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
heading("Entry");
|
|
292
|
+
const main = first("src/main.tsx", "src/main.jsx");
|
|
293
|
+
if (!main) return warn("No src/main.tsx: wrap your app in <TooltipProvider> and add <Toaster /> once.");
|
|
294
|
+
const src = read(main);
|
|
295
|
+
if (src.includes("@waveinno/ui")) return skip(`${main} already uses @waveinno/ui`);
|
|
296
|
+
if (!/<App \/>/.test(src)) return warn(`Wrap your app in <TooltipProvider> and add <Toaster /> once in ${main}.`);
|
|
297
|
+
const imp = 'import { Toaster, TooltipProvider } from "@waveinno/ui";';
|
|
298
|
+
const wrapped = src.replace(/(\s*)<App \/>/, "$1<TooltipProvider delayDuration={300}>$1 <App />$1 <Toaster />$1</TooltipProvider>");
|
|
299
|
+
write(main, withCurrency(`${imp}\n${wrapped}`, currency, imp), "wrap the app with tooltips and toasts in", currency ? `(amounts in ${currency})` : "");
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/* ── init ───────────────────────────────────────────────────────────────── */
|
|
303
|
+
|
|
304
|
+
const FRAMEWORKS = [
|
|
305
|
+
{ value: "next", label: "Next.js", hint: "App Router, server rendering, routing built in" },
|
|
306
|
+
{ value: "vite", label: "React + Vite", hint: "a single-page app, quick to start" },
|
|
307
|
+
];
|
|
308
|
+
const CURRENCIES = [
|
|
309
|
+
{ value: "BDT", label: "৳ BDT", hint: "Bangladeshi taka" },
|
|
310
|
+
{ value: "USD", label: "$ USD", hint: "US dollar" },
|
|
311
|
+
{ value: "INR", label: "₹ INR", hint: "Indian rupee" },
|
|
312
|
+
{ value: "EUR", label: "€ EUR", hint: "Euro" },
|
|
313
|
+
{ value: "", label: "Follow the app shell", hint: "set it per organisation later" },
|
|
314
|
+
];
|
|
315
|
+
const PMS = ["npm", "pnpm", "yarn", "bun"];
|
|
316
|
+
|
|
317
|
+
async function init() {
|
|
318
|
+
say(`\n ${magenta("◆")} ${bold("WAVEINNO UI")} ${dim(`v${PKG.version}`)} ${dim("— the setup")}`);
|
|
319
|
+
say(dim(` ${cwd}`));
|
|
320
|
+
|
|
321
|
+
// 1. What it is, or what to make.
|
|
322
|
+
let pkg = readPackage();
|
|
323
|
+
const found = detect(pkg);
|
|
324
|
+
let framework = opt("framework") ?? found;
|
|
325
|
+
if (!framework && !interactive) fail("No Next.js or Vite app here. Run init in your app's folder, or pass --framework next|vite to create one.");
|
|
326
|
+
if (interactive && !opt("framework")) {
|
|
327
|
+
framework = await select(
|
|
328
|
+
pkg ? "What is this app built with?" : "What would you like to build?",
|
|
329
|
+
FRAMEWORKS.map((f) => ({ ...f, label: f.value === found ? `${f.label} ${green("(detected)")}` : f.label })),
|
|
330
|
+
found ? FRAMEWORKS.findIndex((f) => f.value === found) : 0,
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
if (!["next", "vite"].includes(framework)) fail(`--framework takes next or vite, not "${framework}".`);
|
|
334
|
+
if (found && framework !== found) warn(`This folder is a ${found === "next" ? "Next.js" : "Vite"} app; setting it up as ${framework === "next" ? "Next.js" : "Vite"} may not work.`);
|
|
335
|
+
|
|
336
|
+
// 2. The package manager: from the lockfile, else ask.
|
|
337
|
+
let pm = opt("pm") ?? lockfilePm();
|
|
338
|
+
if (!pm) pm = interactive ? await select("Which package manager?", PMS.map((p) => ({ value: p, label: p }))) : "npm";
|
|
339
|
+
if (!PMS.includes(pm)) fail(`--pm takes ${PMS.join(", ")}.`);
|
|
340
|
+
|
|
341
|
+
// 3. No app yet: make one with the official starter, then set it up.
|
|
342
|
+
if (!pkg) {
|
|
343
|
+
const name = opt("name") ?? (interactive ? await ask("Name of the new app?", "my-app") : "my-app");
|
|
344
|
+
if (fs.existsSync(path.join(cwd, name))) fail(`${name} already exists here. Choose another name, or run init inside it.`);
|
|
345
|
+
if (flag("dry-run")) {
|
|
346
|
+
heading("Plan");
|
|
347
|
+
say(` ${blue("+")} create ${bold(name)} with ${framework === "next" ? "create-next-app" : "create-vite"}, then set it up`);
|
|
348
|
+
return say();
|
|
349
|
+
}
|
|
350
|
+
heading(`Creating ${name}`);
|
|
351
|
+
const ok =
|
|
352
|
+
framework === "next"
|
|
353
|
+
? run("npx", ["--yes", "create-next-app@latest", name, "--ts", "--tailwind", "--app", "--eslint", "--yes", `--use-${pm}`])
|
|
354
|
+
: run("npx", ["--yes", "create-vite@latest", name, "--template", "react-ts"]) && run(pm, ["install"], path.join(cwd, name));
|
|
355
|
+
if (!ok) fail("Creating the app failed; see the output above.");
|
|
356
|
+
cwd = path.join(cwd, name);
|
|
357
|
+
pkg = readPackage();
|
|
358
|
+
if (!pkg) fail(`The starter did not create ${name}/package.json.`);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// 4. The currency amounts show in.
|
|
362
|
+
const tzBangladesh = Intl.DateTimeFormat().resolvedOptions().timeZone === "Asia/Dhaka";
|
|
363
|
+
let currency = opt("currency");
|
|
364
|
+
if (currency === undefined) {
|
|
365
|
+
currency = interactive ? await select("Which currency do amounts show in?", CURRENCIES, tzBangladesh ? 0 : 1) : tzBangladesh ? "BDT" : "";
|
|
366
|
+
}
|
|
367
|
+
currency = currency.toUpperCase().replace(/[^A-Z]/g, "");
|
|
368
|
+
|
|
369
|
+
// 5. The plan: the same steps, printing instead of doing.
|
|
370
|
+
say(`\n ${bold("The plan")} ${dim(`${framework === "next" ? "Next.js" : "React + Vite"} · ${pm}${currency ? ` · ${currency}` : ""}`)}`);
|
|
371
|
+
dry = true;
|
|
372
|
+
if (framework === "next") stepsNext(pm, currency);
|
|
373
|
+
else await stepsVite(pm, currency);
|
|
374
|
+
if (flag("dry-run")) return say(dim("\n Dry run: nothing was changed.\n"));
|
|
375
|
+
|
|
376
|
+
if (interactive && !(await select("Set it up?", [{ value: true, label: "Yes, make these changes" }, { value: false, label: "No, leave everything as it is" }]))) {
|
|
377
|
+
return say(dim("\n Nothing was changed.\n"));
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// 6. Do it.
|
|
381
|
+
say(`\n ${bold("Setting up")}`);
|
|
382
|
+
dry = false;
|
|
383
|
+
if (framework === "next") stepsNext(pm, currency);
|
|
384
|
+
else await stepsVite(pm, currency);
|
|
385
|
+
|
|
386
|
+
const runDev = pm === "npm" ? "npm run dev" : `${pm} dev`;
|
|
387
|
+
const into = path.relative(process.cwd(), cwd);
|
|
388
|
+
say(`\n ${green("◆")} ${bold("Ready.")}`);
|
|
389
|
+
say(` ${into ? `cd ${into} && ` : ""}${bold(runDev)}`);
|
|
390
|
+
say(dim(` import { Button, toast } from "@waveinno/ui";`));
|
|
391
|
+
say(dim(` Check the setup any time: npx @waveinno/ui doctor · ${DOCS}\n`));
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/* ── doctor ─────────────────────────────────────────────────────────────── */
|
|
395
|
+
|
|
396
|
+
function doctor() {
|
|
397
|
+
say(`\n ${magenta("◆")} ${bold("WAVEINNO UI doctor")} ${dim(cwd)}\n`);
|
|
398
|
+
const pkg = readPackage();
|
|
399
|
+
if (!pkg) fail(`No package.json in ${cwd}.`);
|
|
400
|
+
const d = deps(pkg);
|
|
401
|
+
const fw = detect(pkg);
|
|
402
|
+
let bad = 0;
|
|
403
|
+
const check = (ok, good, fix) => {
|
|
404
|
+
if (ok) done(good);
|
|
405
|
+
else {
|
|
406
|
+
bad++;
|
|
407
|
+
say(` ${red("✗")} ${fix}`);
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
check(!!fw, `${fw === "next" ? "Next.js" : "Vite"} app`, "Not a Next.js or Vite app");
|
|
411
|
+
check(!!d["@waveinno/ui"], `@waveinno/ui ${d["@waveinno/ui"] ?? ""}`, "@waveinno/ui is not installed: npm install @waveinno/ui");
|
|
412
|
+
check(/^\^?4|^latest/.test(d.tailwindcss ?? ""), `tailwindcss ${d.tailwindcss}`, "Tailwind CSS 4 is not installed");
|
|
413
|
+
if (fw === "next") check(!!d["@tailwindcss/postcss"], "@tailwindcss/postcss", "@tailwindcss/postcss is missing: npm install -D @tailwindcss/postcss");
|
|
414
|
+
if (fw === "vite") check(!!d["@tailwindcss/vite"], "@tailwindcss/vite", "@tailwindcss/vite is missing: npm install -D @tailwindcss/vite");
|
|
415
|
+
|
|
416
|
+
const cssFile = fw === "next" ? first("src/app/globals.css", "app/globals.css") : first("src/index.css", "src/main.css", "src/styles.css");
|
|
417
|
+
const css = cssFile ? read(cssFile) : "";
|
|
418
|
+
check(css.includes("@waveinno/ui/styles.css"), `${cssFile} imports @waveinno/ui/styles.css`, `${cssFile ?? "Your global CSS"} does not import @waveinno/ui/styles.css`);
|
|
419
|
+
const src = /@source "([^"]+)";/.exec(css)?.[1];
|
|
420
|
+
const resolved = src && cssFile ? path.resolve(path.dirname(path.join(cwd, cssFile)), src) : null;
|
|
421
|
+
check(
|
|
422
|
+
!!resolved && fs.existsSync(resolved),
|
|
423
|
+
`@source points at ${resolved ? rel(resolved) : ""}`,
|
|
424
|
+
src ? `@source "${src}" does not exist from ${cssFile}; it should be ${sourceLine(cssFile)}` : `No @source line: add ${cssFile ? sourceLine(cssFile) : '@source "…/node_modules/@waveinno/ui/dist";'}`,
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
if (fw === "next") {
|
|
428
|
+
const appDir = exists("src/app") ? "src/app" : "app";
|
|
429
|
+
const layout = first(`${appDir}/layout.tsx`, `${appDir}/layout.jsx`);
|
|
430
|
+
check(!!layout && read(layout).includes("themeBootScript"), "The theme script runs before paint", `${layout ?? "layout"}: add themeBootScript() in <head>, or dark mode flashes`);
|
|
431
|
+
const providers = first(`${appDir}/providers.tsx`, `${appDir}/providers.jsx`);
|
|
432
|
+
const p = providers ? read(providers) : "";
|
|
433
|
+
check(p.includes("Toaster"), "Toaster is mounted", "No <Toaster />: toasts will not show");
|
|
434
|
+
check(p.includes("NextRouterProvider"), "Links use the Next.js router", "No <NextRouterProvider>: links in the shell reload the page");
|
|
435
|
+
} else if (fw === "vite") {
|
|
436
|
+
const html = exists("index.html") ? read("index.html") : "";
|
|
437
|
+
check(html.includes("waveinno.theme"), "The theme script runs before paint", "index.html: add the theme script, or dark mode flashes");
|
|
438
|
+
const main = first("src/main.tsx", "src/main.jsx");
|
|
439
|
+
check(!!main && read(main).includes("Toaster"), "Toaster is mounted", "No <Toaster />: toasts will not show");
|
|
440
|
+
}
|
|
441
|
+
say(bad ? `\n ${yellow(`${bad} to fix.`)} ${dim("npx @waveinno/ui init sets these up.")}\n` : `\n ${green(bold("All good."))}\n`);
|
|
442
|
+
process.exit(bad ? 1 : 0);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/* ── main ───────────────────────────────────────────────────────────────── */
|
|
446
|
+
|
|
447
|
+
if (flag("version") || command === "version") say(PKG.version);
|
|
448
|
+
else if (command === "init") await init();
|
|
449
|
+
else if (command === "doctor") doctor();
|
|
450
|
+
else {
|
|
451
|
+
say(`
|
|
452
|
+
${magenta("◆")} ${bold("WAVEINNO UI")} ${dim(`v${PKG.version}`)}
|
|
453
|
+
|
|
454
|
+
${bold("npx @waveinno/ui init")} set up a Next.js or React (Vite) app, or create one
|
|
455
|
+
${bold("npx @waveinno/ui doctor")} check the setup
|
|
456
|
+
|
|
457
|
+
${dim("--yes take the defaults, ask nothing")}
|
|
458
|
+
${dim("--framework next next or vite")}
|
|
459
|
+
${dim("--currency BDT the currency amounts show in (BDT is the default in Bangladesh)")}
|
|
460
|
+
${dim("--pm npm npm, pnpm, yarn or bun (default: from the lockfile)")}
|
|
461
|
+
${dim("--name my-app the name of a new app")}
|
|
462
|
+
${dim("--dry-run show the plan, change nothing")}
|
|
463
|
+
${dim("--skip-install print the install commands instead of running them")}
|
|
464
|
+
${dim("--cwd <dir> the folder (default: here)")}
|
|
465
|
+
`);
|
|
466
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* BdAddressPicker — a Bangladeshi address: division, district (only those of
|
|
4
|
+
* the chosen division), then the area and postcode as text.
|
|
5
|
+
*
|
|
6
|
+
* Every name is in English and Bangla (`lang`); the value holds ids
|
|
7
|
+
* ("dhaka", "cox-s-bazar"), so a stored address does not change with the
|
|
8
|
+
* language. Changing the division clears a district that is not in it.
|
|
9
|
+
*/
|
|
10
|
+
export interface BdAddress {
|
|
11
|
+
division?: string;
|
|
12
|
+
district?: string;
|
|
13
|
+
/** Thana, area, road and house: free text. */
|
|
14
|
+
area?: string;
|
|
15
|
+
/** Four digits. */
|
|
16
|
+
postcode?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface BdAddressPickerProps {
|
|
19
|
+
value?: BdAddress;
|
|
20
|
+
defaultValue?: BdAddress;
|
|
21
|
+
onValueChange?: (address: BdAddress) => void;
|
|
22
|
+
lang?: "en" | "bn";
|
|
23
|
+
/** Which text fields follow the two lists. Default both. */
|
|
24
|
+
fields?: ("area" | "postcode")[];
|
|
25
|
+
disabled?: boolean;
|
|
26
|
+
required?: boolean;
|
|
27
|
+
labels?: Partial<Record<"division" | "district" | "area" | "postcode" | "chooseDivision" | "chooseDistrict" | "divisionFirst", string>>;
|
|
28
|
+
className?: string;
|
|
29
|
+
}
|
|
30
|
+
export declare function BdAddressPicker({ value, defaultValue, onValueChange, lang, fields, disabled, required, labels, className }: BdAddressPickerProps): React.JSX.Element;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";import{jsx as i,jsxs as p}from"react/jsx-runtime";import*as g from"react";import{BD_DIVISIONS as x,bdDivision as h}from"../../lib/bd-geo.js";import{cn as $}from"../../lib/utils.js";import{FormField as a}from"./form-field.js";import{Input as b}from"./input.js";import{Select as f,SelectContent as S,SelectItem as F,SelectTrigger as D,SelectValue as C}from"./select.js";const y={division:"Division",district:"District",area:"Area, road and house",postcode:"Postcode",chooseDivision:"Choose a division",chooseDistrict:"Choose a district",divisionFirst:"Choose the division first"},T={division:"\u09AC\u09BF\u09AD\u09BE\u0997",district:"\u099C\u09C7\u09B2\u09BE",area:"\u098F\u09B2\u09BE\u0995\u09BE, \u09B0\u09BE\u09B8\u09CD\u09A4\u09BE \u0993 \u09AC\u09BE\u09A1\u09BC\u09BF",postcode:"\u09AA\u09CB\u09B8\u09CD\u099F \u0995\u09CB\u09A1",chooseDivision:"\u09AC\u09BF\u09AD\u09BE\u0997 \u09AC\u09BE\u099B\u09C1\u09A8",chooseDistrict:"\u099C\u09C7\u09B2\u09BE \u09AC\u09BE\u099B\u09C1\u09A8",divisionFirst:"\u0986\u0997\u09C7 \u09AC\u09BF\u09AD\u09BE\u0997 \u09AC\u09BE\u099B\u09C1\u09A8"};function _({value:u,defaultValue:B={},onValueChange:A,lang:c="en",fields:l=["area","postcode"],disabled:d,required:m,labels:I,className:N}){const[P,V]=g.useState(B),s=u??P,o={...c==="bn"?T:y,...I},r=g.useId(),v=s.division?h(s.division):void 0,n=e=>{const t={...s,...e};e.division&&e.division!==s.division&&!h(e.division)?.districts.some(k=>k.id===s.district)&&(t.district=void 0),u===void 0&&V(t),A?.(t)};return p("div",{className:$("grid gap-4 sm:grid-cols-2",N),children:[i(a,{label:o.division,required:m,children:p(f,{value:s.division??"",onValueChange:e=>n({division:e}),disabled:d,children:[i(D,{"aria-label":o.division,children:i(C,{placeholder:o.chooseDivision})}),i(S,{children:x.map(e=>i(F,{value:e.id,children:c==="bn"?e.bn:e.en},e.id))})]})}),i(a,{label:o.district,required:m,children:p(f,{value:s.district??"",onValueChange:e=>n({district:e}),disabled:d||!v,children:[i(D,{"aria-label":o.district,children:i(C,{placeholder:v?o.chooseDistrict:o.divisionFirst})}),i(S,{children:v?.districts.map(e=>i(F,{value:e.id,children:c==="bn"?e.bn:e.en},e.id))})]})}),l.includes("area")?i(a,{label:o.area,htmlFor:`${r}-area`,className:l.includes("postcode")?void 0:"sm:col-span-2",children:i(b,{id:`${r}-area`,value:s.area??"",onChange:e=>n({area:e.target.value}),disabled:d,autoComplete:"street-address"})}):null,l.includes("postcode")?i(a,{label:o.postcode,htmlFor:`${r}-post`,children:i(b,{id:`${r}-post`,value:s.postcode??"",onChange:e=>n({postcode:e.target.value.replace(/[০-৯]/g,t=>String("\u09E6\u09E7\u09E8\u09E9\u09EA\u09EB\u09EC\u09ED\u09EE\u09EF".indexOf(t))).replace(/\D/g,"").slice(0,4)}),inputMode:"numeric",autoComplete:"postal-code",placeholder:"1212",disabled:d,className:"num"})}):null]})}export{_ as BdAddressPicker};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
/**
|
|
4
|
+
* CheckboxCard — RadioCard's twin for choosing several: the whole card is the
|
|
5
|
+
* target, with an icon, a title, a line under it and something on the right (a
|
|
6
|
+
* price, a count). Add-ons, features to switch on, sites to include.
|
|
7
|
+
*
|
|
8
|
+
* It is a real checkbox (Space toggles it, screen readers say "checked"), so
|
|
9
|
+
* a grid of them needs no group wrapper; put them in any layout.
|
|
10
|
+
*/
|
|
11
|
+
export interface CheckboxCardProps extends Omit<React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>, "title"> {
|
|
12
|
+
title: React.ReactNode;
|
|
13
|
+
description?: React.ReactNode;
|
|
14
|
+
icon?: React.ReactNode;
|
|
15
|
+
/** On the right: a price, a time, a badge. */
|
|
16
|
+
aside?: React.ReactNode;
|
|
17
|
+
}
|
|
18
|
+
export declare const CheckboxCard: React.ForwardRefExoticComponent<CheckboxCardProps & React.RefAttributes<HTMLButtonElement>>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import*as i from"@radix-ui/react-checkbox";import{Check as l}from"lucide-react";import*as m from"react";import{cn as a}from"../../lib/utils.js";const b=m.forwardRef(({className:d,title:c,description:t,icon:o,aside:s,...n},p)=>r(i.Root,{ref:p,className:a("group relative flex w-full cursor-pointer items-start gap-3 rounded-[calc(12px*var(--radius-scale))] border border-border bg-raised p-3.5 pr-10 text-left outline-none","transition-[border-color,background-color,box-shadow] duration-200 hover:border-border2","focus-visible:ring-2 focus-visible:ring-primary/35","data-[state=checked]:border-primary data-[state=checked]:bg-[linear-gradient(180deg,var(--primary-subtle),transparent_120%)] data-[state=checked]:shadow-[0_0_0_3px_var(--primary-subtle),0_6px_16px_-8px_color-mix(in_srgb,var(--primary)_35%,transparent)]","disabled:cursor-not-allowed disabled:opacity-50",d),...n,children:[o?e("span",{className:a("grid size-9 shrink-0 place-items-center rounded-[calc(10px*var(--radius-scale))] border border-border bg-surface text-muted transition-colors duration-200 [&_svg]:size-[18px]","group-data-[state=checked]:border-primary/25 group-data-[state=checked]:bg-raised group-data-[state=checked]:text-primary"),children:o}):null,r("span",{className:"min-w-0 flex-1",children:[r("span",{className:"flex items-center justify-between gap-3",children:[e("span",{className:"truncate text-[13.5px] font-semibold text-text",children:c}),s?e("span",{className:"num shrink-0 text-[13px] font-semibold text-text",children:s}):null]}),t?e("span",{className:"mt-0.5 block text-[12px] leading-[17px] text-muted",children:t}):null]}),e("span",{"aria-hidden":!0,className:a("absolute right-3.5 top-3.5 grid size-[18px] place-items-center rounded-[5px] border border-border2 bg-raised text-white transition-[background-color,border-color] duration-200","group-data-[state=checked]:border-primary group-data-[state=checked]:bg-primary"),children:e(i.Indicator,{forceMount:!0,className:"grid place-items-center data-[state=unchecked]:hidden",children:e(l,{className:"size-3 animate-[wm-pop_180ms_var(--ease)]",strokeWidth:3})})})]}));b.displayName="CheckboxCard";export{b as CheckboxCard};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as Primitive from "@radix-ui/react-context-menu";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import type { MenuVariant } from "./dropdown-menu";
|
|
4
|
+
/**
|
|
5
|
+
* ContextMenu — the menu a right-click (or a long press on a phone) opens over
|
|
6
|
+
* a row, a card, a file: the actions for that thing, where the pointer is.
|
|
7
|
+
*
|
|
8
|
+
* It looks and reads exactly like DropdownMenu, with the same item options
|
|
9
|
+
* (`icon`, `description`, `shortcut`, `danger`) and surfaces (`default`,
|
|
10
|
+
* `glass`, `dark`), so a table can offer the same actions both ways. Keep the
|
|
11
|
+
* actions available elsewhere too: a right-click is never the only way in.
|
|
12
|
+
*/
|
|
13
|
+
export declare const ContextMenu: React.FC<Primitive.ContextMenuProps>;
|
|
14
|
+
export declare const ContextMenuTrigger: React.ForwardRefExoticComponent<Primitive.ContextMenuTriggerProps & React.RefAttributes<HTMLSpanElement>>;
|
|
15
|
+
export declare const ContextMenuGroup: React.ForwardRefExoticComponent<Primitive.ContextMenuGroupProps & React.RefAttributes<HTMLDivElement>>;
|
|
16
|
+
export declare const ContextMenuSub: React.FC<Primitive.ContextMenuSubProps>;
|
|
17
|
+
export declare const ContextMenuRadioGroup: React.ForwardRefExoticComponent<Primitive.ContextMenuRadioGroupProps & React.RefAttributes<HTMLDivElement>>;
|
|
18
|
+
export declare const ContextMenuContent: React.ForwardRefExoticComponent<Omit<Primitive.ContextMenuContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & {
|
|
19
|
+
variant?: MenuVariant;
|
|
20
|
+
} & React.RefAttributes<HTMLDivElement>>;
|
|
21
|
+
export declare const ContextMenuItem: React.ForwardRefExoticComponent<Omit<Primitive.ContextMenuItemProps & React.RefAttributes<HTMLDivElement>, "ref"> & {
|
|
22
|
+
danger?: boolean;
|
|
23
|
+
icon?: React.ReactNode;
|
|
24
|
+
description?: React.ReactNode;
|
|
25
|
+
shortcut?: string;
|
|
26
|
+
} & React.RefAttributes<HTMLDivElement>>;
|
|
27
|
+
export declare const ContextMenuCheckboxItem: React.ForwardRefExoticComponent<Omit<Primitive.ContextMenuCheckboxItemProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
28
|
+
export declare const ContextMenuRadioItem: React.ForwardRefExoticComponent<Omit<Primitive.ContextMenuRadioItemProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
29
|
+
export declare const ContextMenuLabel: ({ className, ...props }: React.ComponentPropsWithoutRef<typeof Primitive.Label>) => React.JSX.Element;
|
|
30
|
+
export declare const ContextMenuSeparator: ({ className, ...props }: React.ComponentPropsWithoutRef<typeof Primitive.Separator>) => React.JSX.Element;
|
|
31
|
+
export declare const ContextMenuSubTrigger: React.ForwardRefExoticComponent<Omit<Primitive.ContextMenuSubTriggerProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
32
|
+
export declare const ContextMenuSubContent: React.ForwardRefExoticComponent<Omit<Primitive.ContextMenuSubContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";import{jsx as o,jsxs as m}from"react/jsx-runtime";import*as e from"@radix-ui/react-context-menu";import{Check as c,ChevronRight as g}from"lucide-react";import*as s from"react";import{cn as i}from"../../lib/utils.js";const k=e.Root,S=e.Trigger,w=e.Group,W=e.Sub,T=e.RadioGroup,u="z-50 min-w-52 overflow-hidden rounded-dlg border border-border bg-raised p-1 shadow-elev3",l={default:"",glass:"border-white/40 bg-raised/70 backdrop-blur-xl backdrop-saturate-150 dark:border-white/10",dark:"dark border-white/10 bg-[var(--ink)] text-white [--hover:rgba(255,255,255,.08)] [--text:#f5f6fa] [--body:#d6d8e6] [--muted:#9aa1bd] [--border:rgba(255,255,255,.1)]"},x=s.createContext("default"),R=s.forwardRef(({className:r,variant:t="default",...n},a)=>o(x.Provider,{value:t,children:o(e.Portal,{children:o(e.Content,{ref:a,"data-pop":"",className:i(u,l[t],r),...n})})}));R.displayName="ContextMenuContent";const p="relative flex cursor-pointer select-none items-center gap-2.5 rounded-ctl px-2.5 py-2 text-[13px] text-body outline-none transition-colors data-[highlighted]:bg-hover data-[highlighted]:text-text data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0 [&_svg]:text-muted",h=s.forwardRef(({className:r,danger:t,icon:n,description:a,shortcut:d,children:f,...b},C)=>m(e.Item,{ref:C,className:i(p,a&&"items-start",t&&"text-danger data-[highlighted]:bg-danger-subtle data-[highlighted]:text-danger [&_svg]:text-danger",r),...b,children:[n?o("span",{className:i("grid shrink-0 place-items-center",a&&"mt-0.5"),children:n}):null,m("span",{className:"min-w-0 flex-1",children:[o("span",{className:"block truncate",children:f}),a?o("span",{className:"mt-0.5 block text-[11.5px] leading-4 text-muted",children:a}):null]}),d?o("kbd",{className:"ml-auto self-center rounded-[calc(5px*var(--radius-scale))] border border-border bg-surface px-1.5 py-px font-sans text-[10.5px] text-muted",children:d}):null]}));h.displayName="ContextMenuItem";const v=s.forwardRef(({className:r,children:t,...n},a)=>m(e.CheckboxItem,{ref:a,className:i(p,"pr-8",r),...n,children:[t,o(e.ItemIndicator,{className:"absolute right-2.5",children:o(c,{className:"size-3.5 !text-primary",strokeWidth:3})})]}));v.displayName="ContextMenuCheckboxItem";const P=s.forwardRef(({className:r,children:t,...n},a)=>m(e.RadioItem,{ref:a,className:i(p,"pr-8",r),...n,children:[t,o(e.ItemIndicator,{className:"absolute right-2.5",children:o(c,{className:"size-3.5 !text-primary",strokeWidth:3})})]}));P.displayName="ContextMenuRadioItem";const V=({className:r,...t})=>o(e.Label,{className:i("ov px-2.5 pb-1 pt-2",r),...t}),z=({className:r,...t})=>o(e.Separator,{className:i("-mx-1 my-1 h-px bg-border",r),...t}),y=s.forwardRef(({className:r,children:t,...n},a)=>m(e.SubTrigger,{ref:a,className:i(p,"data-[state=open]:bg-hover",r),...n,children:[t,o(g,{className:"ml-auto size-3.5"})]}));y.displayName="ContextMenuSubTrigger";const I=s.forwardRef(({className:r,...t},n)=>{const a=s.useContext(x);return o(e.Portal,{children:o(e.SubContent,{ref:n,"data-pop":"",className:i(u,l[a],r),...t})})});I.displayName="ContextMenuSubContent";export{k as ContextMenu,v as ContextMenuCheckboxItem,R as ContextMenuContent,w as ContextMenuGroup,h as ContextMenuItem,V as ContextMenuLabel,T as ContextMenuRadioGroup,P as ContextMenuRadioItem,z as ContextMenuSeparator,W as ContextMenuSub,I as ContextMenuSubContent,y as ContextMenuSubTrigger,S as ContextMenuTrigger};
|
|
@@ -45,3 +45,12 @@ export * from "./time-picker";
|
|
|
45
45
|
export * from "./toast";
|
|
46
46
|
export * from "./tooltip";
|
|
47
47
|
export * from "./tree-view";
|
|
48
|
+
export * from "./bd-address";
|
|
49
|
+
export * from "./checkbox-card";
|
|
50
|
+
export * from "./context-menu";
|
|
51
|
+
export * from "./multi-select";
|
|
52
|
+
export * from "./nid-input";
|
|
53
|
+
export * from "./payment-method-picker";
|
|
54
|
+
export * from "./phone-input";
|
|
55
|
+
export * from "./plate-input";
|
|
56
|
+
export * from "./taka-input";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export*from"./accordion.js";export*from"./alert.js";export*from"./avatar.js";export*from"./badge.js";export*from"./button.js";export*from"./calendar.js";export*from"./card.js";export*from"./carousel.js";export*from"./checkbox.js";export*from"./color-picker.js";export*from"./combobox.js";export*from"./confirm-dialog.js";export*from"./date-picker.js";export*from"./dialog.js";export*from"./dropdown-menu.js";export*from"./file-upload.js";export*from"./form-field.js";export*from"./hover-card.js";export*from"./input.js";export*from"./kbd.js";export*from"./label.js";export*from"./menu.js";export*from"./number-input.js";export*from"./pagination.js";export*from"./password-input.js";export*from"./pin-input.js";export*from"./popover.js";export*from"./progress.js";export*from"./radio-group.js";export*from"./rating.js";export*from"./scroll-area.js";export*from"./segmented.js";export*from"./select.js";export*from"./separator.js";export*from"./sheet.js";export*from"./skeleton.js";export*from"./slider.js";export*from"./spinner.js";export*from"./steps.js";export*from"./switch.js";export*from"./tabs.js";export*from"./tag-input.js";export*from"./time-picker.js";export*from"./toast.js";export*from"./tooltip.js";export*from"./tree-view.js";
|
|
1
|
+
export*from"./accordion.js";export*from"./alert.js";export*from"./avatar.js";export*from"./badge.js";export*from"./button.js";export*from"./calendar.js";export*from"./card.js";export*from"./carousel.js";export*from"./checkbox.js";export*from"./color-picker.js";export*from"./combobox.js";export*from"./confirm-dialog.js";export*from"./date-picker.js";export*from"./dialog.js";export*from"./dropdown-menu.js";export*from"./file-upload.js";export*from"./form-field.js";export*from"./hover-card.js";export*from"./input.js";export*from"./kbd.js";export*from"./label.js";export*from"./menu.js";export*from"./number-input.js";export*from"./pagination.js";export*from"./password-input.js";export*from"./pin-input.js";export*from"./popover.js";export*from"./progress.js";export*from"./radio-group.js";export*from"./rating.js";export*from"./scroll-area.js";export*from"./segmented.js";export*from"./select.js";export*from"./separator.js";export*from"./sheet.js";export*from"./skeleton.js";export*from"./slider.js";export*from"./spinner.js";export*from"./steps.js";export*from"./switch.js";export*from"./tabs.js";export*from"./tag-input.js";export*from"./time-picker.js";export*from"./toast.js";export*from"./tooltip.js";export*from"./tree-view.js";export*from"./bd-address.js";export*from"./checkbox-card.js";export*from"./context-menu.js";export*from"./multi-select.js";export*from"./nid-input.js";export*from"./payment-method-picker.js";export*from"./phone-input.js";export*from"./plate-input.js";export*from"./taka-input.js";
|
|
@@ -7,6 +7,11 @@ export declare const inputClass: string;
|
|
|
7
7
|
* (FormField does that).
|
|
8
8
|
*/
|
|
9
9
|
export type InputState = "default" | "error" | "warning" | "success";
|
|
10
|
+
/**
|
|
11
|
+
* The same states for a field made of several parts (a prefix, an input, a
|
|
12
|
+
* badge) where the focus ring belongs on the wrapper: PhoneInput, TakaInput….
|
|
13
|
+
*/
|
|
14
|
+
export declare const fieldStateClass: Record<InputState, string>;
|
|
10
15
|
interface Extra {
|
|
11
16
|
state?: InputState;
|
|
12
17
|
/** Icon or text inside the box, before the value (a search icon, "৳", "+880"). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use client";import{jsx as n,jsxs as m}from"react/jsx-runtime";import*as
|
|
1
|
+
"use client";import{jsx as n,jsxs as m}from"react/jsx-runtime";import*as o from"react";import{cn as d}from"../../lib/utils.js";const c="h-ctl w-full rounded-ctl border border-border2 bg-raised px-3 text-[13px] text-text placeholder:text-placeholder outline-none transition-[border-color,box-shadow] duration-150 focus:border-primary focus:ring-2 focus:ring-primary/20 disabled:cursor-not-allowed disabled:bg-hover disabled:text-muted aria-[invalid=true]:border-danger aria-[invalid=true]:ring-danger/20",h={default:"border-border2 focus-within:border-primary focus-within:ring-2 focus-within:ring-primary/20",error:"border-danger bg-danger-subtle/30 focus-within:ring-2 focus-within:ring-danger/20 wm-shake",warning:"border-warning focus-within:ring-2 focus-within:ring-warning/20",success:"border-success focus-within:ring-2 focus-within:ring-success/20"},u={default:"",error:"border-danger bg-danger-subtle/30 focus:border-danger focus:ring-danger/20 wm-shake",warning:"border-warning focus:border-warning focus:ring-warning/20",success:"border-success focus:border-success focus:ring-success/20"};function p({leading:r,trailing:e,children:t}){return!r&&!e?t:m("div",{className:"relative w-full",children:[r?n("span",{className:"pointer-events-none absolute left-3 top-1/2 flex -translate-y-1/2 items-center text-[13px] text-muted [&_svg]:size-4",children:r}):null,t,e?n("span",{className:"absolute right-2.5 top-1/2 flex -translate-y-1/2 items-center text-[13px] text-muted [&_svg]:size-4",children:e}):null]})}const b=o.forwardRef(({className:r,type:e="text",state:t="default",leading:a,trailing:i,style:f,...s},g)=>n(p,{leading:a,trailing:i,children:n("input",{ref:g,type:e,"aria-invalid":t==="error"?!0:s["aria-invalid"],className:d(c,u[t],a?"pl-9":null,i?"pr-10":null,r),style:{...l("paddingLeft",a),...l("paddingRight",i),...f},...s})}));function l(r,e){if(typeof e!="string"&&typeof e!="number")return;const t=String(e).length;return t>1?{[r]:`calc(${t}ch + 20px)`}:void 0}b.displayName="Input";const x=o.forwardRef(({className:r,state:e="default",...t},a)=>n("textarea",{ref:a,"aria-invalid":e==="error"?!0:t["aria-invalid"],className:d(c,"h-auto min-h-20 resize-y py-2 leading-5",u[e],r),...t}));x.displayName="Textarea";export{b as Input,x as Textarea,h as fieldStateClass,c as inputClass};
|