@waveinno/ui 0.8.0 → 1.0.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 CHANGED
@@ -6,6 +6,39 @@ prop, major = a renamed or removed export or prop.
6
6
 
7
7
  ## Unreleased
8
8
 
9
+ ## 1.0.0 — 2026-10-03
10
+
11
+ - **Breaking: four components move to Wavi Pro.** `OrgChart` (with `orgIds`, `OrgNode`), `KanbanBoard`, `AppLauncher`
12
+ (with `LauncherApp`) and `ColorPicker` / `ColorPickerPopover` are no longer exported by `@waveinno/ui`.
13
+ - **To keep using them:** run `npx @waveinno/ui add org-chart kanban app-launcher color-picker --key <licence>`.
14
+ It writes the source to `components/wavi-pro/`, installs `@radix-ui/react-popover` where needed, and prints the
15
+ new import lines. Then change `from "@waveinno/ui"` to those lines.
16
+ - **Unchanged:** the APIs, and the styles (they stay in `styles.css`).
17
+ - **Already-published versions:** up to 0.9.x stay MIT.
18
+ - **`npx @waveinno/ui add <id>`**: adds a Wavi Pro component to your project with your licence key (remembered after
19
+ the first success). `--list`, `--overwrite`, `--dir`, `--url`.
20
+ - Docs: the components menu is grouped by job (Actions, Forms, Date and time, Bangladesh, Overlays, Navigation…), with a
21
+ filter, sections that fold, and Pro and New badges; the components index uses the same groups. Pro component pages
22
+ show the live example to everyone and the source with a licence. The Kanban board has its own page.
23
+ - The CLI calls itself Wavi.
24
+
25
+ ## 0.9.0 — 2026-10-03
26
+
27
+ - **`npx @waveinno/ui init`**: one-command setup.
28
+ - It asks whether you build with Next.js or React + Vite, the currency (taka by default in Bangladesh) and the
29
+ package manager. It can create the app with the official starter.
30
+ - It shows the plan before changing anything, then installs the package and Tailwind CSS 4 and writes the
31
+ stylesheet, fonts, providers and theme script.
32
+ - Starter files are replaced and kept as `.bak`; your own files are left alone, with a note. It also warns when
33
+ your CSS reuses a token name (`--text`, `--border`…).
34
+ - `npx @waveinno/ui doctor` checks a setup. `--yes` and flags for CI.
35
+ - **Wavi**: the design system has a name and a mark. The docs site has a new home page, a "Powered by WAVEINNO" footer,
36
+ and search and AI-friendly metadata: sitemap, robots.txt, llms.txt, Open Graph image, and structured data
37
+ (SoftwareApplication, Organization, FAQPage).
38
+ - **Templates** download as Next.js **or React + Vite** (React Router, a page per route, loaded on demand).
39
+ - PhoneInput and NidInput adapt to their width: in a narrow column the operator or ID-kind label shrinks to its tick,
40
+ so the number keeps its room.
41
+
9
42
  ## 0.8.0 — 2026-10-03
10
43
 
11
44
  - **Bangladesh kit** (new, free):
package/LICENSE CHANGED
@@ -25,4 +25,4 @@ SOFTWARE.
25
25
  This licence covers the @waveinno/ui package: everything published to npm,
26
26
  and the free blocks in playground/blocks/free. The Pro blocks and templates
27
27
  (playground/blocks/pro) are not covered; they are licensed separately under
28
- the WAVEINNO UI Pro Licence in playground/blocks/pro/LICENSE.md.
28
+ the Wavi Pro Licence in playground/blocks/pro/LICENSE.md.
package/README.md CHANGED
@@ -1,13 +1,25 @@
1
- # @waveinno/ui
1
+ # Wavi — @waveinno/ui
2
2
 
3
- The WAVEINNO design system for React and Next.js: tokens, an application shell, primitives, patterns, charts and
4
- the brand, in light and dark, in English and Bangla. Shipped as compiled JavaScript with type definitions.
3
+ **Wavi** is the React design system from Bangladesh, made by WAVEINNO: 100+ components, an application shell, charts
4
+ and Bangladesh-ready inputs (bKash, taka, +880, Bangla dates), styled with Tailwind CSS 4 tokens, in light and dark, in
5
+ English and Bangla. For Next.js and Vite. Shipped as compiled JavaScript with type definitions.
6
+
7
+ ## Set up in one command
5
8
 
6
9
  ```bash
7
- npm install @waveinno/ui
10
+ npx @waveinno/ui init
8
11
  ```
9
12
 
10
- ## Set up
13
+ Run it in your app (or in an empty folder to create one). It asks whether you build with **Next.js** or **React +
14
+ Vite**, your currency and package manager, shows every change it will make, then installs the package and Tailwind
15
+ CSS 4 and writes the stylesheet, fonts, providers and theme script. It never overwrites your own files.
16
+ `npx @waveinno/ui doctor` checks a setup. In CI: `npx @waveinno/ui init --yes --framework next --currency BDT`.
17
+
18
+ ## Set up by hand
19
+
20
+ ```bash
21
+ npm install @waveinno/ui
22
+ ```
11
23
 
