@esuiss/ui 0.5.9 → 0.6.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.
Files changed (3) hide show
  1. package/README.md +26 -11
  2. package/lib/init.mjs +69 -2
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -47,8 +47,9 @@ 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. Installs [`@esuiss/tailwind`](https://www.npmjs.com/package/@esuiss/tailwind), which provides the variants, animations, and utilities the components rely on.
51
- 6. Adds the `button` component.
50
+ 5. Sets up the Inter font: with `next/font/google` in the root layout (Next.js), or with `@fontsource-variable/inter` imported from the stylesheet (Vite).
51
+ 6. Installs [`@esuiss/tailwind`](https://www.npmjs.com/package/@esuiss/tailwind), which provides the variants, animations, and utilities the components rely on.
52
+ 7. Adds the `button` component.
52
53
 
53
54
  ## Commands
54
55
 
@@ -96,28 +97,42 @@ Your stylesheet holds the whole theme:
96
97
  ```css
97
98
  @import "tailwindcss";
98
99
  @import "@esuiss/tailwind";
100
+ @import "@fontsource-variable/inter";
99
101
 
100
102
  :root {
101
- --background: oklch(1 0 0);
102
- --primary: oklch(0.6321 0.2018 254.09);
103
- --radius: 0.75rem;
103
+ --surface: oklch(1 0 0);
104
+ --label: oklch(0.2316 0.0038 286.09);
105
+ --accent: oklch(0.5629 0.1933 256.16);
106
+ --radius: 0.625rem;
104
107
  }
105
108
 
106
109
  .dark {
107
- --background: oklch(0 0 0);
108
- --primary: oklch(0.651471 0.191972 251.4696);
110
+ --surface: oklch(0 0 0);
111
+ --label: oklch(0.9707 0.0026 286.29);
112
+ --accent: oklch(0.5629 0.1933 256.16);
109
113
  }
110
114
 
111
115
  @theme inline {
112
- --font-sans: system-ui, sans-serif;
113
- --color-background: var(--background);
114
- --color-primary: var(--primary);
116
+ --font-sans: "Inter Variable", sans-serif;
117
+ --color-surface: var(--surface);
118
+ --color-label: var(--label);
119
+ --color-accent: var(--accent);
115
120
  }
116
121
  ```
117
122
 
123
+ Colors are named by role, not by component:
124
+
125
+ - **Surfaces:** `surface`, `surface-secondary`, `surface-tertiary`, `surface-raised` (menus, dialogs, cards).
126
+ - **Text:** `label`, `label-secondary`, `label-tertiary`, `label-quaternary`.
127
+ - **Control fills:** `fill`, `fill-secondary`, `fill-tertiary`.
128
+ - **Lines:** `separator`, `separator-strong`.
129
+ - **Accent:** `accent`, `accent-hover`, `accent-pressed`, `on-accent` (text on the accent).
130
+ - **Links, focus and states:** `link`, `focus`, `danger`, `success`, `warning` and their `-surface` tints.
131
+ - **Palette:** `blue`, `green`, `orange`, `red`, `yellow`, `purple`, `pink`, `indigo`, `teal`, `mint`, `cyan`, `brown`, `gray`, used by charts.
132
+
118
133
  - **Change a color:** edit its value under `:root` (light) and `.dark` (dark).
119
134
  - **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.
120
- - **Change the font:** set `--font-sans` in `@theme inline`. By default each device's own system font is used, so nothing has to be downloaded.
135
+ - **Change the font:** set `--font-sans` in `@theme inline`. Inter is loaded by default.
121
136
  - **Corner radius:** `--radius` scales every rounded component.
122
137
 
123
138
  ## Dark mode
package/lib/init.mjs CHANGED
@@ -39,6 +39,17 @@ const TOOLING = {
39
39
  vite: ["tailwindcss", "@tailwindcss/vite", "@types/node"],
40
40
  }
41
41
  const MINIMUM_TAILWIND = [4, 2]
42
+ const FONT_IMPORT = '@import "@fontsource-variable/inter";\n'
43
+ const FONT_FAMILY = '--font-sans: "Inter Variable", sans-serif;'
44
+ const NEXT_FONT_FAMILY = "--font-sans: var(--font-sans);"
45
+ const NEXT_LAYOUTS = [
46
+ "src/app/layout.tsx",
47
+ "app/layout.tsx",
48
+ "src/app/layout.jsx",
49
+ "app/layout.jsx",
50
+ ]
51
+ const NEXT_FONT_NOTE =
52
+ 'Load Inter in your root layout with next/font/google: Inter({ subsets: ["latin", "latin-ext"], variable: "--font-sans" }), and add its .variable to the <html> className.'
42
53
 
43
54
  function relative(project, file) {
44
55
  return path.relative(project.cwd, file)
@@ -184,6 +195,62 @@ function configureVite(project) {
184
195
  return manual
185
196
  }
186
197
 
198
+ function stylesheetFor(project, css) {
199
+ if (project.framework !== "next") return css
200
+ return css.replace(FONT_IMPORT, "").replace(FONT_FAMILY, NEXT_FONT_FAMILY)
201
+ }
202
+
203
+ function configureNextFont(project) {
204
+ const file = NEXT_LAYOUTS.map((name) => path.join(project.cwd, name)).find(
205
+ existsSync
206
+ )
207
+ if (!file) return [NEXT_FONT_NOTE]
208
+
209
+ let text = readFileSync(file, "utf8")
210
+ if (/variable:\s*["']--font-sans["']/.test(text)) return []
211
+
212
+ const html = text.match(/<html\b[^>]*>/)
213
+ if (!html) return [NEXT_FONT_NOTE]
214
+ let tag = html[0]
215
+ const literal = tag.match(/className="([^"]*)"/)
216
+ if (literal) {
217
+ tag = tag.replace(
218
+ literal[0],
219
+ `className={\`\${fontSans.variable} ${literal[1]}\`}`
220
+ )
221
+ } else if (!/className=/.test(tag)) {
222
+ tag = tag.replace(/<html\b/, "<html className={fontSans.variable}")
223
+ } else {
224
+ return [NEXT_FONT_NOTE]
225
+ }
226
+ text = text.replace(html[0], tag)
227
+
228
+ const semi = /from\s+["'][^"']+["'];/.test(text) ? ";" : ""
229
+ const fontImport = text.match(
230
+ /import\s*\{([^}]*)\}\s*from\s*["']next\/font\/google["']/
231
+ )
232
+ if (fontImport) {
233
+ text = text.replace(
234
+ fontImport[0],
235
+ fontImport[0].replace(fontImport[1], `${fontImport[1].trimEnd()}, Inter `)
236
+ )
237
+ } else {
238
+ text = `import { Inter } from "next/font/google"${semi}\n${text}`
239
+ }
240
+
241
+ const imports = [
242
+ ...text.matchAll(/^import\s[\s\S]*?["'][^"']+["'];?[ \t]*\n/gm),
243
+ ]
244
+ const last = imports.at(-1)
245
+ const at = last ? last.index + last[0].length : 0
246
+ const declaration = `\nconst fontSans = Inter({\n subsets: ["latin", "latin-ext"],\n variable: "--font-sans",\n})${semi}\n`
247
+ text = text.slice(0, at) + declaration + text.slice(at)
248
+
249
+ writeFileSync(file, text)
250
+ step(`Loaded Inter in ${relative(project, file)}`)
251
+ return []
252
+ }
253
+
187
254
  function configureNext(project) {
188
255
  const existing = POSTCSS_CONFIGS.map((name) =>
189
256
  path.join(project.cwd, name)
@@ -230,7 +297,7 @@ export async function init(options) {
230
297
  }
231
298
 
232
299
  const registry = await fetchRegistry()
233
- const css = await fetchStylesheet(registry)
300
+ const css = stylesheetFor(project, await fetchStylesheet(registry))
234
301
 
235
302
  if (ensureAlias(project, sourceRoot))
236
303
  step("Added the @/* import alias to tsconfig")
@@ -241,7 +308,7 @@ export async function init(options) {
241
308
  const manual =
242
309
  project.framework === "vite"
243
310
  ? configureVite(project)
244
- : configureNext(project)
311
+ : [...configureNext(project), ...configureNextFont(project)]
245
312
 
246
313
  writeFileSync(stylesheet, css)
247
314
  step(`Wrote ${relative(project, stylesheet)}`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@esuiss/ui",
3
- "version": "0.5.9",
3
+ "version": "0.6.0",
4
4
  "description": "Copy-paste React components with a clean, minimal design, built on Base UI and Tailwind CSS v4. CLI for Next.js and React (Vite).",
5
5
  "keywords": [
6
6
  "react",