plankui 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +104 -6
  2. package/index.mjs +36 -5
  3. package/package.json +15 -5
package/README.md CHANGED
@@ -1,13 +1,111 @@
1
- # plankui
1
+ <p align="center">
2
+ <a href="https://plankui.com"><img src="https://plankui.com/logo.svg" width="64" height="64" alt="PlankUI" /></a>
3
+ </p>
2
4
 
3
- Add [PlankUI](https://plankui.com) components to a React 19 + Tailwind CSS v4 project.
5
+ <h1 align="center">plankui</h1>
6
+
7
+ <p align="center">
8
+ The command line for <a href="https://plankui.com">PlankUI</a>: accessible React components for Tailwind CSS v4,<br />
9
+ copied into your project so you own every line.
10
+ </p>
11
+
12
+ <p align="center">
13
+ <a href="https://www.npmjs.com/package/plankui"><img src="https://img.shields.io/npm/v/plankui?style=flat-square&color=2f6bff&label=npm" alt="npm version" /></a>
14
+ <a href="https://www.npmjs.com/package/plankui"><img src="https://img.shields.io/npm/dm/plankui?style=flat-square&color=2f6bff" alt="Downloads a month" /></a>
15
+ <a href="https://github.com/VectorLabsHQ/plankui"><img src="https://img.shields.io/github/stars/VectorLabsHQ/plankui?style=flat-square&color=2f6bff&logo=github" alt="GitHub stars" /></a>
16
+ <a href="https://github.com/VectorLabsHQ/plankui/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/plankui?style=flat-square&color=2f6bff" alt="MIT licence" /></a>
17
+ </p>
18
+
19
+ <p align="center">
20
+ <a href="https://plankui.com">Website</a> ·
21
+ <a href="https://plankui.com/docs/installation">Installation</a> ·
22
+ <a href="https://plankui.com/docs/components">Components</a> ·
23
+ <a href="https://plankui.com/docs/theming">Theming</a> ·
24
+ <a href="https://plankui.com/docs/mcp">MCP server</a>
25
+ </p>
26
+
27
+ <p align="center">
28
+ <a href="https://plankui.com"><img src="https://plankui.com/readme/components-light.jpg" alt="PlankUI components: charts, forms, a command menu, navigation, toggles and an upload card" /></a>
29
+ </p>
30
+
31
+ ## Quick start
32
+
33
+ In a React 19 project with Tailwind CSS v4:
4
34
 
5
35
  ```bash
6
36
  npx plankui@latest init
7
37
  npx plankui@latest add button select dialog
8
- npx plankui@latest list
9
38
  ```
10
39
 
11
- `init` writes `tokens.css`, `styles.css` and the `cn` helper and installs the shared packages.
12
- `add` copies each component plus every PlankUI file it builds on, and installs their npm packages.
13
- Add `--yes` in CI or for coding agents; it accepts the defaults and keeps files that already exist.
40
+ That's it. `init` sets up the design tokens once, and `add` drops each component into `components/ui`, together with every PlankUI file it builds on and the npm packages it needs.
41
+
42
+ ```tsx
43
+ import { Button } from "@/components/ui/button";
44
+
45
+ export function Save() {
46
+ return <Button>Save changes</Button>;
47
+ }
48
+ ```
49
+
50
+ ## Why PlankUI
51
+
52
+ - **You own the code.** Components land in your repo as plain TypeScript. Read them, change any line, never wait on a package upgrade.
53
+ - **Beautiful by default.** Six themes and five component styles (Soft, Flat, Liquid glass, Keycap, Brutal), in light and dark, from one tokens file.
54
+ - **Accessible.** Keyboard support, focus rings, ARIA roles and reduced motion handled for you.
55
+ - **Agent ready.** The [PlankUI MCP server](https://www.npmjs.com/package/@plankui/mcp) gives Cursor, Claude Code, VS Code and other agents the real source of every component.
56
+
57
+ ## Commands
58
+
59
+ | Command | What it does |
60
+ | --- | --- |
61
+ | `plankui init` | Writes `tokens.css`, `styles.css` and the `cn` helper, and installs `clsx` and `tailwind-merge` |
62
+ | `plankui add <names…>` | Copies components and the PlankUI files they build on, then installs their npm packages |
63
+ | `plankui list` | Lists every component you can add |
64
+
65
+ ## Options
66
+
67
+ | Option | |
68
+ | --- | --- |
69
+ | `--yes`, `-y` | Answer every prompt with its default and keep files that already exist. Use it in CI and for coding agents |
70
+ | `--overwrite` | Replace files that already exist |
71
+ | `--no-install` | Write the files but skip installing npm packages |
72
+ | `--cwd <dir>` | Run in another folder |
73
+ | `--registry <url>` | Read components from another registry |
74
+
75
+ The CLI picks your package manager (npm, pnpm, yarn or bun) from your lockfile, never writes outside your project and has no dependencies of its own.
76
+
77
+ ## Components
78
+
79
+ 35 components today, with more every week.
80
+
81
+ | | |
82
+ | --- | --- |
83
+ | **Actions** | [Button](https://plankui.com/docs/components/button), [Segmented control](https://plankui.com/docs/components/segmented), [Dropdown menu](https://plankui.com/docs/components/dropdown-menu), [Button group](https://plankui.com/docs/components/button-group), [Copy button](https://plankui.com/docs/components/copy-button) |
84
+ | **Forms** | [Input](https://plankui.com/docs/components/input), [Textarea](https://plankui.com/docs/components/textarea), [Select](https://plankui.com/docs/components/select), [Checkbox](https://plankui.com/docs/components/checkbox), [Radio group](https://plankui.com/docs/components/radio-group), [Switch](https://plankui.com/docs/components/switch), [Slider](https://plankui.com/docs/components/slider), [Combobox](https://plankui.com/docs/components/combobox), [Input OTP](https://plankui.com/docs/components/input-otp), [Password input](https://plankui.com/docs/components/password-input) |
85
+ | **Overlays** | [Dialog](https://plankui.com/docs/components/dialog), [Popover](https://plankui.com/docs/components/popover), [Tooltip](https://plankui.com/docs/components/tooltip), [Sheet](https://plankui.com/docs/components/sheet), [Alert dialog](https://plankui.com/docs/components/alert-dialog) |
86
+ | **Feedback** | [Toast](https://plankui.com/docs/components/toast), [Alert](https://plankui.com/docs/components/alert), [Progress](https://plankui.com/docs/components/progress), [Skeleton](https://plankui.com/docs/components/skeleton), [Spinner](https://plankui.com/docs/components/spinner) |
87
+ | **Display** | [Card](https://plankui.com/docs/components/card), [Avatar](https://plankui.com/docs/components/avatar), [Badge](https://plankui.com/docs/components/badge), [Kbd](https://plankui.com/docs/components/kbd), [Separator](https://plankui.com/docs/components/separator), [Table](https://plankui.com/docs/components/table) |
88
+ | **Navigation** | [Tabs](https://plankui.com/docs/components/tabs), [Accordion](https://plankui.com/docs/components/accordion), [Breadcrumb](https://plankui.com/docs/components/breadcrumb), [Pagination](https://plankui.com/docs/components/pagination) |
89
+
90
+ ## Themes and styles
91
+
92
+ Set a theme and a style on `html`, or on any element to scope them:
93
+
94
+ ```html
95
+ <html data-theme="ember" data-style="keycap" class="dark">
96
+ ```
97
+
98
+ | Themes | Base, Ember, Grove, Nebula, Terminal, Candy |
99
+ | --- | --- |
100
+ | **Styles** | Soft, Flat, Liquid glass, Keycap, Brutal |
101
+ | **Accents** | Blue, Vermilion, Violet, Emerald, Rose, Amber, Mono, or any hue you like |
102
+
103
+ Try them live at [plankui.com](https://plankui.com/#themes).
104
+
105
+ ## Requirements
106
+
107
+ React 19, Tailwind CSS v4 and Node 18 or later. Guides for Vite, Next.js and React Router: [plankui.com/docs/installation](https://plankui.com/docs/installation).
108
+
109
+ ## Licence
110
+
111
+ MIT. Made by [Vector Labs](https://github.com/VectorLabsHQ).
package/index.mjs CHANGED
@@ -6,13 +6,13 @@
6
6
  // npx plankui@latest add button select dialog
7
7
  // npx plankui@latest list
8
8
  // npx plankui@latest login YOUR_LICENSE_KEY
9
- import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
9
+ import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
10
10
  import { homedir } from "node:os";
11
- import { dirname, join, relative } from "node:path";
11
+ import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
12
12
  import { spawnSync } from "node:child_process";
13
13
  import { createInterface } from "node:readline/promises";
14
14
 
15
- const VERSION = "0.1.0";
15
+ const VERSION = "0.1.2";
16
16
  const DEFAULT_REGISTRY = "https://plankui.com/r";
17
17
 
18
18
  /* ── Arguments ── */
@@ -147,10 +147,41 @@ async function fetchJson(name) {
147
147
  }
148
148
 
149
149
  /** Write files under the project base, keeping existing ones unless asked. Returns what happened per file. */
150
+ /** True when `p` is `root` itself or inside it. */
151
+ const inside = (root, p) => p === root || p.startsWith(root + sep);
152
+
153
+ /** The real path of `p`, or of its nearest existing parent, so links are followed before we compare. */
154
+ function realish(p) {
155
+ let dir = p;
156
+ while (!existsSync(dir) && dirname(dir) !== dir) dir = dirname(dir);
157
+ return resolve(realpathSync(dir), relative(dir, p));
158
+ }
159
+
160
+ /**
161
+ * Where a registry file may be written: inside the project, never above it.
162
+ * A registry is remote input (and --registry can point anywhere), so a path like "../../.zshrc",
163
+ * an absolute path, or a symlinked folder that leads outside the project stops the whole install.
164
+ */
165
+ function safeTarget(base, path) {
166
+ const bad = (why) => fail(`The registry sent an unsafe file path (${why}): ${JSON.stringify(path)}. Nothing was written.`);
167
+ if (typeof path !== "string" || !path.trim()) bad("empty");
168
+ if (path.includes("\0")) bad("null byte");
169
+ if (isAbsolute(path) || /^[a-zA-Z]:/.test(path) || path.startsWith("\\\\")) bad("absolute");
170
+ if (path.split(/[\\/]+/).includes("..")) bad("goes up a folder");
171
+ const root = resolve(base);
172
+ const target = resolve(root, path);
173
+ if (!inside(root, target) || target === root) bad("outside the project");
174
+ if (!inside(realish(root), realish(target))) bad("a linked folder leads outside the project");
175
+ if (existsSync(target) && lstatSync(target).isSymbolicLink()) bad("the file is a symlink");
176
+ return target;
177
+ }
178
+
150
179
  async function writeFiles(base, files) {
180
+ // Check every path first, so a bad one stops the install before any file is touched.
181
+ const targets = files.map((f) => safeTarget(base, f.path));
151
182
  const done = [];
152
- for (const f of files) {
153
- const target = join(base, f.path);
183
+ for (const [i, f] of files.entries()) {
184
+ const target = targets[i];
154
185
  const shown = relative(cwd, target);
155
186
  if (existsSync(target) && !overwrite) {
156
187
  const same = readFileSync(target, "utf8") === f.content;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "plankui",
3
- "version": "0.1.0",
4
- "description": "Add PlankUI components to your React and Tailwind CSS v4 project.",
3
+ "version": "0.1.2",
4
+ "description": "Add accessible React components for Tailwind CSS v4 to your project. Six themes, five styles, and you own the code.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "plankui": "index.mjs"
@@ -15,13 +15,22 @@
15
15
  },
16
16
  "keywords": [
17
17
  "react",
18
+ "react-19",
18
19
  "tailwindcss",
19
- "radix",
20
+ "tailwind-v4",
21
+ "radix-ui",
20
22
  "components",
21
23
  "ui",
24
+ "design-system",
25
+ "component-library",
26
+ "cli",
27
+ "accessible",
28
+ "dark-mode",
29
+ "themes",
30
+ "copy-paste",
22
31
  "plankui"
23
32
  ],
24
- "homepage": "https://plankui.com",
33
+ "homepage": "https://plankui.com/docs/installation",
25
34
  "license": "MIT",
26
35
  "repository": {
27
36
  "type": "git",
@@ -30,5 +39,6 @@
30
39
  },
31
40
  "bugs": {
32
41
  "url": "https://github.com/VectorLabsHQ/plankui/issues"
33
- }
42
+ },
43
+ "author": "Vector Labs (https://plankui.com)"
34
44
  }