12
24
  Full guides, with every file: run the docs (`npm run dev`, then http://localhost:4500/docs) and open **Installation →
13
25
  Next.js** or **Vite**. In short:
@@ -63,10 +75,11 @@ through `labels` props, so each product passes its own language.
63
75
 
64
76
  | Group | Components |
65
77
  | --- | --- |
66
- | Primitives | Button (working + showcase variants), Badge (soft, solid, outline, dot, gradient, live), Input / Textarea (states, adornments), NumberInput, PasswordInput (strength, Caps Lock), PinInput (OTP), FileUpload, FormField, Select, Combobox (searchable), MultiSelect, Checkbox and CheckboxCard, RadioGroup and RadioCard, Switch, Slider (range, marks), Segmented, Tabs (underline, pill, solid, enclosed), Calendar, DatePicker, DateTimePicker, DateRangePicker, TimePicker, Dialog (drawer that widens or goes full width), Sheet (right, left, bottom with drag to close), ConfirmDialog, DropdownMenu (rich items, header, dark), ContextMenu, Menu (soft, line, raised, gradient, dark, plus the shell's panel and rail; branches, sliding highlight), Popover (arrow, dark, brand), HoverCard, Tooltip, Alert (five tones, four looks), Toaster and toast, Spinner (ring, dots, wave), Kbd, Accordion, Pagination (seven looks), Steps (six looks), TreeView, ScrollArea, TagInput, Rating, Carousel, ColorPicker, Progress / ProgressRing, Avatar, Skeleton |
78
+ | Primitives | Button (working + showcase variants), Badge (soft, solid, outline, dot, gradient, live), Input / Textarea (states, adornments), NumberInput, PasswordInput (strength, Caps Lock), PinInput (OTP), FileUpload, FormField, Select, Combobox (searchable), MultiSelect, Checkbox and CheckboxCard, RadioGroup and RadioCard, Switch, Slider (range, marks), Segmented, Tabs (underline, pill, solid, enclosed), Calendar, DatePicker, DateTimePicker, DateRangePicker, TimePicker, Dialog (drawer that widens or goes full width), Sheet (right, left, bottom with drag to close), ConfirmDialog, DropdownMenu (rich items, header, dark), ContextMenu, Menu (soft, line, raised, gradient, dark, plus the shell's panel and rail; branches, sliding highlight), Popover (arrow, dark, brand), HoverCard, Tooltip, Alert (five tones, four looks), Toaster and toast, Spinner (ring, dots, wave), Kbd, Accordion, Pagination (seven looks), Steps (six looks), TreeView, ScrollArea, TagInput, Rating, Carousel, Progress / ProgressRing, Avatar, Skeleton |
67
79
  | Bangladesh | PhoneInput (+880, operator), TakaInput (lakh grouping, words), BdAddressPicker (divisions and districts, English or Bangla), PaymentMethodPicker (bKash, Nagad, Rocket, Upay, card, cash, bank, COD), PlateInput, NidInput; helpers for Bangla digits, the Bangla calendar and lakh/crore |
68
- | Patterns | AppLauncher (switch-app button: grid, list, spotlight, glass, dark), OrgChart (card, gradient, avatar, compact, list), DataTable (advanced filters, saved views, five looks, inline and bulk edit, bulk delete, sticky actions), UserCell, RowActions, PageHero, PageHeader, StatCard, KpiCard, Money, ActivityFeed, ActivityTimeline, ChangeHistory, ChangeNotes, Note, Kanban, empty / error / loading states |
80
+ | Patterns | DataTable (advanced filters, saved views, five looks, inline and bulk edit, bulk delete, sticky actions), UserCell, RowActions, PageHero, PageHeader, StatCard, KpiCard, Money, ActivityFeed, ActivityTimeline, ChangeHistory, ChangeNotes, Note, empty / error / loading states |
69
81
  | Charts | TrendChart, BarChart, LineChart, DonutChart, GaugeChart, Heatmap, BarList, ChartCard, validated palette |
82
+ | **Wavi Pro** | OrgChart (card, gradient, avatar, compact, list), KanbanBoard, AppLauncher (grid, list, spotlight, glass, dark), ColorPicker. Not in this package: added to your project with `npx @waveinno/ui add <id> --key <licence>` |
70
83
  | Brand | WaveinnoLogo, WaveinnoSymbol, WaveMark (app icon: seven tones, round, 3D, animated), PoweredBy, Watermark; `@waveinno/ui/brand/waveinno-logo.svg` |
71
84
  | Shell | AppShell (rail → panel → content → drawer, ⌘K), theme and density controls, record drawer |
72
85
 
@@ -0,0 +1,8 @@
1
+ export declare const NEXT_GLOBALS_CSS: string;
2
+ export declare const NEXT_PROVIDERS: string;
3
+ export declare const NEXT_LAYOUT: string;
4
+ export declare const VITE_CONFIG: string;
5
+ export declare const VITE_CSS: string;
6
+ export declare const VITE_MAIN: string;
7
+ export declare const FONTS_HREF: string;
8
+ export declare const VITE_ROUTER: string;
@@ -0,0 +1,136 @@
1
+ /**
2
+ * The files every app on WAVEINNO UI starts with, in one place: `npx @waveinno/ui init`
3
+ * writes them, the installation guides show them, the template downloads ship them.
4
+ */
5
+
6
+ /** app/globals.css of a Next.js app. Its @source is relative to app/; init adjusts it for src/app/. */
7
+ export const NEXT_GLOBALS_CSS = `@import "tailwindcss";
8
+ @import "@waveinno/ui/styles.css";
9
+
10
+ /* Tailwind only generates classes it can see. Point it at the package,
11
+ relative to this file: "../node_modules" for app/globals.css,
12
+ "../../node_modules" for src/app/globals.css. */
13
+ @source "../node_modules/@waveinno/ui/dist";
14
+
15
+ /* The fonts loaded in layout.tsx, in the package's font slots.
16
+ Noto Sans Bengali carries Bangla and the Taka sign (৳), which Inter lacks. */
17
+ :root {
18
+ --font-inter: var(--font-body), var(--font-bangla);
19
+ --font-jakarta: var(--font-display), var(--font-bangla);
20
+ --font-jetbrains: var(--font-code);
21
+ }`;
22
+
23
+ /** app/providers.tsx */
24
+ export const NEXT_PROVIDERS = `"use client";
25
+
26
+ import { Toaster, TooltipProvider } from "@waveinno/ui";
27
+ import { NextRouterProvider } from "@waveinno/ui/next";
28
+
29
+ /** Once, around the whole app: Next.js routing for the package, tooltips, toasts. */
30
+ export function Providers({ children }: { children: React.ReactNode }) {
31
+ return (
32
+ <NextRouterProvider>
33
+ <TooltipProvider delayDuration={300}>
34
+ {children}
35
+ <Toaster />
36
+ </TooltipProvider>
37
+ </NextRouterProvider>
38
+ );
39
+ }`;
40
+
41
+ /** app/layout.tsx */
42
+ export const NEXT_LAYOUT = `import type { Metadata } from "next";
43
+ import { Inter, JetBrains_Mono, Noto_Sans_Bengali, Plus_Jakarta_Sans } from "next/font/google";
44
+
45
+ import { themeBootScript } from "@waveinno/ui";
46
+
47
+ import "./globals.css";
48
+ import { Providers } from "./providers";
49
+
50
+ const body = Inter({ subsets: ["latin"], variable: "--font-body" });
51
+ const display = Plus_Jakarta_Sans({ subsets: ["latin"], weight: ["500", "600", "700"], variable: "--font-display" });
52
+ const code = JetBrains_Mono({ subsets: ["latin"], variable: "--font-code" });
53
+ const bangla = Noto_Sans_Bengali({ subsets: ["bengali"], variable: "--font-bangla" });
54
+
55
+ export const metadata: Metadata = { title: "My app" };
56
+
57
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
58
+ return (
59
+ // The boot script sets data-theme before React runs, so the attribute
60
+ // differs from the server's HTML on purpose.
61
+ <html lang="en" suppressHydrationWarning className={[body.variable, display.variable, code.variable, bangla.variable].join(" ")}>
62
+ <head>
63
+ {/* Applies the saved light/dark theme and density before first paint: no flash. */}
64
+ <script dangerouslySetInnerHTML={{ __html: themeBootScript() }} />
65
+ </head>
66
+ <body className="bg-canvas text-body antialiased">
67
+ <Providers>{children}</Providers>
68
+ </body>
69
+ </html>
70
+ );
71
+ }`;
72
+
73
+ /** vite.config.ts */
74
+ export const VITE_CONFIG = `import tailwindcss from "@tailwindcss/vite";
75
+ import react from "@vitejs/plugin-react";
76
+ import { defineConfig } from "vite";
77
+
78
+ export default defineConfig({
79
+ plugins: [react(), tailwindcss()],
80
+ });`;
81
+
82
+ /** src/index.css of a Vite app. */
83
+ export const VITE_CSS = `@import "tailwindcss";
84
+ @import "@waveinno/ui/styles.css";
85
+
86
+ /* Tailwind only generates classes it can see: point it at the package. */
87
+ @source "../node_modules/@waveinno/ui/dist";
88
+
89
+ /* Noto Sans Bengali after each family: it carries Bangla and the Taka sign (৳). */
90
+ :root {
91
+ --font-inter: "Inter", "Noto Sans Bengali";
92
+ --font-jakarta: "Plus Jakarta Sans", "Noto Sans Bengali";
93
+ --font-jetbrains: "JetBrains Mono";
94
+ }`;
95
+
96
+ /** src/main.tsx */
97
+ export const VITE_MAIN = `import { StrictMode } from "react";
98
+ import { createRoot } from "react-dom/client";
99
+
100
+ import { Toaster, TooltipProvider } from "@waveinno/ui";
101
+
102
+ import App from "./App";
103
+ import "./index.css";
104
+
105
+ createRoot(document.getElementById("root")!).render(
106
+ <StrictMode>
107
+ <TooltipProvider delayDuration={300}>
108
+ <App />
109
+ <Toaster />
110
+ </TooltipProvider>
111
+ </StrictMode>,
112
+ );`;
113
+
114
+ /** src/ui-router.tsx: the package's links and navigation through React Router. */
115
+ export const VITE_ROUTER = `import { Link, useLocation, useNavigate } from "react-router";
116
+
117
+ import { RouterProvider, type RouterAdapter } from "@waveinno/ui";
118
+
119
+ // Tell the package to link and navigate through React Router.
120
+ const reactRouter: RouterAdapter = {
121
+ Link: ({ href, ...props }) => <Link to={href} {...props} />,
122
+ usePathname: () => useLocation().pathname,
123
+ useNavigate: () => {
124
+ const navigate = useNavigate();
125
+ return (href) => navigate(href);
126
+ },
127
+ };
128
+
129
+ // Inside <BrowserRouter> (or your data router's root route):
130
+ export function UiRouter({ children }: { children: React.ReactNode }) {
131
+ return <RouterProvider adapter={reactRouter}>{children}</RouterProvider>;
132
+ }`;
133
+
134
+ /** The four families both setups load, for a plain HTML page. */
135
+ export const FONTS_HREF =
136
+ "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Plus+Jakarta+Sans:wght@500;600;700&family=JetBrains+Mono:wght@400;500&family=Noto+Sans+Bengali:wght@400;500;600;700&display=swap";
@@ -0,0 +1,561 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * npx @waveinno/ui init set up Wavi in a Next.js or React (Vite) app
4
+ * npx @waveinno/ui doctor check a setup and say what is missing
5
+ * npx @waveinno/ui add <id> add a Wavi Pro component (org-chart, app-launcher, kanban, color-picker)
6
+ *
7
+ * init asks what you are building (Next.js or React + Vite), creates the app
8
+ * if there is none, asks the currency and the package manager, shows every
9
+ * change it will make, and only then makes them: the package and Tailwind's
10
+ * plugin, the CSS, the providers, the layout or entry, exactly as the
11
+ * installation guides show.
12
+ *
13
+ * It never overwrites your own code: a file is replaced only while it is still
14
+ * the starter's default (create-next-app, create-vite), and then the old one is
15
+ * kept as <file>.bak. Anything else is left alone and you get the line to add.
16
+ *
17
+ * --yes takes the defaults without asking (for scripts and CI); every question
18
+ * also has a flag: --framework next|vite, --currency BDT, --pm npm|pnpm|yarn|bun,
19
+ * --name my-app. --dry-run shows the plan and stops. --skip-install, --cwd <dir>.
20
+ */
21
+ import { spawnSync } from "node:child_process";
22
+ import fs from "node:fs";
23
+ import os from "node:os";
24
+ import path from "node:path";
25
+ import readline from "node:readline";
26
+ import { fileURLToPath, pathToFileURL } from "node:url";
27
+
28
+ import { FONTS_HREF, NEXT_GLOBALS_CSS, NEXT_LAYOUT, NEXT_PROVIDERS, VITE_CONFIG, VITE_CSS } from "./starter-files.mjs";
29
+
30
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
31
+ const PKG = JSON.parse(fs.readFileSync(path.join(HERE, "..", "package.json"), "utf8"));
32
+ const DOCS = "https://www.npmjs.com/package/@waveinno/ui";
33
+
34
+ /* ── output ─────────────────────────────────────────────────────────────── */
35
+
36
+ const color = process.stdout.isTTY && !process.env.NO_COLOR;
37
+ const c = (code) => (s) => (color ? `\x1b[${code}m${s}\x1b[0m` : String(s));
38
+ const bold = c(1);
39
+ const dim = c(2);
40
+ const green = c(32);
41
+ const yellow = c(33);
42
+ const red = c(31);
43
+ const blue = c(34);
44
+ const cyan = c(36);
45
+ const magenta = c(35);
46
+ const say = (s = "") => console.log(s);
47
+ const done = (s) => say(` ${green("✓")} ${s}`);
48
+ const skip = (s) => say(` ${dim("·")} ${dim(s)}`);
49
+ const warn = (s) => say(` ${yellow("!")} ${s}`);
50
+ const heading = (s) => say(`\n ${bold(s)}`);
51
+ const fail = (s) => {
52
+ say(`\n ${red("✗")} ${s}\n`);
53
+ process.exit(1);
54
+ };
55
+
56
+ /* ── args ───────────────────────────────────────────────────────────────── */
57
+
58
+ const argv = process.argv.slice(2);
59
+ const flag = (name) => argv.includes(`--${name}`);
60
+ const opt = (name) => {
61
+ const i = argv.indexOf(`--${name}`);
62
+ return i >= 0 ? argv[i + 1] : undefined;
63
+ };
64
+ // The first word that is neither a flag nor the value of a flag that takes one.
65
+ const VALUED = new Set(["--cwd", "--pm", "--currency", "--framework", "--name", "--key", "--dir", "--url"]);
66
+ const command = argv.find((a, i) => !a.startsWith("-") && !VALUED.has(argv[i - 1] ?? "")) ?? "help";
67
+ const interactive = process.stdin.isTTY && process.stdout.isTTY && !flag("yes");
68
+ let cwd = path.resolve(opt("cwd") ?? process.cwd());
69
+ /** While planning, nothing is written or installed; the same steps print what they would do. */
70
+ let dry = true;
71
+
72
+ /* ── prompts (no dependencies: readline in raw mode) ────────────────────── */
73
+
74
+ /** An arrow-key list. Returns the chosen value. */
75
+ function select(question, choices, initial = 0) {
76
+ return new Promise((resolve) => {
77
+ const out = process.stdout;
78
+ const inp = process.stdin;
79
+ let i = Math.max(0, initial);
80
+ const n = choices.length;
81
+ const line = (ch, on) => `\x1b[2K ${on ? cyan("❯") : " "} ${on ? bold(ch.label) : ch.label}${ch.hint ? ` ${dim(ch.hint)}` : ""}`;
82
+ out.write(`\n ${green("?")} ${bold(question)} ${dim("↑ ↓ to move, Enter to choose")}\n`);
83
+ out.write(`${choices.map((ch, k) => line(ch, k === i)).join("\n")}\n`);
84
+ out.write("\x1b[?25l");
85
+ readline.emitKeypressEvents(inp);
86
+ inp.setRawMode(true);
87
+ inp.resume();
88
+ const onKey = (_, key = {}) => {
89
+ if (key.ctrl && key.name === "c") {
90
+ inp.setRawMode(false);
91
+ out.write("\x1b[?25h\n");
92
+ process.exit(130);
93
+ }
94
+ if (key.name === "up" || key.name === "k") i = (i - 1 + n) % n;
95
+ else if (key.name === "down" || key.name === "j" || key.name === "tab") i = (i + 1) % n;
96
+ else if (key.name === "return") {
97
+ inp.off("keypress", onKey);
98
+ inp.setRawMode(false);
99
+ inp.pause();
100
+ // Fold the list into one answered line.
101
+ out.write(`\x1b[${n + 1}A\x1b[0J ${green("✔")} ${bold(question)} ${cyan(choices[i].label)}\n\x1b[?25h`);
102
+ resolve(choices[i].value);
103
+ return;
104
+ } else return;
105
+ out.write(`\x1b[${n}A${choices.map((ch, k) => line(ch, k === i)).join("\n")}\n`);
106
+ };
107
+ inp.on("keypress", onKey);
108
+ });
109
+ }
110
+
111
+ /** A line of text, with a default. */
112
+ function ask(question, fallback) {
113
+ return new Promise((resolve) => {
114
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
115
+ rl.question(`\n ${green("?")} ${bold(question)} ${dim(`(${fallback})`)} `, (a) => {
116
+ rl.close();
117
+ resolve(a.trim() || fallback);
118
+ });
119
+ });
120
+ }
121
+
122
+ /* ── the project ────────────────────────────────────────────────────────── */
123
+
124
+ const rel = (p) => path.relative(cwd, p).split(path.sep).join("/");
125
+ const exists = (p) => fs.existsSync(path.join(cwd, p));
126
+ const read = (p) => fs.readFileSync(path.join(cwd, p), "utf8");
127
+ const first = (...ps) => ps.find(exists);
128
+
129
+ function write(p, content, why = "wrote", note = "") {
130
+ if (dry) return say(` ${blue("+")} ${why} ${bold(p)} ${dim(note)}`);
131
+ fs.mkdirSync(path.dirname(path.join(cwd, p)), { recursive: true });
132
+ fs.writeFileSync(path.join(cwd, p), content);
133
+ done(`${why} ${bold(p)} ${dim(note)}`);
134
+ }
135
+
136
+ /** Replace a starter file, keeping the old one as .bak. */
137
+ function replace(p, content, why) {
138
+ if (!dry) fs.copyFileSync(path.join(cwd, p), path.join(cwd, `${p}.bak`));
139
+ write(p, content, why, `(the old one is kept as ${p}.bak)`);
140
+ }
141
+
142
+ const readPackage = () => (exists("package.json") ? JSON.parse(read("package.json")) : null);
143
+ const deps = (pkg) => ({ ...pkg?.dependencies, ...pkg?.devDependencies });
144
+ const detect = (pkg) => (deps(pkg).next ? "next" : deps(pkg).vite ? "vite" : null);
145
+
146
+ function lockfilePm() {
147
+ if (exists("pnpm-lock.yaml")) return "pnpm";
148
+ if (exists("yarn.lock")) return "yarn";
149
+ if (exists("bun.lockb") || exists("bun.lock")) return "bun";
150
+ if (exists("package-lock.json")) return "npm";
151
+ const agent = process.env.npm_config_user_agent ?? "";
152
+ for (const m of ["pnpm", "yarn", "bun"]) if (agent.startsWith(m)) return m;
153
+ return null;
154
+ }
155
+
156
+ function run(cmd, args, where = cwd) {
157
+ say(dim(` $ ${cmd} ${args.join(" ")}`));
158
+ // On Windows npm is a .cmd, which only runs through a shell; the arguments are names and flags, so one string is safe.
159
+ const r = process.platform === "win32" ? spawnSync(`${cmd} ${args.join(" ")}`, { cwd: where, stdio: "inherit", shell: true }) : spawnSync(cmd, args, { cwd: where, stdio: "inherit" });
160
+ return r.status === 0;
161
+ }
162
+
163
+ function install(pm, pkgs, dev) {
164
+ if (!pkgs.length) return;
165
+ const args = [pm === "npm" ? "install" : "add", ...(dev ? [pm === "bun" ? "-d" : "-D"] : []), ...pkgs];
166
+ if (dry) return say(` ${blue("+")} install ${bold(pkgs.join(", "))}${dev ? dim(" (dev)") : ""}`);
167
+ if (flag("skip-install")) return warn(`Run yourself: ${bold(`${pm} ${args.join(" ")}`)}`);
168
+ if (!run(pm, args)) fail(`${pm} ${args.join(" ")} failed. Run it yourself, then run init again.`);
169
+ done(`installed ${pkgs.join(", ")}`);
170
+ }
171
+
172
+ /** The @source line, relative to the CSS file. */
173
+ function sourceLine(cssFile) {
174
+ const from = path.dirname(path.join(cwd, cssFile));
175
+ const to = path.join(cwd, "node_modules", "@waveinno", "ui", "dist");
176
+ return `@source "${path.relative(from, to).split(path.sep).join("/")}";`;
177
+ }
178
+ const withSource = (css, cssFile) => css.replace(/@source "[^"]+";/, sourceLine(cssFile));
179
+
180
+ /** Add the currency line after the imports of a providers or entry file. */
181
+ function withCurrency(src, currency, importLine) {
182
+ if (!currency) return src;
183
+ const lines = src.split("\n");
184
+ let last = -1;
185
+ lines.forEach((l, k) => /^import /.test(l) && (last = k));
186
+ 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}" });`);
187
+ return lines.join("\n").replace(importLine, importLine.replace("TooltipProvider }", "TooltipProvider, useSettings }"));
188
+ }
189
+
190
+ /* ── the steps (the same code plans and applies) ────────────────────────── */
191
+
192
+ function setupCss(file, starter, create) {
193
+ if (!file) return write(create, `${withSource(starter, create)}\n`, "write", "(there was no global stylesheet)");
194
+ const css = read(file);
195
+ if (css.includes("@waveinno/ui/styles.css")) {
196
+ if (!css.includes("@source")) warn(`${file} imports the styles but has no @source line; add: ${sourceLine(file)}`);
197
+ return skip(`${file} already imports @waveinno/ui`);
198
+ }
199
+ // A starter stylesheet (create-next-app, create-vite, old and new) sets fonts, colours and body
200
+ // layout that would fight the design system; the newer Vite one even reuses token names.
201
+ const STARTER = /Arial, Helvetica|--font-geist|#646cff|#aa3bff|--text-h:|--social-bg|place-items: center|^\s*@import "tailwindcss";\s*$/m;
202
+ const isStarter = STARTER.test(css) && css.length < 8000;
203
+ const next = withSource(starter, file);
204
+ if (isStarter || !css.trim()) return replace(file, `${next}\n`, "set up");
205
+ write(file, `${next}\n\n/* ── Your styles ── */\n${css.replace(/^@import ["']tailwindcss["'];\s*/m, "")}`, "add Wavi to the top of");
206
+ // Your own variables with a token's name would recolour every component.
207
+ 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]))];
208
+ if (clash.length) warn(`${file} sets ${clash.join(", ")}: the same names as Wavi's tokens, so components change colour. Rename yours.`);
209
+ }
210
+
211
+ function stepsNext(pm, currency) {
212
+ const appDir = exists("src/app") ? "src/app" : exists("app") ? "app" : null;
213
+ if (!appDir) fail("This Next.js app has no app/ directory. Wavi's setup is for the App Router.");
214
+ const d = deps(readPackage());
215
+
216
+ heading("Packages");
217
+ if (d["@waveinno/ui"]) skip(`@waveinno/ui ${d["@waveinno/ui"]} is installed`);
218
+ install(pm, d["@waveinno/ui"] ? [] : [`@waveinno/ui@^${PKG.version}`], false);
219
+ install(pm, ["tailwindcss", "@tailwindcss/postcss"].filter((x) => !d[x]), true);
220
+
221
+ heading("PostCSS");
222
+ const postcss = first("postcss.config.mjs", "postcss.config.js", "postcss.config.cjs", "postcss.config.ts");
223
+ if (!postcss) write("postcss.config.mjs", `export default { plugins: { "@tailwindcss/postcss": {} } };\n`, "write");
224
+ else if (read(postcss).includes("@tailwindcss/postcss")) skip(`${postcss} loads Tailwind`);
225
+ else warn(`${postcss} does not load Tailwind 4. Add: plugins: { "@tailwindcss/postcss": {} }`);
226
+
227
+ heading("CSS");
228
+ setupCss(first(`${appDir}/globals.css`, `${appDir}/global.css`), NEXT_GLOBALS_CSS, `${appDir}/globals.css`);
229
+
230
+ heading("Providers");
231
+ const providers = first(`${appDir}/providers.tsx`, `${appDir}/providers.jsx`);
232
+ 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}` : ""));
233
+ else if (read(providers).includes("@waveinno/ui")) skip(`${providers} already uses @waveinno/ui`);
234
+ else warn(`${providers} is your own: wrap its children in <NextRouterProvider> and <TooltipProvider>, and add <Toaster />.`);
235
+
236
+ heading("Layout");
237
+ const layout = first(`${appDir}/layout.tsx`, `${appDir}/layout.jsx`);
238
+ if (!layout) return write(`${appDir}/layout.tsx`, `${NEXT_LAYOUT}\n`, "write");
239
+ const src = read(layout);
240
+ if (src.includes("themeBootScript") && src.includes("Providers")) return skip(`${layout} is set up`);
241
+ if (/Geist|Create Next App/.test(src) && src.length < 2000) {
242
+ const title = /title:\s*["']([^"']+)["']/.exec(src)?.[1];
243
+ return replace(layout, `${NEXT_LAYOUT.replace('"My app"', JSON.stringify(title && title !== "Create Next App" ? title : "My app"))}\n`, "set up");
244
+ }
245
+ warn(`${layout} is your own, so it stays as it is. Add three things:`);
246
+ say(dim(` import { themeBootScript } from "@waveinno/ui"; import { Providers } from "./providers";`));
247
+ say(dim(` <html suppressHydrationWarning> and <head><script dangerouslySetInnerHTML={{ __html: themeBootScript() }} /></head>`));
248
+ say(dim(` <body><Providers>{children}</Providers></body>`));
249
+ }
250
+
251
+ async function stepsVite(pm, currency) {
252
+ const d = deps(readPackage());
253
+
254
+ heading("Packages");
255
+ if (d["@waveinno/ui"]) skip(`@waveinno/ui ${d["@waveinno/ui"]} is installed`);
256
+ install(pm, d["@waveinno/ui"] ? [] : [`@waveinno/ui@^${PKG.version}`], false);
257
+ install(pm, ["tailwindcss", "@tailwindcss/vite"].filter((x) => !d[x]), true);
258
+
259
+ heading("Vite");
260
+ const config = first("vite.config.ts", "vite.config.js", "vite.config.mts", "vite.config.mjs");
261
+ if (!config) write("vite.config.ts", `${VITE_CONFIG}\n`, "write");
262
+ else {
263
+ const src = read(config);
264
+ if (src.includes("@tailwindcss/vite")) skip(`${config} loads Tailwind`);
265
+ 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");
266
+ else warn(`Add tailwindcss() from "@tailwindcss/vite" to the plugins in ${config}.`);
267
+ }
268
+
269
+ heading("CSS");
270
+ setupCss(first("src/index.css", "src/main.css", "src/styles.css"), VITE_CSS, "src/index.css");
271
+ const entry = first("src/main.tsx", "src/main.jsx");
272
+ if (entry && !/import ["']\.\/(index|main|styles)\.css["']/.test(read(entry))) warn(`Import the stylesheet once in ${entry}: import "./index.css";`);
273
+
274
+ heading("index.html");
275
+ if (!exists("index.html")) warn("No index.html: add the fonts and the theme script from the guide to your page.");
276
+ else {
277
+ let html = read("index.html");
278
+ if (html.includes("waveinno.theme") || html.includes("Noto+Sans+Bengali")) skip("index.html is set up");
279
+ else {
280
+ // The same script as themeBootScript(), from the package itself.
281
+ const { themeBootScript } = await import(pathToFileURL(path.join(HERE, "..", "dist", "lib", "boot-script.js")).href);
282
+ const head = ` <link rel="preconnect" href="https://fonts.googleapis.com" />
283
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
284
+ <link rel="stylesheet" href="${FONTS_HREF}" />
285
+ <script>${themeBootScript()}</script>
286
+ </head>`;
287
+ html = html.replace(/\s*<\/head>/, `\n${head}`);
288
+ if (/<body(?![^>]*class=)/.test(html)) html = html.replace(/<body/, '<body class="bg-canvas text-body antialiased"');
289
+ write("index.html", html, "add the fonts and the theme script to");
290
+ }
291
+ }
292
+
293
+ heading("Entry");
294
+ const main = first("src/main.tsx", "src/main.jsx");
295
+ if (!main) return warn("No src/main.tsx: wrap your app in <TooltipProvider> and add <Toaster /> once.");
296
+ const src = read(main);
297
+ if (src.includes("@waveinno/ui")) return skip(`${main} already uses @waveinno/ui`);
298
+ if (!/<App \/>/.test(src)) return warn(`Wrap your app in <TooltipProvider> and add <Toaster /> once in ${main}.`);
299
+ const imp = 'import { Toaster, TooltipProvider } from "@waveinno/ui";';
300
+ const wrapped = src.replace(/(\s*)<App \/>/, "$1<TooltipProvider delayDuration={300}>$1 <App />$1 <Toaster />$1</TooltipProvider>");
301
+ write(main, withCurrency(`${imp}\n${wrapped}`, currency, imp), "wrap the app with tooltips and toasts in", currency ? `(amounts in ${currency})` : "");
302
+ }
303
+
304
+ /* ── init ───────────────────────────────────────────────────────────────── */
305
+
306
+ const FRAMEWORKS = [
307
+ { value: "next", label: "Next.js", hint: "App Router, server rendering, routing built in" },
308
+ { value: "vite", label: "React + Vite", hint: "a single-page app, quick to start" },
309
+ ];
310
+ const CURRENCIES = [
311
+ { value: "BDT", label: "৳ BDT", hint: "Bangladeshi taka" },
312
+ { value: "USD", label: "$ USD", hint: "US dollar" },
313
+ { value: "INR", label: "₹ INR", hint: "Indian rupee" },
314
+ { value: "EUR", label: "€ EUR", hint: "Euro" },
315
+ { value: "", label: "Follow the app shell", hint: "set it per organisation later" },
316
+ ];
317
+ const PMS = ["npm", "pnpm", "yarn", "bun"];
318
+
319
+ async function init() {
320
+ say(`\n ${magenta("◆")} ${bold("Wavi")} ${dim(`v${PKG.version}`)} ${dim("— the setup")}`);
321
+ say(dim(` ${cwd}`));
322
+
323
+ // 1. What it is, or what to make.
324
+ let pkg = readPackage();
325
+ const found = detect(pkg);
326
+ let framework = opt("framework") ?? found;
327
+ 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.");
328
+ if (interactive && !opt("framework")) {
329
+ framework = await select(
330
+ pkg ? "What is this app built with?" : "What would you like to build?",
331
+ FRAMEWORKS.map((f) => ({ ...f, label: f.value === found ? `${f.label} ${green("(detected)")}` : f.label })),
332
+ found ? FRAMEWORKS.findIndex((f) => f.value === found) : 0,
333
+ );
334
+ }
335
+ if (!["next", "vite"].includes(framework)) fail(`--framework takes next or vite, not "${framework}".`);
336
+ 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.`);
337
+
338
+ // 2. The package manager: from the lockfile, else ask.
339
+ let pm = opt("pm") ?? lockfilePm();
340
+ if (!pm) pm = interactive ? await select("Which package manager?", PMS.map((p) => ({ value: p, label: p }))) : "npm";
341
+ if (!PMS.includes(pm)) fail(`--pm takes ${PMS.join(", ")}.`);
342
+
343
+ // 3. No app yet: make one with the official starter, then set it up.
344
+ if (!pkg) {
345
+ const name = opt("name") ?? (interactive ? await ask("Name of the new app?", "my-app") : "my-app");
346
+ if (fs.existsSync(path.join(cwd, name))) fail(`${name} already exists here. Choose another name, or run init inside it.`);
347
+ if (flag("dry-run")) {
348
+ heading("Plan");
349
+ say(` ${blue("+")} create ${bold(name)} with ${framework === "next" ? "create-next-app" : "create-vite"}, then set it up`);
350
+ return say();
351
+ }
352
+ heading(`Creating ${name}`);
353
+ const ok =
354
+ framework === "next"
355
+ ? run("npx", ["--yes", "create-next-app@latest", name, "--ts", "--tailwind", "--app", "--eslint", "--yes", `--use-${pm}`])
356
+ : run("npx", ["--yes", "create-vite@latest", name, "--template", "react-ts"]) && run(pm, ["install"], path.join(cwd, name));
357
+ if (!ok) fail("Creating the app failed; see the output above.");
358
+ cwd = path.join(cwd, name);
359
+ pkg = readPackage();
360
+ if (!pkg) fail(`The starter did not create ${name}/package.json.`);
361
+ }
362
+
363
+ // 4. The currency amounts show in.
364
+ const tzBangladesh = Intl.DateTimeFormat().resolvedOptions().timeZone === "Asia/Dhaka";
365
+ let currency = opt("currency");
366
+ if (currency === undefined) {
367
+ currency = interactive ? await select("Which currency do amounts show in?", CURRENCIES, tzBangladesh ? 0 : 1) : tzBangladesh ? "BDT" : "";
368
+ }
369
+ currency = currency.toUpperCase().replace(/[^A-Z]/g, "");
370
+
371
+ // 5. The plan: the same steps, printing instead of doing.
372
+ say(`\n ${bold("The plan")} ${dim(`${framework === "next" ? "Next.js" : "React + Vite"} · ${pm}${currency ? ` · ${currency}` : ""}`)}`);
373
+ dry = true;
374
+ if (framework === "next") stepsNext(pm, currency);
375
+ else await stepsVite(pm, currency);
376
+ if (flag("dry-run")) return say(dim("\n Dry run: nothing was changed.\n"));
377
+
378
+ if (interactive && !(await select("Set it up?", [{ value: true, label: "Yes, make these changes" }, { value: false, label: "No, leave everything as it is" }]))) {
379
+ return say(dim("\n Nothing was changed.\n"));
380
+ }
381
+
382
+ // 6. Do it.
383
+ say(`\n ${bold("Setting up")}`);
384
+ dry = false;
385
+ if (framework === "next") stepsNext(pm, currency);
386
+ else await stepsVite(pm, currency);
387
+
388
+ const runDev = pm === "npm" ? "npm run dev" : `${pm} dev`;
389
+ const into = path.relative(process.cwd(), cwd);
390
+ say(`\n ${green("◆")} ${bold("Ready.")}`);
391
+ say(` ${into ? `cd ${into} && ` : ""}${bold(runDev)}`);
392
+ say(dim(` import { Button, toast } from "@waveinno/ui";`));
393
+ say(dim(` Check the setup any time: npx @waveinno/ui doctor · ${DOCS}\n`));
394
+ }
395
+
396
+ /* ── add (Wavi Pro components) ─────────────────────────────────────────── */
397
+
398
+ /** Where the Pro source is served. Set WAVI_URL (or --url) to your own copy of the docs site. */
399
+ const PRO_URL = (opt("url") ?? process.env.WAVI_URL ?? "https://wavi.waveinno.com").replace(/\/$/, "");
400
+ const KEY_FILE = path.join(os.homedir(), ".wavi", "licence");
401
+
402
+ /** Keep a key the server accepted, for next time, readable only by you. */
403
+ function rememberKey(key) {
404
+ try {
405
+ fs.mkdirSync(path.dirname(KEY_FILE), { recursive: true });
406
+ fs.writeFileSync(KEY_FILE, key, { mode: 0o600 });
407
+ } catch {
408
+ // Not saved; it still worked this once.
409
+ }
410
+ }
411
+
412
+ function licenceKey() {
413
+ const given = opt("key") ?? process.env.WAVI_PRO_KEY;
414
+ if (given) return given.trim();
415
+ try {
416
+ return fs.readFileSync(KEY_FILE, "utf8").trim();
417
+ } catch {
418
+ return null;
419
+ }
420
+ }
421
+
422
+ async function add() {
423
+ const ids = argv.filter((a, i) => !a.startsWith("-") && !VALUED.has(argv[i - 1] ?? "") && a !== "add");
424
+ say(`\n ${magenta("◆")} ${bold("Wavi Pro")} ${dim(`— adding ${ids.join(", ") || "components"}`)}`);
425
+
426
+ if (flag("list") || !ids.length) {
427
+ const r = await fetch(`${PRO_URL}/api/pro/component`).catch(() => null);
428
+ if (!r?.ok) fail(`Could not reach ${PRO_URL}. Check your connection, or pass --url.`);
429
+ const list = await r.json();
430
+ heading("Pro components");
431
+ for (const c of list) say(` ${cyan(c.id.padEnd(16))} ${c.title}`);
432
+ say(dim(`\n npx @waveinno/ui add <id> --key <your licence key>\n`));
433
+ return;
434
+ }
435
+
436
+ const key = licenceKey();
437
+ if (!key) fail(`A Wavi Pro licence key is needed: npx @waveinno/ui add ${ids[0]} --key <key>\n Get one at ${PRO_URL}/pricing. The key is then remembered in ${KEY_FILE}.`);
438
+
439
+ const pkg = readPackage();
440
+ if (!pkg) fail("Run add inside your app (no package.json here).");
441
+ const d = deps(pkg);
442
+ if (!d["@waveinno/ui"]) warn("@waveinno/ui is not installed here yet: run npx @waveinno/ui init first.");
443
+ const fw = detect(pkg);
444
+ const dir = opt("dir") ?? (exists("src") && (fw === "vite" || exists("src/app")) ? "src/components/wavi-pro" : "components/wavi-pro");
445
+ const pm = opt("pm") ?? lockfilePm() ?? "npm";
446
+ dry = flag("dry-run");
447
+
448
+ const needed = new Set();
449
+ const imports = [];
450
+ for (const id of ids) {
451
+ const r = await fetch(`${PRO_URL}/api/pro/component`, {
452
+ method: "POST",
453
+ headers: { "content-type": "application/json", authorization: `Bearer ${key}` },
454
+ body: JSON.stringify({ id }),
455
+ }).catch(() => null);
456
+ if (!r) fail(`Could not reach ${PRO_URL}. Check your connection, or pass --url.`);
457
+ const data = await r.json().catch(() => ({}));
458
+ if (!r.ok) fail(data.error ?? `${id}: the server said ${r.status}.`);
459
+ rememberKey(key);
460
+ heading(data.title);
461
+ for (const f of data.files) {
462
+ const target = `${dir}/${f.path}`;
463
+ if (exists(target) && !flag("overwrite")) {
464
+ if (read(target) === f.content) skip(`${target} is up to date`);
465
+ else warn(`${target} exists and differs. Run with --overwrite to replace it (yours is kept as .bak).`);
466
+ continue;
467
+ }
468
+ if (exists(target)) replace(target, f.content, "update");
469
+ else write(target, f.content, "add");
470
+ }
471
+ for (const dep of data.dependencies ?? []) if (!d[dep]) needed.add(dep);
472
+ const names = (data.exports ?? []).filter((e) => !e.startsWith("type "));
473
+ const from = fw === "vite" ? `./components/wavi-pro/${data.files[0].path.replace(/\.tsx$/, "")}` : `@/${dir.replace(/^src\//, "")}/${data.files[0].path.replace(/\.tsx$/, "")}`;
474
+ imports.push(`import { ${names.join(", ")} } from "${from}";`);
475
+ }
476
+
477
+ if (needed.size) {
478
+ heading("Packages");
479
+ install(pm, [...needed], false);
480
+ }
481
+
482
+ say(`\n ${green("◆")} ${bold("Added.")} Import it:`);
483
+ for (const line of imports) say(` ${dim(line)}`);
484
+ say(dim(` The files are yours: covered by the Wavi Pro Licence, kept up to date with npx @waveinno/ui add <id> --overwrite.\n`));
485
+ }
486
+
487
+ /* ── doctor ─────────────────────────────────────────────────────────────── */
488
+
489
+ function doctor() {
490
+ say(`\n ${magenta("◆")} ${bold("Wavi doctor")} ${dim(cwd)}\n`);
491
+ const pkg = readPackage();
492
+ if (!pkg) fail(`No package.json in ${cwd}.`);
493
+ const d = deps(pkg);
494
+ const fw = detect(pkg);
495
+ let bad = 0;
496
+ const check = (ok, good, fix) => {
497
+ if (ok) done(good);
498
+ else {
499
+ bad++;
500
+ say(` ${red("✗")} ${fix}`);
501
+ }
502
+ };
503
+ check(!!fw, `${fw === "next" ? "Next.js" : "Vite"} app`, "Not a Next.js or Vite app");
504
+ check(!!d["@waveinno/ui"], `@waveinno/ui ${d["@waveinno/ui"] ?? ""}`, "@waveinno/ui is not installed: npm install @waveinno/ui");
505
+ check(/^\^?4|^latest/.test(d.tailwindcss ?? ""), `tailwindcss ${d.tailwindcss}`, "Tailwind CSS 4 is not installed");
506
+ if (fw === "next") check(!!d["@tailwindcss/postcss"], "@tailwindcss/postcss", "@tailwindcss/postcss is missing: npm install -D @tailwindcss/postcss");
507
+ if (fw === "vite") check(!!d["@tailwindcss/vite"], "@tailwindcss/vite", "@tailwindcss/vite is missing: npm install -D @tailwindcss/vite");
508
+
509
+ const cssFile = fw === "next" ? first("src/app/globals.css", "app/globals.css") : first("src/index.css", "src/main.css", "src/styles.css");
510
+ const css = cssFile ? read(cssFile) : "";
511
+ check(css.includes("@waveinno/ui/styles.css"), `${cssFile} imports @waveinno/ui/styles.css`, `${cssFile ?? "Your global CSS"} does not import @waveinno/ui/styles.css`);
512
+ const src = /@source "([^"]+)";/.exec(css)?.[1];
513
+ const resolved = src && cssFile ? path.resolve(path.dirname(path.join(cwd, cssFile)), src) : null;
514
+ check(
515
+ !!resolved && fs.existsSync(resolved),
516
+ `@source points at ${resolved ? rel(resolved) : ""}`,
517
+ 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";'}`,
518
+ );
519
+
520
+ if (fw === "next") {
521
+ const appDir = exists("src/app") ? "src/app" : "app";
522
+ const layout = first(`${appDir}/layout.tsx`, `${appDir}/layout.jsx`);
523
+ check(!!layout && read(layout).includes("themeBootScript"), "The theme script runs before paint", `${layout ?? "layout"}: add themeBootScript() in <head>, or dark mode flashes`);
524
+ const providers = first(`${appDir}/providers.tsx`, `${appDir}/providers.jsx`);
525
+ const p = providers ? read(providers) : "";
526
+ check(p.includes("Toaster"), "Toaster is mounted", "No <Toaster />: toasts will not show");
527
+ check(p.includes("NextRouterProvider"), "Links use the Next.js router", "No <NextRouterProvider>: links in the shell reload the page");
528
+ } else if (fw === "vite") {
529
+ const html = exists("index.html") ? read("index.html") : "";
530
+ check(html.includes("waveinno.theme"), "The theme script runs before paint", "index.html: add the theme script, or dark mode flashes");
531
+ const main = first("src/main.tsx", "src/main.jsx");
532
+ check(!!main && read(main).includes("Toaster"), "Toaster is mounted", "No <Toaster />: toasts will not show");
533
+ }
534
+ say(bad ? `\n ${yellow(`${bad} to fix.`)} ${dim("npx @waveinno/ui init sets these up.")}\n` : `\n ${green(bold("All good."))}\n`);
535
+ process.exit(bad ? 1 : 0);
536
+ }
537
+
538
+ /* ── main ───────────────────────────────────────────────────────────────── */
539
+
540
+ if (flag("version") || command === "version") say(PKG.version);
541
+ else if (command === "init") await init();
542
+ else if (command === "doctor") doctor();
543
+ else if (command === "add") await add();
544
+ else {
545
+ say(`
546
+ ${magenta("◆")} ${bold("Wavi")} ${dim(`v${PKG.version}`)}
547
+
548
+ ${bold("npx @waveinno/ui init")} set up a Next.js or React (Vite) app, or create one
549
+ ${bold("npx @waveinno/ui doctor")} check the setup
550
+ ${bold("npx @waveinno/ui add <id>")} add a Wavi Pro component (--list to see them; --key once)
551
+
552
+ ${dim("--yes take the defaults, ask nothing")}
553
+ ${dim("--framework next next or vite")}
554
+ ${dim("--currency BDT the currency amounts show in (BDT is the default in Bangladesh)")}
555
+ ${dim("--pm npm npm, pnpm, yarn or bun (default: from the lockfile)")}
556
+ ${dim("--name my-app the name of a new app")}
557
+ ${dim("--dry-run show the plan, change nothing")}
558
+ ${dim("--skip-install print the install commands instead of running them")}
559
+ ${dim("--cwd <dir> the folder (default: here)")}
560
+ `);
561
+ }
@@ -1,15 +1,12 @@
1
1
  /** Patterns: compositions of primitives that whole screens are built from. */
