@nextlyhq/ui 0.0.2-alpha.4 → 0.0.2-alpha.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/utils.cjs ADDED
@@ -0,0 +1,13 @@
1
+ 'use strict';
2
+
3
+ var clsx = require('clsx');
4
+ var tailwindMerge = require('tailwind-merge');
5
+
6
+ // src/lib/utils.ts
7
+ function cn(...inputs) {
8
+ return tailwindMerge.twMerge(clsx.clsx(inputs));
9
+ }
10
+
11
+ exports.cn = cn;
12
+ //# sourceMappingURL=utils.cjs.map
13
+ //# sourceMappingURL=utils.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/utils.ts"],"names":["twMerge","clsx"],"mappings":";;;;;;AAeO,SAAS,MAAM,MAAA,EAAsB;AAC1C,EAAA,OAAOA,qBAAA,CAAQC,SAAA,CAAK,MAAM,CAAC,CAAA;AAC7B","file":"utils.cjs","sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Utility function to merge class names with Tailwind CSS support.\n *\n * Combines clsx for conditional class names with tailwind-merge\n * to properly handle Tailwind CSS class conflicts.\n *\n * @example\n * ```typescript\n * cn('px-2 py-1', 'px-4') // => 'py-1 px-4'\n * cn('text-muted-foreground', condition && 'text-foreground')\n * ```\n */\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n"]}
@@ -0,0 +1,17 @@
1
+ import { ClassValue } from 'clsx';
2
+
3
+ /**
4
+ * Utility function to merge class names with Tailwind CSS support.
5
+ *
6
+ * Combines clsx for conditional class names with tailwind-merge
7
+ * to properly handle Tailwind CSS class conflicts.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * cn('px-2 py-1', 'px-4') // => 'py-1 px-4'
12
+ * cn('text-muted-foreground', condition && 'text-foreground')
13
+ * ```
14
+ */
15
+ declare function cn(...inputs: ClassValue[]): string;
16
+
17
+ export { cn };
@@ -0,0 +1,17 @@
1
+ import { ClassValue } from 'clsx';
2
+
3
+ /**
4
+ * Utility function to merge class names with Tailwind CSS support.
5
+ *
6
+ * Combines clsx for conditional class names with tailwind-merge
7
+ * to properly handle Tailwind CSS class conflicts.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * cn('px-2 py-1', 'px-4') // => 'py-1 px-4'
12
+ * cn('text-muted-foreground', condition && 'text-foreground')
13
+ * ```
14
+ */
15
+ declare function cn(...inputs: ClassValue[]): string;
16
+
17
+ export { cn };
package/dist/utils.mjs ADDED
@@ -0,0 +1,11 @@
1
+ import { clsx } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+
4
+ // src/lib/utils.ts
5
+ function cn(...inputs) {
6
+ return twMerge(clsx(inputs));
7
+ }
8
+
9
+ export { cn };
10
+ //# sourceMappingURL=utils.mjs.map
11
+ //# sourceMappingURL=utils.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/utils.ts"],"names":[],"mappings":";;;;AAeO,SAAS,MAAM,MAAA,EAAsB;AAC1C,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B","file":"utils.mjs","sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Utility function to merge class names with Tailwind CSS support.\n *\n * Combines clsx for conditional class names with tailwind-merge\n * to properly handle Tailwind CSS class conflicts.\n *\n * @example\n * ```typescript\n * cn('px-2 py-1', 'px-4') // => 'py-1 px-4'\n * cn('text-muted-foreground', condition && 'text-foreground')\n * ```\n */\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n"]}
@@ -0,0 +1,178 @@
1
+ # Plugin UI authoring guide
2
+
3
+ How to build admin UI — plugin views, custom fields, dashboards — that looks native
4
+ to the Nextly admin and inherits light/dark mode for free.
5
+
6
+ The rules here are a **contract**. A lint guard (`pnpm lint:design`, run in CI) fails the
7
+ build on the most common violations, so following this guide is not optional for code that
8
+ ships in this repo.
9
+
10
+ ---
11
+
12
+ ## 1. Where your styles come from
13
+
14
+ Nextly's design system lives in [`@nextlyhq/ui`](../README.md). It ships two CSS entry
15
+ points:
16
+
17
+ | Entry point | Contents | Use when |
18
+ | ------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------- |
19
+ | `@nextlyhq/ui/theme.css` | Design tokens + `@theme` mappings + `@keyframes`. No reset, no `@import`. | You run your own Tailwind v4 build and want the tokens. |
20
+ | `@nextlyhq/ui/styles.css` | Pre-compiled bundle (Tailwind + tokens + reset). Zero config. | Standalone UI, or a quick embed with no build wiring. |
21
+
22
+ **Inside a Nextly admin plugin you usually need neither.** The admin already loads
23
+ `@nextlyhq/admin/style.css`, which defines every token scoped to `.nextly-admin` (and
24
+ `.nextly-admin.dark` for dark mode). Plugin admin views render inside that scope, so the tokens
25
+ are already on the page. Your plugin CSS should **consume** those tokens, never redefine
26
+ them.
27
+
28
+ Only import a stylesheet yourself when you render outside the admin shell (e.g. a public
29
+ page or a standalone tool) — then import `@nextlyhq/ui/styles.css` once at the root.
30
+
31
+ ---
32
+
33
+ ## 2. The token contract
34
+
35
+ Tokens are **complete color values** (OKLCH). Reference them directly:
36
+
37
+ ```css
38
+ .my-panel {
39
+ background: var(--nx-card);
40
+ color: var(--nx-foreground);
41
+ border: 1px solid var(--nx-border);
42
+ border-radius: var(--radius);
43
+ }
44
+ ```
45
+
46
+ ### Never wrap a token in `hsl()` / `rgb()`
47
+
48
+ Older Nextly builds stored tokens as bare HSL channels (`--nx-primary: 221 83% 53%`) and code
49
+ wrapped them: `hsl(var(--nx-primary))`. **That convention is gone.** Tokens are now full colors
50
+ (`--nx-primary: oklch(0 0 0)`), so `hsl(var(--nx-primary))` becomes `hsl(oklch(0 0 0))`, which is
51
+ invalid CSS — the browser drops the declaration and your element loses its color. Always:
52
+
53
+ ```css
54
+ /* ✗ wrong — produces hsl(oklch(...)), silently dropped */
55
+ color: hsl(var(--nx-foreground));
56
+
57
+ /* ✓ right */
58
+ color: var(--nx-foreground);
59
+ ```
60
+
61
+ ### Alpha / transparency
62
+
63
+ You can no longer do `hsl(var(--nx-primary) / 0.1)`. Use `color-mix`:
64
+
65
+ ```css
66
+ /* 10% primary over transparent */
67
+ background: color-mix(in srgb, var(--nx-primary) 10%, transparent);
68
+
69
+ /* a subtle hover tint from the foreground */
70
+ background: color-mix(in srgb, var(--nx-foreground) 6%, transparent);
71
+ ```
72
+
73
+ ### The tokens you may use
74
+
75
+ Colors: `--nx-background` / `--nx-foreground`, `--nx-card` / `--nx-card-foreground`,
76
+ `--nx-popover` / `--nx-popover-foreground`, `--nx-primary` / `--nx-primary-foreground`,
77
+ `--nx-secondary` / `--nx-secondary-foreground`, `--nx-muted` / `--nx-muted-foreground`,
78
+ `--nx-accent` / `--nx-accent-foreground`, `--nx-destructive`, `--nx-success`, `--nx-warning`,
79
+ `--nx-border`, `--nx-border-strong`, `--nx-input`, `--nx-ring`,
80
+ `--nx-sidebar-background` and the `--nx-sidebar-*` family.
81
+
82
+ Sizing: `--radius` (the system is square — this is `0`; always route corner radius through
83
+ it so a future rounding is one edit), and the control-height scale `--nx-control-height`,
84
+ `--nx-control-height-sm`, `--nx-control-height-md`, `--nx-control-height-lg` for anything that should
85
+ line up with admin inputs and buttons.
86
+
87
+ Do not hardcode hex, `rgb()`, `rgba()`, or named colors. The only literals allowed are
88
+ `transparent`, `currentColor`, and `inherit`.
89
+
90
+ ---
91
+
92
+ ## 3. Prefer components and utilities over hand-rolled CSS
93
+
94
+ For new UI, reach for `@nextlyhq/ui` components and Tailwind utility classes before writing
95
+ a stylesheet. They are already token-driven, accessible, and dark-mode-correct.
96
+
97
+ ```tsx
98
+ import { Button, Input, Select, Switch, Badge } from "@nextlyhq/ui";
99
+ import { cn } from "@nextlyhq/ui/utils";
100
+
101
+ <Button variant="outline" size="sm">Add field</Button>
102
+ <Input placeholder="Form name" />
103
+ ```
104
+
105
+ - Merge conditional classes with `cn()` (clsx + tailwind-merge).
106
+ - Express variants with `cva` (class-variance-authority), the way the components do.
107
+ - Target internal parts with the `data-slot` attribute, not by re-styling the component.
108
+
109
+ Hand-written CSS is still fine for bespoke layouts (a drag-and-drop canvas, a box-model
110
+ control). When you write it, it must obey the token contract in section 2 and be scoped
111
+ (section 5).
112
+
113
+ ---
114
+
115
+ ## 4. Dark mode is automatic — don't fight it
116
+
117
+ The admin flips every token under `.nextly-admin.dark`. If you only ever reference tokens, your
118
+ UI switches with it and you never write a dark rule.
119
+
120
+ - **Never** hardcode a light or dark color.
121
+ - **Never** gate admin theming on `@media (prefers-color-scheme: dark)` — admin dark mode is
122
+ class-based (`.dark` / `.nextly-admin.dark`), independent of the OS. A media query makes your
123
+ UI ignore the admin's own theme toggle. Use Tailwind's `dark:` variant or `.nextly-admin.dark`
124
+ descendant selectors if you truly need a mode-specific tweak.
125
+
126
+ ---
127
+
128
+ ## 5. Scope your class names
129
+
130
+ Plugin CSS shares the page with the admin. Prefix every class so nothing leaks:
131
+ `nx-pb-*` (page builder), `field-editor-*` / `form-settings-*` (form builder), etc. Never
132
+ style bare element selectors (`button {}`, `input {}`) or admin utility classes.
133
+
134
+ ---
135
+
136
+ ## 6. Responsive: containers, not the viewport
137
+
138
+ Plugin panels live in variable-width columns, so a media query keyed to the viewport is the
139
+ wrong tool — it fires on window size while your panel might be half that. Use CSS container
140
+ queries (Tailwind v4 core) against the nearest sized ancestor:
141
+
142
+ ```tsx
143
+ <div className="@container/panel">
144
+ <div className="grid grid-cols-1 @2xl/panel:grid-cols-2">...</div>
145
+ </div>
146
+ ```
147
+
148
+ Container breakpoints are **container-sized**, not viewport-sized: `@md` = a 448px container,
149
+ `@2xl` = 672px, `@4xl` = 896px. Pick the threshold by testing the panel, not by mapping from
150
+ `md:`/`lg:`.
151
+
152
+ ---
153
+
154
+ ## 7. The guardrail
155
+
156
+ `pnpm lint:design` (run in CI) scans the admin and plugin packages and fails on:
157
+
158
+ - `hsl(var(--…))` / `rgb(var(--…))` — the dead HSL-channel convention (section 2).
159
+ - Hardcoded colors: hex, `rgb(`, `rgba(`, `hsl(` with literal channels (outside `url(...)`
160
+ data URIs). Allowed literals: `transparent`, `currentColor`, `inherit`.
161
+ - `!important` outside the small, reviewed allowlist.
162
+
163
+ Run it locally before pushing. If a finding is a genuine false positive, add it to the
164
+ allowlist in the script with a one-line reason — don't silence the whole check.
165
+
166
+ ---
167
+
168
+ ## Checklist
169
+
170
+ - [ ] Colors come from `var(--token)` — no `hsl()`/`rgb()` wrappers, no hardcoded values.
171
+ - [ ] Alpha uses `color-mix(in srgb, var(--token) N%, transparent)`.
172
+ - [ ] Corner radius routes through `var(--radius)`; control heights use `--nx-control-height*`.
173
+ - [ ] New UI uses `@nextlyhq/ui` components + Tailwind utilities where practical.
174
+ - [ ] No `@media (prefers-color-scheme)`; dark mode inherited from the admin.
175
+ - [ ] Class names are prefixed/scoped; no bare element selectors.
176
+ - [ ] Responsive rules use container queries, tested against the panel width.
177
+ - [ ] `pnpm lint:design` passes.
178
+ - [ ] Verified in the admin in **both** light and dark mode.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextlyhq/ui",
3
- "version": "0.0.2-alpha.4",
3
+ "version": "0.0.2-alpha.40",
4
4
  "description": "Nextly UI — Reusable React component library for Nextly plugins and custom admin UIs",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -17,13 +17,45 @@
