bearnie 0.2.0 → 0.3.1

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 CHANGED
@@ -63,7 +63,7 @@ This will:
63
63
  - Install `clsx`, `tailwind-merge`, and `tailwindcss` dependencies
64
64
  - Add the `@/*` path alias to `tsconfig.json`
65
65
  - Wire `@tailwindcss/vite` into your Astro config (simple configs only — you get a hint otherwise)
66
- - Install the theme variables to `src/styles/bearnie.css`
66
+ - Ask for a base color and an accent color (see Themes below) and install the result to `src/styles/bearnie.css`
67
67
 
68
68
  Projects created with `create-bearnie` already include `bearnie.json` and can skip init.
69
69
 
@@ -162,6 +162,23 @@ Any new npm dependencies the updated components need are installed automatically
162
162
  - `-y, --yes` - Skip confirmation prompt
163
163
  - `--cwd <path>` - Set the working directory
164
164
 
165
+ ## Themes
166
+
167
+ Themes combine a **base color** (the grays used for backgrounds, text, and borders) with an **accent color** (buttons, focus rings, active states), both from Tailwind's official palette:
168
+
169
+ - **Bases:** `neutral` (default), `slate`, `gray`, `zinc`, `stone`, `mauve`, `olive`, `mist`, `taupe`
170
+ - **Accents:** neutral default, `red`, `rose`, `orange`, `amber`, `yellow`, `lime`, `green`, `emerald`, `teal`, `cyan`, `sky`, `blue`, `indigo`, `violet`, `purple`, `fuchsia`, `pink`
171
+
172
+ Every combination is a registry entry: `styles-blue` (neutral base, blue accent), `styles-slate` (slate base, neutral accent), `styles-slate-blue`, and so on. They all install the same `bearnie.css` file, so every component works with every theme.
173
+
174
+ `init` and `create-bearnie` ask for base and accent. Switch later with:
175
+
176
+ ```bash
177
+ npx bearnie add styles-slate-blue --overwrite
178
+ ```
179
+
180
+ Switching records the theme in `bearnie.json`, so `diff` and `update` compare your CSS against the right palette.
181
+
165
182
  ## Package Managers
166
183
 
167
184
  The CLI detects your package manager from the lockfile (`package-lock.json`, `pnpm-lock.yaml`, `yarn.lock`, `bun.lock`) and uses it for all dependency installs. No configuration needed.
package/dist/index.js CHANGED
@@ -23,8 +23,20 @@ var DEFAULT_CONFIG = {
23
23
  utilsDir: "src/utils",
24
24
  stylesDir: "src/styles",
25
25
  tailwindConfig: "tailwind.config.mjs",
26
- typescript: true
26
+ typescript: true,
27
+ theme: "default"
27
28
  };
29
+ function themeEntryName(theme) {
30
+ return theme === "default" ? "styles" : `styles-${theme}`;
31
+ }
32
+ function composeThemeName(base, accent) {
33
+ if (base === "neutral") return accent;
34
+ if (accent === "default") return base;
35
+ return `${base}-${accent}`;
36
+ }
37
+ function isThemeEntry(name) {
38
+ return name === "styles" || name.startsWith("styles-");
39
+ }
28
40
  var CONFIG_FILE = "bearnie.json";