2
2
  export * from "./activity-feed";
3
- export * from "./app-launcher";
4
3
  export * from "./data-table";
5
4
  export * from "./data-table-cells";
6
5
  export * from "./data-table-edit";
7
6
  export * from "./data-table-filters";
8
7
  export type { ColumnDef } from "@tanstack/react-table";
9
- export * from "./kanban";
10
8
  export * from "./kpi-card";
11
9
  export * from "./money";
12
- export * from "./org-chart";
13
10
  export * from "./page-header";
14
11
  export * from "./page-hero";
15
12
  export * from "./page-shell";
@@ -1 +1 @@
1
- export*from"./activity-feed.js";export*from"./app-launcher.js";export*from"./data-table.js";export*from"./data-table-cells.js";export*from"./data-table-edit.js";export*from"./data-table-filters.js";export*from"./kanban.js";export*from"./kpi-card.js";export*from"./money.js";export*from"./org-chart.js";export*from"./page-header.js";export*from"./page-hero.js";export*from"./page-shell.js";export*from"./stat-card.js";export*from"./states.js";export*from"./timeline.js";
1
+ export*from"./activity-feed.js";export*from"./data-table.js";export*from"./data-table-cells.js";export*from"./data-table-edit.js";export*from"./data-table-filters.js";export*from"./kpi-card.js";export*from"./money.js";export*from"./page-header.js";export*from"./page-hero.js";export*from"./page-shell.js";export*from"./stat-card.js";export*from"./states.js";export*from"./timeline.js";
@@ -8,7 +8,6 @@ export * from "./calendar";
8
8
  export * from "./card";