17
17
  "types": "dist/index.d.ts",
18
18
  "exports": {
19
19
  ".": {
20
- "types": "./dist/index.d.ts",
21
- "import": "./dist/index.mjs",
22
- "require": "./dist/index.cjs"
23
- }
20
+ "import": {
21
+ "types": "./dist/index.d.ts",
22
+ "default": "./dist/index.mjs"
23
+ },
24
+ "require": {
25
+ "types": "./dist/index.d.cts",
26
+ "default": "./dist/index.cjs"
27
+ }
28
+ },
29
+ "./tailwind-preset": {
30
+ "import": {
31
+ "types": "./dist/tailwind-preset.d.ts",
32
+ "default": "./dist/tailwind-preset.mjs"
33
+ },
34
+ "require": {
35
+ "types": "./dist/tailwind-preset.d.cts",
36
+ "default": "./dist/tailwind-preset.cjs"
37
+ }
38
+ },
39
+ "./utils": {
40
+ "import": {
41
+ "types": "./dist/utils.d.ts",
42
+ "default": "./dist/utils.mjs"
43
+ },
44
+ "require": {
45
+ "types": "./dist/utils.d.cts",
46
+ "default": "./dist/utils.cjs"
47
+ }
48
+ },
49
+ "./theme.css": "./dist/theme.css",
50
+ "./styles.css": "./dist/styles.css",
51
+ "./styles.scoped.css": "./dist/styles.scoped.css"
24
52
  },