29
41
  async function getProjectConfig(cwd) {
30
42
  const configPath = path.join(cwd, CONFIG_FILE);
@@ -377,6 +389,14 @@ async function init(options) {
377
389
  print.newline();
378
390
  console.log(` ${chalk2.bold("Where should things go?")}`);
379
391
  print.newline();
392
+ let themeBases = ["neutral"];
393
+ let themeAccents = ["default"];
394
+ try {
395
+ const index = await getRegistryIndex();
396
+ if (index.themeBases?.length) themeBases = index.themeBases;
397
+ if (index.themeAccents?.length) themeAccents = index.themeAccents;
398
+ } catch {
399
+ }
380
400
  const responses = await prompts([
381
401
  {
382
402
  type: "text",
@@ -389,11 +409,37 @@ async function init(options) {
389
409
  name: "utilsDir",
390
410
  message: "Utilities directory",
391
411
  initial: DEFAULT_CONFIG.utilsDir
392
- }
412
+ },
413
+ ...themeBases.length > 1 ? [
414
+ {
415
+ type: "select",
416
+ name: "themeBase",
417
+ message: "Base color (grays and surfaces)",
418
+ choices: themeBases.map((name) => ({
419
+ title: name,
420
+ value: name
421
+ })),
422
+ initial: 0
423
+ }
424
+ ] : [],
425
+ ...themeAccents.length > 1 ? [
426
+ {
427
+ type: "select",
428
+ name: "themeAccent",
429
+ message: "Accent color (buttons, focus rings)",
430
+ choices: themeAccents.map((name) => ({
431
+ title: name === "default" ? "default (neutral)" : name,
432
+ value: name
433
+ })),
434
+ initial: 0
435
+ }
436
+ ] : []
393
437
  ]);
438
+ const { themeBase, themeAccent, ...dirs } = responses;
394
439
  config = {
395
440
  ...config,
396
- ...responses
441
+ ...dirs,
442
+ theme: composeThemeName(themeBase ?? "neutral", themeAccent ?? "default")
397
443
  };
398
444
  }
399
445
  const spinner = ora({
@@ -469,7 +515,7 @@ export function cn(...inputs: ClassValue[]) {
469
515
  );
470
516
  }
471
517
  try {
472
- const stylesEntry = await getComponent("styles");
518
+ const stylesEntry = await getComponent(themeEntryName(config.theme));
473
519
  let stylesWritten = false;
474
520
  for (const file of stylesEntry.files) {
475
521
  const stylesPath = path4.join(cwd, config.stylesDir, file.name);
@@ -480,12 +526,17 @@ export function cn(...inputs: ClassValue[]) {
480
526
  }
481
527
  }
482
528
  if (stylesWritten) {
529
+ const themeLabel = config.theme === "default" ? "" : ` (${config.theme} theme)`;
483
530
  print.step(
484
- `${brand.success("\u2713")} Added theme variables to ${chalk2.cyan(`${config.stylesDir}/bearnie.css`)}`
531
+ `${brand.success("\u2713")} Added theme variables to ${chalk2.cyan(`${config.stylesDir}/bearnie.css`)}${themeLabel}`
485
532
  );
486
533
  manualSteps.push(
487
534
  `Import the styles in your global CSS: ${chalk2.cyan(`@import "./bearnie.css";`)} (after ${chalk2.cyan(`@import "tailwindcss";`)})`
488
535
  );
536
+ } else if (config.theme !== "default") {
537
+ manualSteps.push(
538
+ `bearnie.css already exists \u2014 switch to the ${config.theme} theme with ${chalk2.cyan(`npx bearnie add ${themeEntryName(config.theme)} --overwrite`)}`
539
+ );
489
540
  }
490
541
  } catch {
491
542
  manualSteps.push(`Add theme variables: ${chalk2.cyan("npx bearnie add styles")}`);
@@ -513,6 +564,7 @@ async function add(components, options) {
513
564
  console.log(` ${messages.addStart()}`);
514
565
  print.newline();
515
566
  let config = await getProjectConfig(cwd);
567
+ const hadConfig = config !== null;
516
568
  if (!config) {
517
569
  print.warning(
518
570
  `Project not initialized. Run ${chalk3.cyan("npx bearnie init")} first.`
@@ -566,8 +618,11 @@ async function add(components, options) {
566
618
  }
567
619
  selectedComponents = selected;
568
620
  } else {
569
- const availableNames = registryIndex.components.map((c) => c.name);
570
- const invalid = components.filter((c) => !availableNames.includes(c));
621
+ const availableNames = new Set(registryIndex.components.map((c) => c.name));
622
+ for (const theme of registryIndex.themes ?? []) {
623
+ availableNames.add(theme === "default" ? "styles" : `styles-${theme}`);
624
+ }
625
+ const invalid = components.filter((c) => !availableNames.has(c));
571
626
  if (invalid.length > 0) {
572
627
  print.error(messages.unknownComponent(invalid));
573
628
  print.hint(`Run ${chalk3.cyan("npx bearnie list")} to see what's available.`);
@@ -672,6 +727,19 @@ async function add(components, options) {
672
727
  print.hint(`${error}`);
673
728
  }
674
729
  }
730
+ if (hadConfig) {
731
+ const themeInstalled = selectedComponents.find(
732
+ (name) => isThemeEntry(name)
733
+ );
734
+ if (themeInstalled) {
735
+ const theme = themeInstalled === "styles" ? "default" : themeInstalled.replace(/^styles-/, "");
736
+ if (config.theme !== theme) {
737
+ config = { ...config, theme };
738
+ await saveProjectConfig(cwd, config);
739
+ print.step(`${brand.success("\u2713")} Theme set to ${chalk3.cyan(theme)} in bearnie.json`);
740
+ }
741
+ }
742
+ }
675
743
  const allDeps = [...npmDeps];
676
744
  const allDevDeps = [...npmDevDeps];
677
745
  if (allDeps.length > 0 || allDevDeps.length > 0) {
@@ -723,6 +791,8 @@ async function add(components, options) {
723
791
  }
724
792
  print.newline();
725
793
  print.success();
794
+ print.newline();
795
+ print.footer();
726
796
  }
727
797
 
728
798
  // src/commands/list.ts
@@ -813,11 +883,14 @@ import { structuredPatch } from "diff";
813
883
  import fs6 from "fs-extra";
814
884
  async function getAllRegistryNames() {
815
885
  const index = await getRegistryIndex();
816
- const names = index.components.map((c) => c.name);
886
+ const names = new Set(index.components.map((c) => c.name));
817
887
  for (const utility of index.utilities ?? []) {
818
- names.push(utility.name);
888
+ names.add(utility.name);
889
+ }
890
+ for (const theme of index.themes ?? []) {
891
+ names.add(themeEntryName(theme));
819
892
  }
820
- return names;
893
+ return [...names];
821
894
  }
822
895
  async function getEntryState(cwd, config, name) {
823
896
  const entry = await getComponent(name);
@@ -856,7 +929,8 @@ async function getInstalledEntries(cwd, config, names) {
856
929
  throw new Error(`Unknown components: ${unknown.join(", ")}`);
857
930
  }
858
931
  }
859
- const targets = names?.length ? names : allNames;
932
+ const activeTheme = themeEntryName(config.theme ?? "default");
933
+ const targets = names?.length ? names : allNames.filter((name) => !isThemeEntry(name) || name === activeTheme);
860
934
  const installed = [];
861
935
  for (const name of targets) {
862
936
  const state = await getEntryState(cwd, config, name);
@@ -4,8 +4,20 @@ export interface ProjectConfig {
4
4
  stylesDir: string;
5
5
  tailwindConfig: string;
6
6
  typescript: boolean;
7
+ /** Which color theme is installed ("default", "amber", ...). */
8
+ theme: string;
7
9
  }
8
10
  export declare const DEFAULT_CONFIG: ProjectConfig;
11
+ /** Registry entry name for a theme: "default" -> styles, "amber" -> styles-amber. */
12
+ export declare function themeEntryName(theme: string): string;
13
+ /**
14
+ * Theme name from a base (gray scale) + accent (primary color) pair:
15
+ * neutral+default -> "default", neutral+blue -> "blue",
16
+ * slate+default -> "slate", slate+blue -> "slate-blue".
17
+ */
18
+ export declare function composeThemeName(base: string, accent: string): string;
19
+ /** True for the styles/styles-* entries, which all install the same CSS file. */
20
+ export declare function isThemeEntry(name: string): boolean;
9
21
  export declare const CONFIG_FILE = "bearnie.json";
10
22
  export declare function getProjectConfig(cwd: string): Promise<ProjectConfig | null>;
11
23
  export declare function saveProjectConfig(cwd: string, config: ProjectConfig): Promise<void>;
@@ -17,7 +17,7 @@ export interface InstalledEntry {
17
17
  files: FileState[];
18
18
  hasChanges: boolean;
19
19
  }
20
- /** Every name in the registry: components, utilities, styles, barrel. */
20
+ /** Every name in the registry: components, utilities, styles, themes, barrel. */
21
21
  export declare function getAllRegistryNames(): Promise<string[]>;
22
22
  /**
23
23
  * Compares one registry entry against the project. Returns null when the
@@ -28,6 +28,9 @@ export interface RegistryIndex {
28
28
  name: string;
29
29
  description: string;
30
30
  }[];
31
+ themes?: string[];
32
+ themeBases?: string[];
33
+ themeAccents?: string[];
31
34
  }
32
35
  export declare function getRegistryIndex(): Promise<RegistryIndex>;
33
36
  export declare function getComponent(name: string): Promise<RegistryComponent>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bearnie",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "CLI for installing Bearnie UI components",
5
5
  "type": "module",
6
6
  "license": "MIT",