9
9
  export * from "./carousel";
10
10
  export * from "./checkbox";
11
- export * from "./color-picker";
12
11
  export * from "./combobox";
13
12
  export * from "./confirm-dialog";
14
13
  export * from "./date-picker";
@@ -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";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";
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"./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";
@@ -1 +1 @@
1
- "use client";import{jsx as u,jsxs as m}from"react/jsx-runtime";import{Check as v,IdCard as y}from"lucide-react";import*as p from"react";import{cn as g}from"../../lib/utils.js";import{fieldStateClass as w}from"./input.js";const a={10:"smart",13:"old13",17:"old17"},C={smart:"Smart card",old13:"Old NID",old17:"Old NID, with birth year"},d=e=>e.replace(/[০-৯]/g,i=>String("\u09E6\u09E7\u09E8\u09E9\u09EA\u09EB\u09EC\u09ED\u09EE\u09EF".indexOf(i))).replace(/\D/g,"").slice(0,17);function S(e){const i=e.length<=10?[3,3,4]:e.length<=13?[4,4,5]:[4,4,4,5],r=[];let t=0;for(const o of i){if(t>=e.length)break;r.push(e.slice(t,t+o)),t+=o}return r.join(" ")}const K=p.forwardRef(({value:e,defaultValue:i="",onValueChange:r,state:t="default",labels:o,className:f,disabled:c,placeholder:h="123 456 7890",...x},N)=>{const[b,I]=p.useState(()=>d(i)),n=e!==void 0?d(e):b,l=a[n.length]??null;return m("div",{className:g("flex h-ctl w-full items-center gap-2 rounded-ctl border bg-raised px-3 shadow-[inset_0_1px_1px_rgba(5,8,22,.04)] transition-[border-color,box-shadow]",w[t],c&&"cursor-not-allowed bg-hover opacity-70",f),children:[u(y,{className:"size-4 shrink-0 text-muted"}),u("input",{ref:N,type:"text",inputMode:"numeric",autoComplete:"off",disabled:c,placeholder:h,value:S(n),onChange:k=>{const s=d(k.target.value);e===void 0&&I(s),r?.(s,{valid:!!a[s.length],kind:a[s.length]??null})},className:"num h-full min-w-0 flex-1 bg-transparent text-[13.5px] tracking-[0.04em] text-text outline-none placeholder:tracking-normal placeholder:text-placeholder",...x}),n?m("span",{className:g("inline-flex shrink-0 items-center gap-1 rounded-full px-2 py-0.5 text-[11.5px] font-medium",l?"bg-success-subtle text-success":"bg-hover text-muted"),children:[l?u(v,{className:"size-3",strokeWidth:3}):null,l?o?.[l]??C[l]:`${n.length} / ${n.length<10?10:n.length<13?13:17}`]}):null]})});K.displayName="NidInput";export{K as NidInput};
1
+ "use client";import{jsx as a,jsxs as f}from"react/jsx-runtime";import{Check as y,IdCard as w}from"lucide-react";import*as c from"react";import{cn as g}from"../../lib/utils.js";import{fieldStateClass as C}from"./input.js";const d={10:"smart",13:"old13",17:"old17"},m={smart:"Smart card",old13:"Old NID",old17:"Old NID, with birth year"},p=e=>e.replace(/[০-৯]/g,l=>String("\u09E6\u09E7\u09E8\u09E9\u09EA\u09EB\u09EC\u09ED\u09EE\u09EF".indexOf(l))).replace(/\D/g,"").slice(0,17);function S(e){const l=e.length<=10?[3,3,4]:e.length<=13?[4,4,5]:[4,4,4,5],s=[];let n=0;for(const r of l){if(n>=e.length)break;s.push(e.slice(n,n+r)),n+=r}return s.join(" ")}const K=c.forwardRef(({value:e,defaultValue:l="",onValueChange:s,state:n="default",labels:r,className:h,disabled:u,placeholder:x="123 456 7890",...N},b)=>{const[I,k]=c.useState(()=>p(l)),i=e!==void 0?p(e):I,t=d[i.length]??null;return f("div",{className:g("@container flex h-ctl w-full items-center gap-2 rounded-ctl border bg-raised px-3 shadow-[inset_0_1px_1px_rgba(5,8,22,.04)] transition-[border-color,box-shadow]",C[n],u&&"cursor-not-allowed bg-hover opacity-70",h),children:[a(w,{className:"size-4 shrink-0 text-muted"}),a("input",{ref:b,type:"text",inputMode:"numeric",autoComplete:"off",disabled:u,placeholder:x,value:S(i),onChange:v=>{const o=p(v.target.value);e===void 0&&k(o),s?.(o,{valid:!!d[o.length],kind:d[o.length]??null})},className:"num h-full min-w-[14ch] flex-1 bg-transparent text-[13.5px] tracking-[0.04em] text-text outline-none placeholder:tracking-normal placeholder:text-placeholder",...N}),i?f("span",{title:t?r?.[t]??m[t]:void 0,className:g("inline-flex shrink-0 items-center gap-1 rounded-full px-2 py-0.5 text-[11.5px] font-medium",t?"bg-success-subtle text-success @max-[420px]:px-1.5":"bg-hover text-muted"),children:[t?a(y,{className:"size-3",strokeWidth:3}):null,t?a("span",{className:"@max-[420px]:sr-only",children:r?.[t]??m[t]}):`${i.length} / ${i.length<10?10:i.length<13?13:17}`]}):null]})});K.displayName="NidInput";export{K as NidInput};
@@ -1 +1 @@
1
- "use client";import{jsx as r,jsxs as d}from"react/jsx-runtime";import{Check as _}from"lucide-react";import*as m from"react";import{BD_OPERATOR_NAMES as C,parseBdPhone as o}from"../../lib/bd-geo.js";import{cn as h}from"../../lib/utils.js";import{fieldStateClass as M}from"./input.js";const R=e=>e.length>4?`${e.slice(0,4)}-${e.slice(4)}`:e,S=m.forwardRef(({value:e,defaultValue:b="",onValueChange:u,showOperator:g=!0,state:c="default",labels:v,className:P,disabled:f,placeholder:w="1712-345678",...N},I)=>{const s=e!==void 0,[y,x]=m.useState(()=>o(b).national),i=s?o(e).national:y,n=o(i),a=i.length>=2&&!n.operator;return d("div",{className:h("flex h-ctl w-full items-center overflow-hidden rounded-ctl border bg-raised text-[13px] shadow-[inset_0_1px_1px_rgba(5,8,22,.04)] transition-[border-color,box-shadow]",M[a?"error":c],f&&"cursor-not-allowed bg-hover opacity-70",P),children:[d("span",{className:"flex h-full shrink-0 items-center gap-1.5 border-r border-border bg-surface px-2.5 text-muted",children:[r("span",{"aria-hidden":!0,className:"relative h-3 w-[18px] overflow-hidden rounded-[2px] bg-[#006a4e]",children:r("span",{className:"absolute left-[38%] top-1/2 size-[7px] -translate-x-1/2 -translate-y-1/2 rounded-full bg-[#f42a41]"})}),r("span",{className:"num font-medium text-text",children:"+880"})]}),r("input",{ref:I,type:"tel",inputMode:"tel",autoComplete:"tel-national",disabled:f,placeholder:w,"aria-invalid":a||c==="error"||void 0,value:R(i),onChange:l=>{const t=o(l.target.value);s||x(t.national),u?.(t.e164,t)},onPaste:l=>{const t=l.clipboardData.getData("text");if(!t)return;l.preventDefault();const p=o(t);s||x(p.national),u?.(p.e164,p)},className:"num h-full min-w-0 flex-1 bg-transparent px-3 text-text outline-none placeholder:text-placeholder disabled:cursor-not-allowed",...N}),g&&(n.operator||a)?d("span",{className:h("mr-2 inline-flex shrink-0 items-center gap-1 rounded-full px-2 py-0.5 text-[11.5px] font-medium",a?"bg-danger-subtle text-danger":n.valid?"bg-success-subtle text-success":"bg-hover text-muted"),children:[n.valid?r(_,{className:"size-3",strokeWidth:3}):null,a?v?.invalidPrefix??"Not a mobile prefix":C[n.operator]]}):null]})});S.displayName="PhoneInput";export{S as PhoneInput};
1
+ "use client";import{jsx as t,jsxs as d}from"react/jsx-runtime";import{Check as k}from"lucide-react";import*as h from"react";import{BD_OPERATOR_NAMES as b,parseBdPhone as o}from"../../lib/bd-geo.js";import{cn as g}from"../../lib/utils.js";import{fieldStateClass as C}from"./input.js";const M=e=>e.length>4?`${e.slice(0,4)}-${e.slice(4)}`:e,R=h.forwardRef(({value:e,defaultValue:v="",onValueChange:u,showOperator:P=!0,state:x="default",labels:c,className:N,disabled:m,placeholder:w="1712-345678",...I},y)=>{const s=e!==void 0,[_,f]=h.useState(()=>o(v).national),i=s?o(e).national:_,n=o(i),a=i.length>=2&&!n.operator;return d("div",{className:g("@container flex h-ctl w-full items-center overflow-hidden rounded-ctl border bg-raised text-[13px] shadow-[inset_0_1px_1px_rgba(5,8,22,.04)] transition-[border-color,box-shadow]",C[a?"error":x],m&&"cursor-not-allowed bg-hover opacity-70",N),children:[d("span",{className:"flex h-full shrink-0 items-center gap-1.5 border-r border-border bg-surface px-2.5 text-muted @max-[230px]:px-2",children:[t("span",{"aria-hidden":!0,className:"relative h-3 w-[18px] overflow-hidden rounded-[2px] bg-[#006a4e] @max-[230px]:hidden",children:t("span",{className:"absolute left-[38%] top-1/2 size-[7px] -translate-x-1/2 -translate-y-1/2 rounded-full bg-[#f42a41]"})}),t("span",{className:"num font-medium text-text",children:"+880"})]}),t("input",{ref:y,type:"tel",inputMode:"tel",autoComplete:"tel-national",disabled:m,placeholder:w,"aria-invalid":a||x==="error"||void 0,value:M(i),onChange:l=>{const r=o(l.target.value);s||f(r.national),u?.(r.e164,r)},onPaste:l=>{const r=l.clipboardData.getData("text");if(!r)return;l.preventDefault();const p=o(r);s||f(p.national),u?.(p.e164,p)},className:"num h-full min-w-[10ch] flex-1 bg-transparent px-3 text-text outline-none placeholder:text-placeholder disabled:cursor-not-allowed @max-[230px]:px-2",...I}),P&&(n.operator||a)?d("span",{title:a?c?.invalidPrefix??"Not a mobile prefix":b[n.operator],className:g("mr-2 inline-flex shrink-0 items-center gap-1 rounded-full px-2 py-0.5 text-[11.5px] font-medium @max-[290px]:min-h-5 @max-[290px]:px-1.5",a?"bg-danger-subtle text-danger":n.valid?"bg-success-subtle text-success":"bg-hover text-muted"),children:[n.valid?t(k,{className:"size-3",strokeWidth:3}):t("span",{"aria-hidden":!0,className:"hidden size-1.5 rounded-full bg-current @max-[290px]:block"}),t("span",{className:"@max-[290px]:sr-only",children:a?c?.invalidPrefix??"Not a mobile prefix":b[n.operator]})]}):null]})});R.displayName="PhoneInput";export{R as PhoneInput};
package/package.json CHANGED
@@ -1,14 +1,26 @@
1
1
  {
2
2
  "name": "@waveinno/ui",
3
- "version": "0.8.0",
4
- "description": "WAVEINNO design system — tokens, shell, primitives and patterns shared by every WAVEINNO product",
3
+ "version": "1.0.0",
4
+ "description": "Wavi: the React design system from Bangladesh. 100+ components, app shell, charts, and bKash/taka/Bangla-ready inputs for Next.js and Vite, with Tailwind CSS 4. Set up with npx @waveinno/ui init.",
5
5
  "keywords": [
6
6
  "waveinno",
7
7
  "design-system",
8
8
  "react",
9
9
  "nextjs",
10
10
  "tailwindcss",
11
- "radix-ui"
11
+ "radix-ui",
12
+ "wavi",
13
+ "react-components",
14
+ "component-library",
15
+ "ui-kit",
16
+ "tailwind-css-4",
17
+ "vite",
18
+ "dark-mode",
19
+ "bangladesh",
20
+ "bangla",
21
+ "bkash",
22
+ "taka",
23
+ "dashboard"
12
24
  ],
13
25
  "license": "MIT",
14
26
  "author": "WAVEINNO <info@waveinno.com>",
@@ -40,6 +52,7 @@
40
52
  },