53
+ "sideEffects": [
54
+ "**/*.css"
55
+ ],
25
56
  "files": [
26
- "dist"
57
+ "dist",
58
+ "docs"
27
59
  ],
28
60
  "engines": {
29
61
  "node": ">=20.0.0"
@@ -34,22 +66,29 @@
34
66
  "react-dom": "^18.0.0 || ^19.0.0"
35
67
  },
36
68
  "devDependencies": {
69
+ "@tailwindcss/cli": "^4.3.2",
70
+ "@types/culori": "^4.0.1",
37
71
  "@types/react": "19.2.0",
38
72
  "@types/react-dom": "19.2.0",
39
73
  "@vitejs/plugin-react": "^5.1.0",
40
- "@vitest/coverage-v8": "^4.0.8",
41
- "@vitest/ui": "^4.0.8",
42
- "eslint": "^9.34.0",
43
- "lucide-react": "^0.544.0",
74
+ "@vitest/coverage-v8": "^4.1.0",
75
+ "@vitest/ui": "^4.1.0",
76
+ "culori": "^4.0.2",
77
+ "eslint": "^9.39.1",
44
78
  "jsdom": "^27.1.0",
79
+ "lucide-react": "^0.544.0",
80
+ "postcss": "^8.5.6",
45
81
  "react": "19.2.0",
46
82
  "react-dom": "19.2.0",
83
+ "rimraf": "^6.1.3",
84
+ "tailwindcss": "^4.3.2",
47
85
  "tsup": "^8.5.0",
48
86
  "typescript": "^5.9.3",
49
87
  "vite-tsconfig-paths": "^5.1.4",
50
- "vitest": "^4.0.8",
51
- "@nextlyhq/eslint-config": "0.0.2-alpha.3",
52
- "@nextlyhq/tsconfig": "0.0.2-alpha.3"
88
+ "vitest": "^4.1.0",
89
+ "@nextlyhq/admin-css": "0.0.2-alpha.40",
90
+ "@nextlyhq/eslint-config": "0.0.2-alpha.40",
91
+ "@nextlyhq/tsconfig": "0.0.2-alpha.40"
53
92
  },
54
93
  "dependencies": {
55
94
  "@radix-ui/react-accordion": "^1.2.12",
@@ -90,8 +129,10 @@
90
129
  },
91
130
  "author": "Nextly <contact@nextlyhq.com> (https://nextlyhq.com)",
92
131
  "scripts": {
93
- "build": "tsup",
94
- "dev": "tsup --watch",
132
+ "build": "pnpm build:js && node scripts/build-css.mjs",
133
+ "build:js": "rimraf dist && tsup && tsup --config tsup.server-safe.config.ts && node scripts/check-client-directive.mjs",
134
+ "build:css": "node scripts/build-css.mjs",
135
+ "dev": "tsup --watch & tsup --config tsup.server-safe.config.ts --watch & wait",
95
136
  "check-types": "tsc --noEmit",
96
137
  "lint": "eslint . --max-warnings 0",
97
138
  "lint:fix": "eslint . --fix",