@esuiss/ui 0.6.0 → 0.7.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/README.md +19 -6
- package/index.mjs +5 -3
- package/lib/add.mjs +41 -7
- package/lib/init.mjs +9 -7
- package/lib/registry.mjs +4 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -47,7 +47,7 @@ export function Example() {
|
|
|
47
47
|
2. Adds the `@/*` import alias to `tsconfig.json` if it is missing.
|
|
48
48
|
3. Installs and wires up Tailwind CSS if needed (`postcss.config.mjs` for Next.js, `vite.config.ts` for Vite).
|
|
49
49
|
4. Writes the theme stylesheet to `app/globals.css` (Next.js) or `src/index.css` (Vite). It asks before replacing a stylesheet that already has content.
|
|
50
|
-
5. Sets up the
|
|
50
|
+
5. Sets up the font: the system font on macOS and iOS, Inter everywhere else. Inter is loaded with `next/font/google` in the root layout (Next.js), or with `@fontsource-variable/inter` imported from the stylesheet (Vite).
|
|
51
51
|
6. Installs [`@esuiss/tailwind`](https://www.npmjs.com/package/@esuiss/tailwind), which provides the variants, animations, and utilities the components rely on.
|
|
52
52
|
7. Adds the `button` component.
|
|
53
53
|
|
|
@@ -63,9 +63,10 @@ npx @esuiss/ui@latest init [options]
|
|
|
63
63
|
|
|
64
64
|
```bash
|
|
65
65
|
npx @esuiss/ui@latest add <component...> [options]
|
|
66
|
+
npx @esuiss/ui@latest add patterns <pattern...> [options]
|
|
66
67
|
```
|
|
67
68
|
|
|
68
|
-
Components are written to `components/ui/`
|
|
69
|
+
Components are written to `components/ui/`, patterns to `components/patterns/` and hooks to `hooks/` under your `@/*` alias root. Components they depend on are added too, and missing npm packages are installed with your package manager (npm, pnpm, yarn, or bun).
|
|
69
70
|
|
|
70
71
|
### Options
|
|
71
72
|
|
|
@@ -73,7 +74,7 @@ Components are written to `components/ui/` (and hooks to `hooks/`) under your `@
|
|
|
73
74
|
| --- | --- |
|
|
74
75
|
| `-o, --overwrite` | Replace local files that differ from the registry |
|
|
75
76
|
| `--diff` | Show how your local files differ from the registry |
|
|
76
|
-
| `-a, --all` | Add every component |
|
|
77
|
+
| `-a, --all` | Add every component, or every pattern with `add patterns` |
|
|
77
78
|
| `-y, --yes` | Skip confirmation prompts |
|
|
78
79
|
| `-c, --cwd <dir>` | Run in another project directory |
|
|
79
80
|
| `-h, --help` | Show help |
|
|
@@ -113,7 +114,7 @@ Your stylesheet holds the whole theme:
|
|
|
113
114
|
}
|
|
114
115
|
|
|
115
116
|
@theme inline {
|
|
116
|
-
--font-sans: "Inter Variable", sans-serif;
|
|
117
|
+
--font-sans: -apple-system, BlinkMacSystemFont, "Inter Variable", sans-serif;
|
|
117
118
|
--color-surface: var(--surface);
|
|
118
119
|
--color-label: var(--label);
|
|
119
120
|
--color-accent: var(--accent);
|
|
@@ -126,13 +127,14 @@ Colors are named by role, not by component:
|
|
|
126
127
|
- **Text:** `label`, `label-secondary`, `label-tertiary`, `label-quaternary`.
|
|
127
128
|
- **Control fills:** `fill`, `fill-secondary`, `fill-tertiary`.
|
|
128
129
|
- **Lines:** `separator`, `separator-strong`.
|
|
130
|
+
- **Scrim:** `scrim`, the dimmed layer behind dialogs, sheets and drawers.
|
|
129
131
|
- **Accent:** `accent`, `accent-hover`, `accent-pressed`, `on-accent` (text on the accent).
|
|
130
132
|
- **Links, focus and states:** `link`, `focus`, `danger`, `success`, `warning` and their `-surface` tints.
|
|
131
133
|
- **Palette:** `blue`, `green`, `orange`, `red`, `yellow`, `purple`, `pink`, `indigo`, `teal`, `mint`, `cyan`, `brown`, `gray`, used by charts.
|
|
132
134
|
|
|
133
135
|
- **Change a color:** edit its value under `:root` (light) and `.dark` (dark).
|
|
134
136
|
- **Add a color:** define it under `:root` and `.dark`, then map it in `@theme inline`, for example `--color-brand: var(--brand);`. Classes such as `bg-brand` and `text-brand` become available.
|
|
135
|
-
- **Change the font:** set `--font-sans` in `@theme inline`.
|
|
137
|
+
- **Change the font:** set `--font-sans` in `@theme inline`. By default it uses the system font on macOS and iOS and Inter elsewhere.
|
|
136
138
|
- **Corner radius:** `--radius` scales every rounded component.
|
|
137
139
|
|
|
138
140
|
## Dark mode
|
|
@@ -157,10 +159,21 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|
|
157
159
|
|
|
158
160
|
## Components
|
|
159
161
|
|
|
160
|
-
accordion, alert, alert-dialog, aspect-ratio, attachment, avatar, badge, breadcrumb, bubble, button, button-group, calendar, card, carousel, chart, checkbox, collapsible, combobox, command, context-menu, dialog, direction, drawer, dropdown-menu, empty, field, hover-card, input, input-group, input-otp, item, kbd, label, marker, menubar, message, message-scroller, native-select, navigation-menu, pagination, popover, progress, questionnaire, radio-group, resizable, scroll-area, select, separator, sheet, sidebar, skeleton, slider, spinner, switch, table, tabs, textarea, toast, toggle, toggle-group, tooltip
|
|
162
|
+
accordion, alert, alert-dialog, aspect-ratio, attachment, avatar, badge, breadcrumb, bubble, button, button-group, calendar, card, carousel, chart, checkbox, collapsible, combobox, command, context-menu, dialog, direction, drawer, dropdown-menu, empty, field, hover-card, input, input-group, input-otp, item, kbd, label, marker, menubar, message, message-scroller, native-select, navigation-menu, pagination, popover, progress, questionnaire, radio-group, resizable, scroll-area, select, separator, sheet, sidebar, skeleton, slider, spinner, switch, table, tabs, textarea, toast, toggle, toggle-group, tooltip, wheel-picker
|
|
161
163
|
|
|
162
164
|
Hooks: use-message-scroller, use-mobile, use-questionnaire
|
|
163
165
|
|
|
166
|
+
## Patterns
|
|
167
|
+
|
|
168
|
+
Patterns combine several components into one ready-made piece, such as a date and time picker. They are not added by `init` or `add --all`; add them by name, or all at once:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
npx @esuiss/ui add patterns date-time-picker
|
|
172
|
+
npx @esuiss/ui add patterns --all
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
The components a pattern uses are added with it.
|
|
176
|
+
|
|
164
177
|
## Troubleshooting
|
|
165
178
|
|
|
166
179
|
- **"Tailwind CSS 4.2 or later is required"**: upgrade with `npm install tailwindcss@latest` and run `init` again.
|
package/index.mjs
CHANGED
|
@@ -15,11 +15,12 @@ const usage = `${name} ${version}
|
|
|
15
15
|
Usage:
|
|
16
16
|
npx ${name} init [options] set up Tailwind, the theme, and the button
|
|
17
17
|
npx ${name} add <component...> [options] add components, e.g. add button sidebar
|
|
18
|
+
npx ${name} add patterns <pattern...> add patterns, e.g. add patterns date-time-picker
|
|
18
19
|
|
|
19
20
|
Options:
|
|
20
21
|
-o, --overwrite replace local files that differ from the registry
|
|
21
22
|
--diff show how local files differ from the registry
|
|
22
|
-
-a, --all add every component
|
|
23
|
+
-a, --all add every component, or every pattern after "patterns"
|
|
23
24
|
-y, --yes skip confirmation prompts
|
|
24
25
|
-c, --cwd <dir> project directory (defaults to the current directory)
|
|
25
26
|
-h, --help show this help
|
|
@@ -79,8 +80,9 @@ async function main() {
|
|
|
79
80
|
await init(parseArgs(args).options)
|
|
80
81
|
break
|
|
81
82
|
case "add": {
|
|
82
|
-
const
|
|
83
|
-
|
|
83
|
+
const patterns = args[0] === "patterns"
|
|
84
|
+
const { options, names } = parseArgs(patterns ? args.slice(1) : args)
|
|
85
|
+
await add(names, { ...options, patterns })
|
|
84
86
|
break
|
|
85
87
|
}
|
|
86
88
|
case "-v":
|
package/lib/add.mjs
CHANGED
|
@@ -4,7 +4,12 @@ import path from "node:path"
|
|
|
4
4
|
import { formatDiff } from "./diff.mjs"
|
|
5
5
|
import { CliError, color, list, note, step } from "./output.mjs"
|
|
6
6
|
import { detectProject, findAliasRoot, install } from "./project.mjs"
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
fetchRegistry,
|
|
9
|
+
fetchText,
|
|
10
|
+
isPattern,
|
|
11
|
+
resolveItems,
|
|
12
|
+
} from "./registry.mjs"
|
|
8
13
|
|
|
9
14
|
async function fetchFiles(items) {
|
|
10
15
|
return Promise.all(
|
|
@@ -93,6 +98,40 @@ async function showDiff(project, root, registry, names) {
|
|
|
93
98
|
if (!changes) step("No differences from the registry.")
|
|
94
99
|
}
|
|
95
100
|
|
|
101
|
+
function selectNames(registry, names, { all, patterns }) {
|
|
102
|
+
const inScope = (item) => isPattern(item) === patterns
|
|
103
|
+
const scoped = registry.items.filter(inScope)
|
|
104
|
+
const kind = patterns ? "pattern" : "component"
|
|
105
|
+
const command = patterns ? "add patterns" : "add"
|
|
106
|
+
|
|
107
|
+
if (all) {
|
|
108
|
+
if (!scoped.length) throw new CliError(`No ${kind}s are available yet.`)
|
|
109
|
+
return scoped.map((item) => item.name)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!names.length) {
|
|
113
|
+
throw new CliError(
|
|
114
|
+
`Name at least one ${kind}, e.g. npx @esuiss/ui ${command} ${patterns ? "<name>" : "button"}`
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const byName = new Map(registry.items.map((item) => [item.name, item]))
|
|
119
|
+
for (const name of names) {
|
|
120
|
+
const item = byName.get(name)
|
|
121
|
+
if (item && inScope(item)) continue
|
|
122
|
+
if (item) {
|
|
123
|
+
const other = patterns ? "add" : "add patterns"
|
|
124
|
+
throw new CliError(
|
|
125
|
+
`"${name}" is ${patterns ? "a component" : "a pattern"}. Run npx @esuiss/ui ${other} ${name}`
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
throw new CliError(
|
|
129
|
+
`Unknown ${kind}: ${name}.\nAvailable: ${scoped.map((entry) => entry.name).join(", ") || "none yet"}`
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
return names
|
|
133
|
+
}
|
|
134
|
+
|
|
96
135
|
export async function add(names, options) {
|
|
97
136
|
const project = detectProject(options.cwd)
|
|
98
137
|
const root = findAliasRoot(project.cwd)
|
|
@@ -103,12 +142,7 @@ export async function add(names, options) {
|
|
|
103
142
|
}
|
|
104
143
|
|
|
105
144
|
const registry = await fetchRegistry()
|
|
106
|
-
const selected =
|
|
107
|
-
if (!selected.length) {
|
|
108
|
-
throw new CliError(
|
|
109
|
-
"Name at least one component, e.g. npx @esuiss/ui add button"
|
|
110
|
-
)
|
|
111
|
-
}
|
|
145
|
+
const selected = selectNames(registry, names, options)
|
|
112
146
|
|
|
113
147
|
if (options.diff) await showDiff(project, root, registry, selected)
|
|
114
148
|
else await installItems(project, root, registry, selected, options)
|
package/lib/init.mjs
CHANGED
|
@@ -40,8 +40,10 @@ const TOOLING = {
|
|
|
40
40
|
}
|
|
41
41
|
const MINIMUM_TAILWIND = [4, 2]
|
|
42
42
|
const FONT_IMPORT = '@import "@fontsource-variable/inter";\n'
|
|
43
|
-
const FONT_FAMILY =
|
|
44
|
-
|
|
43
|
+
const FONT_FAMILY =
|
|
44
|
+
'--font-sans: -apple-system, BlinkMacSystemFont, "Inter Variable", sans-serif;'
|
|
45
|
+
const NEXT_FONT_FAMILY =
|
|
46
|
+
"--font-sans: -apple-system, BlinkMacSystemFont, var(--font-inter), sans-serif;"
|
|
45
47
|
const NEXT_LAYOUTS = [
|
|
46
48
|
"src/app/layout.tsx",
|
|
47
49
|
"app/layout.tsx",
|
|
@@ -49,7 +51,7 @@ const NEXT_LAYOUTS = [
|
|
|
49
51
|
"app/layout.jsx",
|
|
50
52
|
]
|
|
51
53
|
const NEXT_FONT_NOTE =
|
|
52
|
-
'Load Inter in your root layout with next/font/google: Inter({ subsets: ["latin", "latin-ext"], variable: "--font-
|
|
54
|
+
'Load Inter in your root layout with next/font/google: Inter({ subsets: ["latin", "latin-ext"], variable: "--font-inter" }), and add its .variable to the <html> className.'
|
|
53
55
|
|
|
54
56
|
function relative(project, file) {
|
|
55
57
|
return path.relative(project.cwd, file)
|
|
@@ -207,7 +209,7 @@ function configureNextFont(project) {
|
|
|
207
209
|
if (!file) return [NEXT_FONT_NOTE]
|
|
208
210
|
|
|
209
211
|
let text = readFileSync(file, "utf8")
|
|
210
|
-
if (/variable:\s*["']--font-
|
|
212
|
+
if (/variable:\s*["']--font-inter["']/.test(text)) return []
|
|
211
213
|
|
|
212
214
|
const html = text.match(/<html\b[^>]*>/)
|
|
213
215
|
if (!html) return [NEXT_FONT_NOTE]
|
|
@@ -216,10 +218,10 @@ function configureNextFont(project) {
|
|
|
216
218
|
if (literal) {
|
|
217
219
|
tag = tag.replace(
|
|
218
220
|
literal[0],
|
|
219
|
-
`className={\`\${
|
|
221
|
+
`className={\`\${fontInter.variable} ${literal[1]}\`}`
|
|
220
222
|
)
|
|
221
223
|
} else if (!/className=/.test(tag)) {
|
|
222
|
-
tag = tag.replace(/<html\b/, "<html className={
|
|
224
|
+
tag = tag.replace(/<html\b/, "<html className={fontInter.variable}")
|
|
223
225
|
} else {
|
|
224
226
|
return [NEXT_FONT_NOTE]
|
|
225
227
|
}
|
|
@@ -243,7 +245,7 @@ function configureNextFont(project) {
|
|
|
243
245
|
]
|
|
244
246
|
const last = imports.at(-1)
|
|
245
247
|
const at = last ? last.index + last[0].length : 0
|
|
246
|
-
const declaration = `\nconst
|
|
248
|
+
const declaration = `\nconst fontInter = Inter({\n subsets: ["latin", "latin-ext"],\n variable: "--font-inter",\n})${semi}\n`
|
|
247
249
|
text = text.slice(0, at) + declaration + text.slice(at)
|
|
248
250
|
|
|
249
251
|
writeFileSync(file, text)
|
package/lib/registry.mjs
CHANGED
|
@@ -20,14 +20,12 @@ export async function fetchRegistry() {
|
|
|
20
20
|
return JSON.parse(await fetchText("registry.json"))
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
export function isPattern(item) {
|
|
24
|
+
return item.type === "pattern"
|
|
25
|
+
}
|
|
26
|
+
|
|
23
27
|
export function resolveItems(registry, names) {
|
|
24
28
|
const byName = new Map(registry.items.map((item) => [item.name, item]))
|
|
25
|
-
const unknown = names.filter((name) => !byName.has(name))
|
|
26
|
-
if (unknown.length) {
|
|
27
|
-
throw new CliError(
|
|
28
|
-
`Unknown component: ${unknown.join(", ")}.\nAvailable: ${[...byName.keys()].join(", ")}`
|
|
29
|
-
)
|
|
30
|
-
}
|
|
31
29
|
|
|
32
30
|
const ordered = []
|
|
33
31
|
const seen = new Set()
|
package/package.json
CHANGED