41
53
  "files": [
42
54
  "dist",
55
+ "bin",
43
56
  "README.md",
44
57
  "LICENSE",
45
58
  "CHANGELOG.md"
@@ -113,5 +126,8 @@
113
126
  "react-dom": "^19.3.0",
114
127
  "tailwindcss": "^4.3.3",
115
128
  "typescript": "^7.0.2"
129
+ },
130
+ "bin": {
131
+ "waveinno-ui": "bin/waveinno-ui.mjs"
116
132
  }
117
133
  }
@@ -1,53 +0,0 @@
1
- import * as React from "react";
2
- /**
3
- * AppLauncher — the "switch app" button: a 3×3 grid of dots that opens the
4
- * apps a person can move to. The dots fold into a × while it is open, so the
5
- * same spot closes it.
6
- *
7
- * Five looks — set `variant`:
8
- * grid — the default: tiles in threes. Up to nine apps.
9
- * list — rows with a line on each app. When the names alone do not say enough.
10
- * spotlight — a list beside a preview of the app under the pointer. A suite's front door.
11
- * glass — frosted tiles, for a launcher over a map, a photo, a hero.
12
- * dark — ink tiles that glow in each app's colour.
13
- *
14
- * For a product inside AppShell, the shell's own switcher (⌘⇧K) already does
15
- * this; AppLauncher is for everything outside it: marketing sites, consoles,
16
- * a header that is not the shell's.
17
- */
18
- export interface LauncherApp {
19
- id: string;
20
- name: string;
21
- /** One line on what it is for. Shown by `list` and `spotlight`. */
22
- description?: string;
23
- /** A finished mark (WaveMark, an <img>), or a plain icon drawn on `tint`. */
24
- icon: React.ReactNode;
25
- /** Tile colour behind a plain icon: any CSS colour or gradient. Leave out for a finished mark. */
26
- tint?: string;
27
- /** Where it opens. Without one, the app is a button and `onSelect` decides. */
28
- href?: string;
29
- badge?: React.ReactNode;
30
- /** Shown, not openable: coming soon, not on the plan. */
31
- disabled?: boolean;
32
- }
33
- export type AppLauncherVariant = "grid" | "list" | "spotlight" | "glass" | "dark";
34
- export interface AppLauncherProps {
35
- apps: LauncherApp[];
36
- /** The `id` of the app the person is in. */
37
- current?: string;
38
- onSelect?: (app: LauncherApp) => void;
39
- variant?: AppLauncherVariant;
40
- /** `icon`: the dots alone (headers). `chip`: the current app's tile and name beside them. */
41
- trigger?: "icon" | "chip";
42
- title?: React.ReactNode;
43
- /** Top right of the panel: "Manage", a settings link. */
44
- action?: React.ReactNode;
45
- /** Under the apps: "All products", a link to the marketplace. */
46
- footer?: React.ReactNode;
47
- align?: "start" | "center" | "end";
48
- side?: "top" | "right" | "bottom" | "left";
49
- open?: boolean;
50
- onOpenChange?: (open: boolean) => void;
51
- className?: string;
52
- }
53
- export declare function AppLauncher({ apps, current, onSelect, variant, trigger, title, action, footer, align, side, open: openProp, onOpenChange, className, }: AppLauncherProps): React.JSX.Element;
@@ -1 +0,0 @@
1
- "use client";import{Fragment as _,jsx as e,jsxs as i}from"react/jsx-runtime";import*as b from"@radix-ui/react-popover";import{ArrowUpRight as M,Check as w}from"lucide-react";import*as N from"react";import{cn as c}from"../../lib/utils.js";const G={grid:"w-[328px] border-border bg-raised",list:"w-[340px] border-border bg-raised",spotlight:"w-[560px] max-w-[calc(100vw-24px)] border-border bg-raised p-0",glass:"w-[328px] border-white/50 bg-raised/65 backdrop-blur-2xl backdrop-saturate-150 shadow-[inset_0_1px_0_rgba(255,255,255,.6),var(--elev3)] dark:border-white/10 dark:shadow-[inset_0_1px_0_rgba(255,255,255,.08),var(--elev3)]",dark:"w-[328px] border-white/10 text-white bg-[var(--ink)] bg-[image:radial-gradient(120%_60%_at_0%_0%,color-mix(in_srgb,var(--brand-2)_32%,transparent),transparent_60%)] [--text:#f5f6fa] [--body:#c9cce0] [--muted:#8d93b3] [--border:rgba(255,255,255,.1)] [--hover:rgba(255,255,255,.07)] [--primary:#a5a8ff] [--primary-subtle:rgba(139,143,248,.16)] [--raised:rgba(255,255,255,.06)] [--surface:rgba(255,255,255,.04)]"},U={icon:"size-9 rounded-full text-muted hover:bg-hover hover:text-text data-[state=open]:bg-hover data-[state=open]:text-text",chip:"h-9 gap-2 rounded-full border border-border bg-raised pl-1.5 pr-3 text-[13px] font-medium text-text shadow-[var(--lit),0_1px_2px_rgba(5,8,22,.06)] hover:bg-hover data-[state=open]:bg-hover"};function T({open:a}){return e("svg",{viewBox:"0 0 18 18",className:"size-[18px] shrink-0","aria-hidden":!0,children:Array.from({length:9},(n,s)=>{const r=Math.floor(s/3),o=s%3,p=r===o||r+o===2,g=a&&p?(1-o)*1.6:0,u=a&&p?(1-r)*1.6:0;return e("circle",{cx:3+o*6,cy:3+r*6,r:1.7,fill:"currentColor",style:{transform:`translate(${g}px, ${u}px) scale(${a&&!p?0:1})`,transformOrigin:`${3+o*6}px ${3+r*6}px`,transition:`transform 260ms cubic-bezier(.3,.7,.2,1) ${a?s*12:(8-s)*12}ms`}},s)})})}function x({app:a,size:n,glow:s}){const r={sm:"size-8 rounded-[calc(9px*var(--radius-scale))] [&_svg]:size-4",md:"size-12 rounded-[calc(14px*var(--radius-scale))] [&_svg]:size-[22px]",lg:"size-14 rounded-[calc(16px*var(--radius-scale))] [&_svg]:size-6"}[n];return a.tint?e("span",{className:c("relative isolate grid shrink-0 place-items-center overflow-hidden text-white","before:absolute before:inset-x-0 before:top-0 before:-z-10 before:h-1/2 before:bg-[linear-gradient(180deg,rgba(255,255,255,.28),transparent)]",r),style:{background:a.tint,boxShadow:`inset 0 1px 0 rgba(255,255,255,.35), inset 0 -8px 16px -8px rgba(5,8,22,.3), 0 1px 2px rgba(5,8,22,.18)${s?", 0 10px 24px -10px color-mix(in srgb, var(--brand-2) 70%, transparent)":""}`},children:a.icon}):e("span",{className:c("grid shrink-0 place-items-center [&>img]:size-full [&>*]:max-h-full [&>*]:max-w-full",r),children:a.icon})}function j(a){return n=>{const s=Array.from(n.currentTarget.querySelectorAll("[data-launcher-app]:not([aria-disabled='true'])")),r=s.indexOf(document.activeElement);if(r<0)return;const o={ArrowRight:1,ArrowLeft:-1,ArrowDown:a,ArrowUp:-a}[n.key];let p;o!==void 0?p=s[Math.min(Math.max(r+o,0),s.length-1)]:n.key==="Home"?p=s[0]:n.key==="End"&&(p=s[s.length-1]),p&&(n.preventDefault(),p.focus())}}function y({app:a,current:n,onChoose:s,className:r,children:o,onFocus:p,onMouseEnter:g}){const u={"data-launcher-app":"","aria-current":n?"page":void 0,"aria-disabled":a.disabled||void 0,className:c("outline-none focus-visible:ring-2 focus-visible:ring-primary/45",a.disabled&&"pointer-events-none opacity-45",r),onFocus:p,onMouseEnter:g};return a.href&&!a.disabled?e("a",{href:a.href,...u,onClick:()=>s(a),children:o}):e("button",{type:"button",disabled:a.disabled,...u,onClick:()=>s(a),children:o})}function W({apps:a,current:n,onSelect:s,variant:r="grid",trigger:o="icon",title:p="Apps",action:g,footer:u,align:C="end",side:O="bottom",open:k,onOpenChange:S,className:D}){const[H,$]=N.useState(!1),h=k??H,A=t=>{k===void 0&&$(t),S?.(t)},m=a.find(t=>t.id===n),[K,L]=N.useState(n),l=a.find(t=>t.id===K)??m??a[0],z=r==="grid"||r==="glass"||r==="dark",R=j(z?3:1),f=N.useRef(null),v=t=>{s?.(t),A(!1)},P=i("div",{className:c("flex items-center justify-between gap-2",r==="spotlight"?"border-b border-border px-4 py-3":"px-1 pb-2.5"),children:[e("p",{className:"text-[13px] font-semibold text-text",children:p}),g]}),q=e("div",{ref:f,role:"list",onKeyDown:R,className:"grid grid-cols-3 gap-1",children:a.map(t=>{const d=t.id===n;return e("div",{role:"listitem",children:i(y,{app:t,current:d,onChoose:v,className:c("group/app relative flex w-full cursor-pointer flex-col items-center gap-2 rounded-[calc(12px*var(--radius-scale))] px-1 pb-2.5 pt-3 text-center transition-colors duration-150",r==="glass"?"hover:bg-white/40 focus-visible:bg-white/40 dark:hover:bg-white/10 dark:focus-visible:bg-white/10":"hover:bg-hover focus-visible:bg-hover",d&&(r==="dark"?"bg-white/[.06]":r==="glass"?"bg-white/30 dark:bg-white/[.06]":"bg-primary-subtle/60")),children:[i("span",{className:"relative transition-transform duration-200 ease-[cubic-bezier(.3,.7,.2,1)] group-hover/app:-translate-y-0.5 group-hover/app:scale-[1.04]",children:[e(x,{app:t,size:"md",glow:r==="dark"}),d?e("span",{className:"absolute -bottom-1 -right-1 grid size-4 place-items-center rounded-full bg-primary text-white ring-2 ring-[var(--raised)] [&_svg]:size-2.5",children:e(w,{strokeWidth:3.5})}):null]}),e("span",{className:"w-full truncate text-[12px] font-medium leading-4 text-text",children:t.name}),t.badge?e("span",{className:"-mt-1 scale-90",children:t.badge}):null]})},t.id)})}),E=e("div",{ref:f,role:"list",onKeyDown:R,className:c("flex flex-col gap-0.5",r==="spotlight"&&"p-1.5"),children:a.map(t=>{const d=t.id===n,V=r==="spotlight"&&t.id===l?.id;return e("div",{role:"listitem",children:i(y,{app:t,current:d,onChoose:v,onFocus:()=>L(t.id),onMouseEnter:()=>L(t.id),className:c("group/app flex w-full cursor-pointer items-center gap-3 rounded-[calc(10px*var(--radius-scale))] px-2 py-2 text-left transition-colors duration-150",V?"bg-raised shadow-[var(--lit),0_1px_2px_rgba(5,8,22,.06),0_4px_12px_-4px_rgba(5,8,22,.14)] ring-1 ring-border":"hover:bg-hover focus-visible:bg-hover"),children:[e(x,{app:t,size:r==="spotlight"?"sm":"md"}),i("span",{className:"min-w-0 flex-1",children:[i("span",{className:"flex items-center gap-2",children:[e("span",{className:c("truncate text-[13px] font-medium",d?"text-primary":"text-text"),children:t.name}),t.badge]}),t.description&&r==="list"?e("span",{className:"mt-0.5 block truncate text-[11.5px] text-muted",children:t.description}):null]}),d?e(w,{className:"size-4 shrink-0 text-primary"}):e(M,{className:"size-4 shrink-0 text-muted opacity-0 transition-[opacity,transform] duration-150 group-hover/app:translate-x-0.5 group-hover/app:-translate-y-0.5 group-hover/app:opacity-100"})]})},t.id)})}),F=i(_,{children:[P,i("div",{className:"grid min-h-[260px] grid-cols-1 sm:grid-cols-[220px_1fr]",children:[e("div",{className:"border-border bg-surface sm:border-r",children:E}),l?i("div",{className:"hidden flex-col p-5 animate-[wave-in_220ms_var(--ease)] sm:flex",children:[e(x,{app:l,size:"lg",glow:!0}),i("div",{className:"mt-4 flex items-center gap-2",children:[e("p",{className:"disp text-[16px] font-semibold text-text",children:l.name}),l.badge]}),l.description?e("p",{className:"mt-1 text-[12.5px] leading-[18px] text-body",children:l.description}):null,e("div",{className:"mt-auto pt-5",children:l.id===n?i("p",{className:"flex items-center gap-1.5 text-[12px] font-medium text-primary",children:[e(w,{className:"size-3.5"})," You are here"]}):i(y,{app:l,current:!1,onChoose:v,className:"inline-flex h-8 cursor-pointer items-center gap-1.5 rounded-full bg-[linear-gradient(135deg,var(--brand-1),var(--brand-2))] px-3.5 text-[12.5px] font-medium text-white shadow-[inset_0_1px_0_rgba(255,255,255,.25),0_6px_14px_-6px_color-mix(in_srgb,var(--brand-1)_70%,transparent)] transition-[filter] hover:brightness-110",children:["Open ",l.name," ",e(M,{className:"size-3.5"})]})})]},l.id):null]}),u?e("div",{className:"border-t border-border bg-surface px-4 py-2.5 text-[12px] text-muted",children:u}):null]});return i(b.Root,{open:h,onOpenChange:A,children:[e(b.Trigger,{"aria-label":m?`${m.name}. Switch app`:"Switch app",className:c("inline-flex shrink-0 cursor-pointer items-center justify-center outline-none transition-colors duration-150 focus-visible:ring-2 focus-visible:ring-primary/45",U[o],D),children:o==="chip"&&m?i(_,{children:[e("span",{className:"[&_svg]:!size-3.5",children:e(x,{app:m,size:"sm"})}),e("span",{className:"max-w-[120px] truncate",children:m.name}),e("span",{className:"text-muted",children:e(T,{open:h})})]}):e(T,{open:h})}),e(b.Portal,{children:e(b.Content,{"data-pop":"",align:C,side:O,sideOffset:8,collisionPadding:12,onOpenAutoFocus:t=>{t.preventDefault();const d=f.current;(d?.querySelector("[data-launcher-app][aria-current]")??d?.querySelector("[data-launcher-app]"))?.focus()},className:c("z-50 overflow-hidden rounded-[calc(18px*var(--radius-scale))] border p-2.5 shadow-elev3 outline-none",G[r]),children:r==="spotlight"?F:i(_,{children:[e("div",{className:"px-1 pt-1",children:P}),z?q:E,u?e("div",{className:"mt-2 border-t border-border px-1 pt-2.5 text-[12px] text-muted",children:u}):null]})})})]})}export{W as AppLauncher};
@@ -1,28 +0,0 @@
1
- import * as React from "react";
2
- export interface KanbanColumn {
3
- id: string;
4
- label: string;
5
- /** Accent for the column rule and the count chip. */
6
- color: string;
7
- }
8
- /**
9
- * Kanban board — the pipeline surface from docs/crm-module.md §3.
10
- *
11
- * Native HTML5 drag and drop, with a keyboard path that does the same job:
12
- * focus a card and press ← / → to move it a stage. A board that can only be
13
- * operated with a mouse is a board half the organisation cannot use, and in a
14
- * sales tool the stage change *is* the record update.
15
- *
16
- * Columns keep a fixed width and scroll horizontally. Making them flex would
17
- * squeeze seven stages into unreadable slivers on a laptop.
18
- */
19
- export declare function KanbanBoard<T>({ columns, items, getId, getColumn, onMove, renderCard, renderColumnSummary, className, }: {
20
- columns: KanbanColumn[];
21
- items: T[];
22
- getId: (item: T) => string;
23
- getColumn: (item: T) => string;
24
- onMove: (id: string, toColumn: string) => void;
25
- renderCard: (item: T) => React.ReactNode;
26
- renderColumnSummary?: (column: KanbanColumn, items: T[]) => React.ReactNode;
27
- className?: string;
28
- }): React.JSX.Element;
@@ -1 +0,0 @@
1
- "use client";import{jsx as a,jsxs as g}from"react/jsx-runtime";import*as h from"react";import{cn as c}from"../../lib/utils.js";function k({columns:l,items:m,getId:b,getColumn:p,onMove:f,renderCard:N,renderColumnSummary:x,className:y}){const[d,u]=h.useState(null),[D,i]=h.useState(null),v=(e,o)=>{const s=m.find(n=>b(n)===e);if(!s)return;const r=l.findIndex(n=>n.id===p(s)),t=l[r+o];t&&f(e,t.id)};return a("div",{className:c("flex gap-3 overflow-x-auto pb-3",y),children:l.map(e=>{const o=m.filter(r=>p(r)===e.id),s=D===e.id;return g("section",{"aria-label":e.label,onDragOver:r=>{r.preventDefault(),i(e.id)},onDragLeave:()=>i(r=>r===e.id?null:r),onDrop:r=>{r.preventDefault(),d&&f(d,e.id),u(null),i(null)},className:c("flex w-[268px] shrink-0 flex-col rounded-card border bg-surface transition-colors",s?"border-primary bg-selected":"border-border"),children:[g("header",{className:"border-b border-border px-3 py-2.5",children:[g("div",{className:"flex items-center gap-2",children:[a("span",{className:"h-3.5 w-1 shrink-0 rounded-full",style:{background:e.color},"aria-hidden":!0}),a("h3",{className:"flex-1 truncate text-[13px] font-semibold text-text",children:e.label}),a("span",{className:"num rounded-full bg-hover px-1.5 text-[11px] font-medium leading-[18px] text-muted",children:o.length})]}),x?a("div",{className:"mt-1.5",children:x(e,o)}):null]}),a("div",{className:"flex min-h-24 flex-1 flex-col gap-2 p-2",children:o.length===0?a("p",{className:"px-2 py-6 text-center text-xs text-muted",children:"Nothing at this stage"}):o.map(r=>{const t=b(r);return a("div",{tabIndex:0,role:"button",draggable:!0,onDragStart:()=>u(t),onDragEnd:()=>{u(null),i(null)},onKeyDown:n=>{n.key==="ArrowRight"?(n.preventDefault(),v(t,1)):n.key==="ArrowLeft"&&(n.preventDefault(),v(t,-1))},className:c("cursor-grab rounded-ctl border border-border bg-raised p-2.5 text-left","transition-[box-shadow,border-color,opacity] duration-150","hover:border-border2 hover:shadow-elev2 active:cursor-grabbing",d===t&&"opacity-40"),children:N(r)},t)})})]},e.id)})})}export{k as KanbanBoard};
@@ -1,51 +0,0 @@
1
- import * as React from "react";
2
- /**
3
- * OrgChart — who reports to whom: a company, a site's staff, a franchise.
4
- *
5
- * A top-down tree with rounded connectors. Each person with reports carries a
6
- * count that folds and unfolds their team. Opening a team never moves the
7
- * person you clicked: the chart is re-anchored under them before the frame is
8
- * painted, the connector draws down, the team springs in one by one, and the
9
- * view glides to show all of it. Closing plays the same in reverse. Drag the
10
- * background to pan (it glides on when let go), Ctrl/⌘ + wheel or the toolbar
11
- * to zoom; Fit, Expand all and Collapse all are one click. Departments get a
12
- * colour (pass `departments`, or one is chosen from the name).
13
- *
14
- * Looks:
15
- * card — the default: a card per person with photo, role and department.
16
- * gradient — the top of the tree in the brand gradient, department-coloured cards below.
17
- * avatar — photo-first: large round portraits with names under them.
18
- * compact — small pills, for large organisations.
19
- * list — an indented outline with guide lines, for phones and very wide teams.
20
- */
21
- export type OrgChartVariant = "card" | "gradient" | "avatar" | "compact" | "list";
22
- export interface OrgNode {
23
- id: string;
24
- name: string;
25
- title?: string;
26
- department?: string;
27
- /** Photo URL; without one, initials. */
28
- avatar?: string;
29
- /** Beside the name: "New", "On leave". */
30
- badge?: React.ReactNode;
31
- children?: OrgNode[];
32
- }
33
- export interface OrgChartProps {
34
- data: OrgNode;
35
- variant?: OrgChartVariant;
36
- /** Department → colour. Departments not listed get one from a palette. */
37
- departments?: Record<string, string>;
38
- /** Ids folded at first. Or `collapseDepth` folds everything below a depth. */
39
- defaultCollapsed?: string[];
40
- collapseDepth?: number;
41
- selected?: string;
42
- onSelect?: (node: OrgNode) => void;
43
- /** Height of the chart box (not used by `list`). */
44
- height?: number;
45
- className?: string;
46
- }
47
- declare const ids: (n: OrgNode) => string[];
48
- export declare function OrgChart({ data, variant, departments, defaultCollapsed, collapseDepth, selected, onSelect, height, className, }: OrgChartProps): React.JSX.Element;
49
- /** Every id in a tree: handy for "expand all" outside the chart. */
50
- export declare const orgIds: typeof ids;
51
- export {};
@@ -1 +0,0 @@
1
- "use client";import{jsx as r,jsxs as d}from"react/jsx-runtime";import{ChevronDown as ie,ChevronRight as oe,ChevronsDownUp as le,ChevronsUpDown as ce,Maximize2 as de,Minus as ue,Plus as pe}from"lucide-react";import*as h from"react";import{cn as w}from"../../lib/utils.js";import{Avatar as I}from"../ui/avatar.js";const re=["#4f46e5","#0ea5e9","#10b981","#f59e0b","#e11d48","#7c3aed","#0d9488","#ea580c"],ge=t=>[...t].reduce((_,y)=>_*31+y.charCodeAt(0)>>>0,7),j=t=>(t.children??[]).reduce((_,y)=>_+1+j(y),0),ne=t=>[t.id,...(t.children??[]).flatMap(ne)],me=220,U=()=>typeof window<"u"&&window.matchMedia("(prefers-reduced-motion: reduce)").matches;function we({data:t,variant:_="card",departments:y,defaultCollapsed:v,collapseDepth:C,selected:k,onSelect:$,height:R=520,className:o}){const c=h.useMemo(()=>{if(v)return new Set(v);if(C===void 0)return new Set;const s=new Set,x=(O,N)=>{N>=C&&O.children?.length&&s.add(O.id),O.children?.forEach(P=>x(P,N+1))};return x(t,0),s},[t,v,C]),[p,a]=h.useState(c),g=s=>s.department?y?.[s.department]??re[ge(s.department)%re.length]:"var(--primary)";return _==="list"?r(xe,{data:t,folded:p,toggle:x=>a(O=>{const N=new Set(O);return N.has(x)?N.delete(x):N.add(x),N}),tone:g,selected:k,onSelect:$,className:o}):r(be,{data:t,variant:_,folded:p,setFolded:a,tone:g,selected:k,onSelect:$,height:R,className:o})}function be({data:t,variant:_,folded:y,setFolded:v,tone:C,selected:k,onSelect:$,height:R,className:o}){const c=h.useRef(null),p=h.useRef(null),[a,g]=h.useState({x:0,y:28,z:1}),[s,x]=h.useState(!1),[O,N]=h.useState(new Set),[P,H]=h.useState(!1),S=h.useRef(a);S.current=a;const E=h.useRef(null),T=h.useRef(null),m=h.useRef(void 0),A=e=>p.current?.querySelector(`[data-org-card="${CSS.escape(e)}"]`)?.getBoundingClientRect(),J=(e,n)=>{x(n&&!U()),g(e)},D=h.useCallback(e=>{const n=c.current,u=p.current;if(!n||!u)return;const l=u.offsetWidth,i=u.offsetHeight,b=Math.max(.3,Math.min(1,(n.clientWidth-64)/l,(n.clientHeight-64)/i));x(e&&!U()),g({x:(n.clientWidth-l*b)/2,y:Math.max(28,(n.clientHeight-i*b)/2),z:b})},[]);h.useLayoutEffect(()=>D(!1),[D]);const se=e=>{const n=A(e);if(y.has(e)){n&&(E.current={id:e,x:n.left,y:n.top,reveal:!0}),v(l=>{const i=new Set(l);return i.delete(e),i});return}N(l=>new Set(l).add(e)),window.setTimeout(()=>{const l=A(e);l&&(E.current={id:e,x:l.left,y:l.top,reveal:!1}),v(i=>new Set(i).add(e)),N(i=>{const b=new Set(i);return b.delete(e),b})},U()?0:me)},Q=e=>{const n=A(t.id);n&&(E.current={id:t.id,x:n.left,y:n.top,reveal:!1}),v(e),requestAnimationFrame(()=>requestAnimationFrame(()=>D(!0)))};h.useLayoutEffect(()=>{const e=E.current;if(!e)return;E.current=null;const n=A(e.id);if(!n)return;const u=S.current,l={...u,x:u.x+(e.x-n.left),y:u.y+(e.y-n.top)};x(!1),g(l),e.reveal&&window.setTimeout(()=>{const i=c.current,b=p.current?.querySelector(`[data-org-li="${CSS.escape(e.id)}"]`);if(!i||!b)return;const f=i.getBoundingClientRect(),L=b.getBoundingClientRect(),M=40,z=S.current;let F=z.z;L.width>f.width-M*2&&(F=Math.max(.35,z.z*((f.width-M*2)/L.width)));const q=F/z.z,V=(L.left-f.left-z.x)*q+z.x,K=(L.top-f.top-z.y)*q+z.y,W=L.width*q,te=L.height*q;let X=z.x,Z=z.y;W>f.width-M*2?X+=f.width/2-(V+W/2):V<M?X+=M-V:V+W>f.width-M&&(X+=f.width-M-(V+W)),K+te>f.height-M&&(Z+=Math.max(M-K,f.height-M-(K+te))),(X!==z.x||Z!==z.y||F!==z.z)&&J({x:X,y:Z,z:F},!0)},60)},[y]);const B=(e,n,u)=>{const l=c.current;if(!l)return;const i=S.current,b=Math.min(1.6,Math.max(.3,e)),f=n??l.clientWidth/2,L=u??l.clientHeight/2;J({z:b,x:f-(f-i.x)/i.z*b,y:L-(L-i.y)/i.z*b},!0)},ee=()=>{m.current&&cancelAnimationFrame(m.current),m.current=void 0};h.useEffect(()=>ee,[]);const Y="grid size-8 cursor-pointer place-items-center rounded-[calc(8px*var(--radius-scale))] text-muted outline-none transition-[background-color,color,transform] hover:bg-hover hover:text-text active:scale-90 focus-visible:ring-2 focus-visible:ring-primary/40 [&_svg]:size-4",G="520ms cubic-bezier(.22,1,.36,1)";return d("div",{ref:c,className:w("relative touch-none select-none overflow-hidden rounded-[calc(16px*var(--radius-scale))] border border-border bg-surface",P?"cursor-grabbing":"cursor-grab",o),style:{height:R,backgroundImage:"radial-gradient(color-mix(in srgb, var(--border2) 60%, transparent) 1px, transparent 1px)",backgroundSize:`${20*a.z}px ${20*a.z}px`,backgroundPosition:`${a.x}px ${a.y}px`,transition:s?`background-position ${G}, background-size ${G}`:void 0},onPointerDown:e=>{e.target.closest("button, a, input")||(ee(),x(!1),H(!0),T.current={x:e.clientX,y:e.clientY,vx:a.x,vy:a.y,t:performance.now(),speedX:0,speedY:0},e.currentTarget.setPointerCapture(e.pointerId))},onPointerMove:e=>{const n=T.current;if(!n)return;const u=performance.now(),l=n.vx+e.clientX-n.x,i=n.vy+e.clientY-n.y,b=S.current,f=Math.max(1,u-n.t);n.speedX=n.speedX*.6+(l-b.x)/f*.4,n.speedY=n.speedY*.6+(i-b.y)/f*.4,n.t=u,g({...b,x:l,y:i})},onPointerUp:()=>{const e=T.current;if(T.current=null,H(!1),!e||U())return;let n=e.speedX*16,u=e.speedY*16;if(Math.hypot(n,u)<1.5||performance.now()-e.t>80)return;const l=()=>{n*=.92,u*=.92,g(i=>({...i,x:i.x+n,y:i.y+u})),m.current=Math.hypot(n,u)>.4?requestAnimationFrame(l):void 0};m.current=requestAnimationFrame(l)},onWheel:e=>{if(!(e.ctrlKey||e.metaKey))return;e.preventDefault();const n=c.current.getBoundingClientRect();B(S.current.z*(e.deltaY<0?1.12:1/1.12),e.clientX-n.left,e.clientY-n.top)},onDoubleClick:e=>{if(e.target.closest("button, a, input"))return;const n=c.current.getBoundingClientRect();B(S.current.z*1.4,e.clientX-n.left,e.clientY-n.top)},children:[r("div",{ref:p,className:"wm-org absolute left-0 top-0 w-max origin-top-left will-change-transform",style:{transform:`translate3d(${a.x}px, ${a.y}px, 0) scale(${a.z})`,transition:s?`transform ${G}`:void 0,"--org-line":_==="gradient"?"color-mix(in srgb, var(--primary) 55%, var(--border2))":void 0},onTransitionEnd:e=>{e.target===e.currentTarget&&x(!1)},children:r("ul",{children:r(ae,{node:t,index:0,depth:0,variant:_,folded:y,leaving:O,toggle:se,tone:C,selected:k,onSelect:$})})}),d("div",{className:"absolute right-3 top-3 flex items-center gap-0.5 rounded-[calc(11px*var(--radius-scale))] border border-border bg-raised/90 p-1 shadow-[var(--lit),0_4px_14px_-6px_rgba(5,8,22,.25)] backdrop-blur",children:[r("button",{type:"button","aria-label":"Zoom out",className:Y,onClick:()=>B(a.z/1.25),children:r(ue,{})}),d("span",{className:"num w-11 text-center text-[11.5px] font-medium tabular-nums text-muted",children:[Math.round(a.z*100),"%"]}),r("button",{type:"button","aria-label":"Zoom in",className:Y,onClick:()=>B(a.z*1.25),children:r(pe,{})}),r("span",{className:"mx-0.5 h-5 w-px bg-border"}),r("button",{type:"button","aria-label":"Fit to the box",className:Y,onClick:()=>D(!0),children:r(de,{})}),r("button",{type:"button","aria-label":"Expand everyone",className:Y,onClick:()=>Q(new Set),children:r(ce,{})}),r("button",{type:"button","aria-label":"Collapse to the top",className:Y,onClick:()=>Q(new Set((t.children??[]).filter(e=>e.children?.length).map(e=>e.id))),children:r(le,{})})]})]})}function ae({node:t,index:_,depth:y,variant:v,folded:C,leaving:k,toggle:$,tone:R,selected:o,onSelect:c}){const p=t.children??[],a=p.length>0&&!C.has(t.id),g=k.has(t.id),s=R(t),x=o===t.id,O=j(t),N=p.length?d("button",{type:"button",onClick:()=>$(t.id),"aria-expanded":a&&!g,"aria-label":`${a&&!g?"Hide":"Show"} ${t.name}'s team (${O})`,className:w("absolute -bottom-3 left-1/2 z-10 flex h-6 -translate-x-1/2 cursor-pointer items-center gap-0.5 rounded-full border px-2 text-[11px] font-semibold outline-none","shadow-[0_2px_6px_-2px_rgba(5,8,22,.25)] transition-[background-color,color,border-color,transform,box-shadow] duration-300 ease-[cubic-bezier(.3,1.4,.5,1)] hover:scale-110 active:scale-95 focus-visible:ring-2 focus-visible:ring-primary/40",a&&!g?"border-border bg-raised text-muted hover:text-text":"border-transparent bg-[linear-gradient(135deg,var(--brand-1),var(--brand-2))] text-white hover:shadow-[0_6px_16px_-6px_var(--brand-1)]"),children:[r("span",{className:"num",children:O}),r(ie,{className:w("size-3 transition-transform duration-300 ease-[cubic-bezier(.3,1.4,.5,1)]",a&&!g&&"rotate-180")})]}):null,P=c?()=>c(t):void 0,H=c?"button":"div",S=c?{type:"button",onClick:P}:{},E="transition-[border-color,box-shadow,transform] duration-300 ease-[cubic-bezier(.22,1,.36,1)]";let T;if(v==="avatar")T=d("div",{"data-org-card":t.id,className:"relative flex w-36 flex-col items-center pb-4 text-center",children:[r(H,{...S,className:w("relative rounded-full outline-none focus-visible:ring-2 focus-visible:ring-primary/50",E,c&&"cursor-pointer hover:-translate-y-1"),children:r("span",{className:w("block rounded-full p-[3px] transition-shadow duration-300",x&&"shadow-[0_0_0_5px_var(--primary-subtle),0_12px_28px_-12px_var(--primary)]"),style:{background:`conic-gradient(from 210deg, ${s}, color-mix(in srgb, ${s} 30%, transparent), ${s})`},children:r("span",{className:"block rounded-full bg-raised p-[3px]",children:r(I,{name:t.name,src:t.avatar,size:"xl"})})})}),r("p",{className:"mt-2 text-[13.5px] font-semibold leading-5 text-text",children:t.name}),t.title?r("p",{className:"text-[11.5px] leading-4 text-muted",children:t.title}):null,t.badge?r("div",{className:"mt-1.5",children:t.badge}):null,N]});else if(v==="compact")T=d("div",{"data-org-card":t.id,className:"relative pb-3",children:[d(H,{...S,className:w("flex h-10 max-w-[200px] items-center gap-2 rounded-full border bg-raised pl-1.5 pr-3.5 text-left shadow-[var(--lit),0_1px_2px_rgba(5,8,22,.06)] outline-none focus-visible:ring-2 focus-visible:ring-primary/40",E,x?"border-primary shadow-[0_0_0_3px_var(--primary-subtle)]":"border-border hover:border-border2",c&&"cursor-pointer hover:-translate-y-0.5 hover:shadow-[var(--lit),0_8px_18px_-10px_rgba(5,8,22,.3)]"),children:[r(I,{name:t.name,src:t.avatar,size:"md"}),d("span",{className:"min-w-0",children:[r("span",{className:"block truncate text-[12.5px] font-semibold leading-4 text-text",children:t.name}),t.title?r("span",{className:"block truncate text-[10.5px] leading-[14px] text-muted",children:t.title}):null]}),r("span",{className:"ml-1 size-2 shrink-0 rounded-full",style:{background:s}})]}),N]});else{const m=v==="gradient"&&y===0;T=d("div",{"data-org-card":t.id,className:"relative pb-3",children:[d(H,{...S,className:w("relative flex w-[212px] flex-col overflow-hidden rounded-[calc(14px*var(--radius-scale))] border text-left outline-none focus-visible:ring-2 focus-visible:ring-primary/40",E,m?"border-transparent bg-[linear-gradient(140deg,var(--brand-1),color-mix(in_srgb,var(--brand-1)_78%,#000)_45%,var(--brand-2))] text-white shadow-[inset_0_1px_0_rgba(255,255,255,.25),0_16px_34px_-16px_var(--brand-1)] [--muted:rgba(255,255,255,.75)] [--text:#fff]":"border-border bg-raised shadow-[var(--lit),0_1px_2px_rgba(5,8,22,.05),0_8px_20px_-14px_rgba(5,8,22,.25)]",x&&!m&&"border-primary shadow-[0_0_0_3px_var(--primary-subtle),0_14px_30px_-16px_color-mix(in_srgb,var(--primary)_65%,transparent)]",c&&"cursor-pointer hover:-translate-y-1 hover:shadow-[var(--lit),0_18px_36px_-18px_rgba(5,8,22,.35)]"),children:[m?null:r("span",{"aria-hidden":!0,className:"h-[3px] w-full",style:{background:v==="gradient"?`linear-gradient(90deg, ${s}, color-mix(in srgb, ${s} 40%, transparent))`:s}}),d("span",{className:"flex items-center gap-3 p-3",children:[r("span",{className:w("rounded-full",m&&"ring-2 ring-white/40"),style:m?void 0:{boxShadow:`0 0 0 2px var(--raised), 0 0 0 3.5px color-mix(in srgb, ${s} 55%, transparent)`},children:r(I,{name:t.name,src:t.avatar,size:"lg"})}),d("span",{className:"min-w-0 flex-1",children:[d("span",{className:"flex items-center gap-1.5",children:[r("span",{className:"truncate text-[13.5px] font-semibold text-text",children:t.name}),t.badge]}),t.title?r("span",{className:"block truncate text-[12px] text-muted",children:t.title}):null]})]}),t.department?d("span",{className:w("mx-3 mb-3 inline-flex w-fit items-center gap-1.5 rounded-full px-2 py-0.5 text-[10.5px] font-semibold",m&&"bg-white/15 text-white"),style:m?void 0:{background:`color-mix(in srgb, ${s} 12%, transparent)`,color:s},children:[r("span",{className:"size-1.5 rounded-full",style:{background:m?"#fff":s}}),t.department]}):null]}),N]})}return d("li",{"data-org-li":t.id,style:{"--i":_},children:[T,a?r("ul",{className:g?"wm-org-leave":"wm-org-enter",children:p.map((m,A)=>r(ae,{node:m,index:A,depth:y+1,variant:v,folded:C,leaving:k,toggle:$,tone:R,selected:o,onSelect:c},m.id))}):null]})}function xe({data:t,folded:_,toggle:y,tone:v,selected:C,onSelect:k,className:$}){const R=(o,c)=>{const p=o.children??[],a=p.length>0&&!_.has(o.id),g=C===o.id;return d("li",{children:[d("div",{className:w("group/row flex items-center gap-2.5 rounded-[calc(10px*var(--radius-scale))] py-1.5 pr-2 transition-colors duration-200",g?"bg-primary-subtle":"hover:bg-hover"),style:{paddingLeft:6+c*22},children:[p.length?r("button",{type:"button","aria-expanded":a,"aria-label":`${a?"Hide":"Show"} ${o.name}'s team`,onClick:()=>y(o.id),className:"grid size-6 shrink-0 cursor-pointer place-items-center rounded-[6px] text-muted transition-colors hover:bg-border/60 hover:text-text",children:r(oe,{className:w("size-3.5 transition-transform duration-300 ease-[cubic-bezier(.3,1.4,.5,1)]",a&&"rotate-90")})}):r("span",{className:"size-6 shrink-0"}),d("button",{type:"button",onClick:k?()=>k(o):void 0,className:w("flex min-w-0 flex-1 items-center gap-2.5 text-left outline-none",k&&"cursor-pointer"),children:[r("span",{className:"rounded-full",style:{boxShadow:`0 0 0 2px var(--canvas), 0 0 0 3.5px color-mix(in srgb, ${v(o)} 60%, transparent)`},children:r(I,{name:o.name,src:o.avatar,size:"md"})}),d("span",{className:"min-w-0 flex-1",children:[d("span",{className:"flex items-center gap-1.5",children:[r("span",{className:w("truncate text-[13px] font-semibold",g?"text-primary":"text-text"),children:o.name}),o.badge]}),r("span",{className:"block truncate text-[11.5px] text-muted",children:[o.title,o.department].filter(Boolean).join(" \xB7 ")})]})]}),p.length?r("span",{className:"num shrink-0 rounded-full bg-hover px-2 py-px text-[11px] font-medium text-muted group-hover/row:bg-raised",children:j(o)}):null]}),p.length?r("div",{className:w("grid transition-[grid-template-rows,opacity] duration-[380ms] ease-[cubic-bezier(.22,1,.36,1)]",a?"grid-rows-[1fr] opacity-100":"grid-rows-[0fr] opacity-0"),children:d("ul",{className:"relative min-h-0 overflow-hidden",inert:!a,children:[r("span",{"aria-hidden":!0,className:"absolute bottom-3 top-0 w-px bg-border",style:{left:18+c*22}}),p.map(s=>R(s,c+1))]})}):null]},o.id)};return r("ul",{className:w("space-y-px",$),children:R(t,0)})}const ye=ne;export{we as OrgChart,ye as orgIds};
@@ -1,14 +0,0 @@
1
- import * as React from "react";
2
- export interface ColorPickerProps {
3
- value?: string;
4
- defaultValue?: string;
5
- onValueChange?: (hex: string) => void;
6
- /** Quick choices under the strip; `false` for none. */
7
- swatches?: string[] | false;
8
- className?: string;
9
- }
10
- export declare function ColorPicker({ value, defaultValue, onValueChange, swatches, className }: ColorPickerProps): React.JSX.Element;
11
- /** The picker behind a field-like button showing the colour and its hex. */
12
- export declare function ColorPickerPopover({ value, defaultValue, onValueChange, swatches, "aria-label": ariaLabel, className, }: ColorPickerProps & {
13
- "aria-label"?: string;
14
- }): React.JSX.Element;
@@ -1 +0,0 @@
1
- "use client";import{jsx as l,jsxs as m}from"react/jsx-runtime";import*as x from"@radix-ui/react-popover";import{Pipette as $}from"lucide-react";import*as p from"react";import{cn as P}from"../../lib/utils.js";function _(o){let t=o.trim().replace(/^#/,"");if(t.length===3&&(t=[...t].map(e=>e+e).join("")),!/^[0-9a-f]{6}$/i.test(t))return null;const a=parseInt(t,16);return[a>>16&255,a>>8&255,a&255]}const H=(o,t,a)=>`#${[o,t,a].map(e=>Math.round(e).toString(16).padStart(2,"0")).join("")}`;function N(o,t,a){o/=255,t/=255,a/=255;const e=Math.max(o,t,a),i=e-Math.min(o,t,a);let d=0;return i&&(d=e===o?(t-a)/i%6:e===t?(a-o)/i+2:(o-t)/i+4),{h:(d*60+360)%360,s:e?i/e:0,v:e}}function k({h:o,s:t,v:a}){const e=i=>{const d=(i+o/60)%6;return(a-a*t*Math.max(0,Math.min(d,4-d,1)))*255};return H(e(5),e(3),e(1))}const S=["#4f46e5","#1f45ea","#7c3aed","#db2777","#e11d48","#ea580c","#d97706","#16a34a","#059669","#0d9488","#0284c7","#334155"];function T(o){const t=p.useRef(null),a=e=>{const i=t.current.getBoundingClientRect();o(Math.min(1,Math.max(0,(e.clientX-i.left)/i.width)),Math.min(1,Math.max(0,(e.clientY-i.top)/i.height)))};return{ref:t,onPointerDown:e=>{e.preventDefault(),e.currentTarget.setPointerCapture(e.pointerId),e.currentTarget.focus(),a(e)},onPointerMove:e=>{e.currentTarget.hasPointerCapture(e.pointerId)&&a(e)}}}function L({value:o,defaultValue:t="#4f46e5",onValueChange:a,swatches:e=S,className:i}){const[d,b]=p.useState(t),c=(o??d).toLowerCase(),[s,g]=p.useState(()=>N(..._(c)??[79,70,229])),[h,w]=p.useState(c);p.useEffect(()=>{if(k(s)!==c){const r=_(c);if(r){const n=N(...r);g(u=>({h:n.s===0||n.v===0?u.h:n.h,s:n.v===0?u.s:n.s,v:n.v}))}}w(c)},[c]);const v=r=>{g(r);const n=k(r);o===void 0&&b(n),a?.(n)},y=r=>{const n=_(r);if(!n)return;const u=H(...n);o===void 0&&b(u),a?.(u)},E=T((r,n)=>v({...s,s:r,v:1-n})),R=T(r=>v({...s,h:r*360})),M=(r,n)=>{const u=r.shiftKey?.1:.02,f={ArrowLeft:[-u,0],ArrowRight:[u,0],ArrowUp:[0,u],ArrowDown:[0,-u]}[r.key];f&&(r.preventDefault(),v(n==="area"?{...s,s:Math.min(1,Math.max(0,s.s+f[0])),v:Math.min(1,Math.max(0,s.v+f[1]))}:{...s,h:(s.h+(f[0]+f[1])*360+360)%360}))},D=typeof window<"u"&&"EyeDropper"in window,C=k({h:s.h,s:1,v:1});return m("div",{className:P("w-64 space-y-3",i),children:[l("div",{...E,role:"slider",tabIndex:0,"aria-label":"Saturation and brightness","aria-valuetext":`Saturation ${Math.round(s.s*100)}%, brightness ${Math.round(s.v*100)}%`,onKeyDown:r=>M(r,"area"),className:"relative h-40 cursor-crosshair touch-none rounded-[calc(10px*var(--radius-scale))] outline-none ring-offset-2 ring-offset-[var(--raised)] focus-visible:ring-2 focus-visible:ring-primary/50",style:{background:`linear-gradient(to top, #000, transparent), linear-gradient(to right, #fff, ${C})`},children:l("span",{"aria-hidden":!0,className:"pointer-events-none absolute size-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white shadow-[0_0_0_1px_rgba(5,8,22,.25),0_2px_6px_rgba(5,8,22,.35)]",style:{left:`${s.s*100}%`,top:`${(1-s.v)*100}%`,background:c}})}),l("div",{...R,role:"slider",tabIndex:0,"aria-label":"Hue","aria-valuemin":0,"aria-valuemax":360,"aria-valuenow":Math.round(s.h),onKeyDown:r=>M(r,"hue"),className:"relative h-3 cursor-pointer touch-none rounded-full outline-none ring-offset-2 ring-offset-[var(--raised)] focus-visible:ring-2 focus-visible:ring-primary/50",style:{background:"linear-gradient(to right, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00)"},children:l("span",{"aria-hidden":!0,className:"pointer-events-none absolute top-1/2 size-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white shadow-[0_0_0_1px_rgba(5,8,22,.25),0_2px_6px_rgba(5,8,22,.35)]",style:{left:`${s.h/360*100}%`,background:C}})}),m("div",{className:"flex items-center gap-2",children:[l("span",{className:"size-8 shrink-0 rounded-[calc(8px*var(--radius-scale))] shadow-[inset_0_0_0_1px_rgba(5,8,22,.12)]",style:{background:c}}),m("label",{className:"flex h-8 min-w-0 flex-1 items-center gap-1 rounded-ctl border border-border2 bg-raised px-2 focus-within:border-primary focus-within:ring-2 focus-within:ring-primary/20",children:[l("span",{className:"text-[12px] text-muted",children:"#"}),l("input",{value:h.replace(/^#/,""),"aria-label":"Hex colour",spellCheck:!1,maxLength:7,onChange:r=>{const n=`#${r.target.value.replace(/[^0-9a-f]/gi,"").slice(0,6)}`;w(n),n.length===7&&y(n)},onBlur:()=>w(c),className:"num min-w-0 flex-1 bg-transparent font-mono text-[12.5px] uppercase text-text outline-none"})]}),D?l("button",{type:"button","aria-label":"Pick a colour from the screen",onClick:async()=>{try{const r=await new window.EyeDropper().open();y(r.sRGBHex)}catch{}},className:"grid size-8 shrink-0 cursor-pointer place-items-center rounded-ctl border border-border2 bg-raised text-muted transition-colors hover:bg-hover hover:text-text",children:l($,{className:"size-4"})}):null]}),e&&e.length?l("div",{className:"grid grid-cols-6 gap-1.5",children:e.map(r=>{const n=r.toLowerCase()===c;return l("button",{type:"button","aria-label":r,"aria-pressed":n,onClick:()=>y(r),className:P("h-7 cursor-pointer rounded-[calc(7px*var(--radius-scale))] shadow-[inset_0_0_0_1px_rgba(5,8,22,.12)] transition-transform hover:scale-110",n&&"ring-2 ring-[var(--text)] ring-offset-2 ring-offset-[var(--raised)]"),style:{background:r}},r)})}):null]})}function B({value:o,defaultValue:t="#4f46e5",onValueChange:a,swatches:e,"aria-label":i="Colour",className:d}){const[b,c]=p.useState(t),s=o??b,g=h=>{o===void 0&&c(h),a?.(h)};return m(x.Root,{children:[m(x.Trigger,{"aria-label":`${i}: ${s}`,className:P("inline-flex h-ctl cursor-pointer items-center gap-2 rounded-ctl border border-border2 bg-raised pl-1.5 pr-3 text-[13px] text-text outline-none transition-[border-color,box-shadow]","hover:border-muted/50 focus-visible:border-primary focus-visible:ring-2 focus-visible:ring-primary/20 data-[state=open]:border-primary data-[state=open]:ring-2 data-[state=open]:ring-primary/20",d),children:[l("span",{className:"size-6 rounded-[calc(6px*var(--radius-scale))] shadow-[inset_0_0_0_1px_rgba(5,8,22,.12)]",style:{background:s}}),l("span",{className:"num font-mono text-[12.5px] uppercase",children:s})]}),l(x.Portal,{children:l(x.Content,{"data-pop":"",align:"start",sideOffset:6,collisionPadding:12,className:"z-50 rounded-dlg border border-border bg-raised p-3 shadow-elev3 outline-none",children:l(L,{value:s,onValueChange:g,swatches:e})})})]})}export{L as ColorPicker,B as ColorPickerPopover};