@busiverse/ui 0.2.11 → 0.2.12

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
@@ -277,3 +277,14 @@ The frontend should not duplicate BUSIVERSE brand assets in `public/`; brand ass
277
277
  ## Theme contract
278
278
 
279
279
  `@busiverse/ui@0.2.10` owns the shared BUSIVERSE light/dark CSS contract. Apps should place `light` or `dark` on `document.documentElement`; `system` mode should resolve to one of those classes. Do not hard-code `className="dark ..."` on page wrappers, because it prevents light mode from working.
280
+
281
+ ## BUSIVERSE theme contract
282
+
283
+ This frontend uses the shared BUSIVERSE theme contract from `@busiverse/ui`.
284
+
285
+ - Theme choices are `light`, `dark`, and `system`.
286
+ - The shared storage key is `busiverse-theme`.
287
+ - The DOM theme class is owned by `next-themes` through `attribute="class"`.
288
+ - App pages must not hard-code `className="dark ..."` wrappers.
289
+ - `vite.config.ts` must keep `loadEnv`; do not remove it while adding build or chunking improvements.
290
+ - Existing `VITE_*` environment variable names must not be renamed without approval.
@@ -0,0 +1,63 @@
1
+ import {
2
+ cn
3
+ } from "./chunk-PYZVP4NI.js";
4
+
5
+ // src/theme/ThemeModeSelect.tsx
6
+ import { useTheme } from "next-themes";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+ var MODES = [
9
+ {
10
+ value: "light",
11
+ label: "Light",
12
+ title: "Use light mode",
13
+ icon: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", className: "h-4 w-4", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
14
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "4" }),
15
+ /* @__PURE__ */ jsx("path", { d: "M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41" })
16
+ ] })
17
+ },
18
+ {
19
+ value: "dark",
20
+ label: "Dark",
21
+ title: "Use dark mode",
22
+ icon: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", className: "h-4 w-4", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M20.99 12.43A8.5 8.5 0 1 1 11.57 3a6.5 6.5 0 0 0 9.42 9.43Z" }) })
23
+ },
24
+ {
25
+ value: "system",
26
+ label: "System",
27
+ title: "Follow system theme",
28
+ icon: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", className: "h-4 w-4", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
29
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "4", width: "18", height: "12", rx: "2" }),
30
+ /* @__PURE__ */ jsx("path", { d: "M8 20h8M12 16v4" })
31
+ ] })
32
+ }
33
+ ];
34
+ function ThemeModeSelect({ className, buttonClassName, label = "Theme", compact = false }) {
35
+ const { theme = "system", setTheme, resolvedTheme } = useTheme();
36
+ const currentMode = theme === "light" || theme === "dark" || theme === "system" ? theme : "system";
37
+ return /* @__PURE__ */ jsxs("div", { className: cn("busiverse-theme-mode-select", className), role: "group", "aria-label": label, "data-theme-mode": currentMode, "data-resolved-theme": resolvedTheme ?? currentMode, children: [
38
+ !compact && /* @__PURE__ */ jsx("span", { className: "busiverse-theme-mode-label", children: label }),
39
+ /* @__PURE__ */ jsx("div", { className: "busiverse-theme-mode-options", children: MODES.map((mode) => {
40
+ const active = currentMode === mode.value;
41
+ return /* @__PURE__ */ jsxs(
42
+ "button",
43
+ {
44
+ type: "button",
45
+ title: mode.title,
46
+ "aria-pressed": active,
47
+ className: cn("busiverse-theme-mode-button", { "is-active": active }, buttonClassName),
48
+ onClick: () => setTheme(mode.value),
49
+ children: [
50
+ mode.icon,
51
+ !compact && /* @__PURE__ */ jsx("span", { children: mode.label }),
52
+ compact && /* @__PURE__ */ jsx("span", { className: "busiverse-sr-only", children: mode.label })
53
+ ]
54
+ },
55
+ mode.value
56
+ );
57
+ }) })
58
+ ] });
59
+ }
60
+
61
+ export {
62
+ ThemeModeSelect
63
+ };
package/dist/index.d.ts CHANGED
@@ -7,4 +7,5 @@ export * from "./api";
7
7
  export * from "./auth";
8
8
  export * from "./i18n";
9
9
  export * from "./utils/cn";
10
+ export * from "./theme";
10
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,WAAW,CAAC;AAC1B,cAAc,OAAO,CAAC;AACtB,cAAc,QAAQ,CAAC;AACvB,cAAc,QAAQ,CAAC;AACvB,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,WAAW,CAAC;AAC1B,cAAc,OAAO,CAAC;AACtB,cAAc,QAAQ,CAAC;AACvB,cAAc,QAAQ,CAAC;AACvB,cAAc,YAAY,CAAC;AAE3B,cAAc,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -1,3 +1,6 @@
1
+ import {
2
+ ThemeModeSelect
3
+ } from "./chunk-BJYKR462.js";
1
4
  import {
2
5
  Badge,
3
6
  Card,
@@ -383,6 +386,7 @@ export {
383
386
  StatCard,
384
387
  StatusPill,
385
388
  Textarea,
389
+ ThemeModeSelect,
386
390
  TimeSeriesTable,
387
391
  UsageQuotaBar,
388
392
  UserMenu,
package/dist/styles.css CHANGED
@@ -487,3 +487,60 @@ img, svg { max-width: 100%; display: block; }
487
487
  .light .bg-slate-950,
488
488
  .light .bg-slate-900,
489
489
  .light .bg-\[hsl\(var\(--base-bg\)\)\] { background-color: hsl(var(--background)) !important; }
490
+
491
+ .busiverse-theme-mode-select {
492
+ display: inline-flex;
493
+ align-items: center;
494
+ gap: .5rem;
495
+ color: hsl(var(--foreground));
496
+ }
497
+ .busiverse-theme-mode-label {
498
+ font-size: .75rem;
499
+ font-weight: 600;
500
+ letter-spacing: .04em;
501
+ text-transform: uppercase;
502
+ color: hsl(var(--muted-foreground));
503
+ }
504
+ .busiverse-theme-mode-options {
505
+ display: inline-flex;
506
+ align-items: center;
507
+ gap: .25rem;
508
+ border-radius: 9999px;
509
+ border: 1px solid hsl(var(--border));
510
+ background: hsl(var(--card) / .72);
511
+ padding: .25rem;
512
+ backdrop-filter: blur(16px);
513
+ }
514
+ .busiverse-theme-mode-button {
515
+ display: inline-flex;
516
+ align-items: center;
517
+ justify-content: center;
518
+ gap: .375rem;
519
+ min-height: 2rem;
520
+ border-radius: 9999px;
521
+ border: 0;
522
+ background: transparent;
523
+ color: hsl(var(--muted-foreground));
524
+ padding: .375rem .625rem;
525
+ font: inherit;
526
+ font-size: .8125rem;
527
+ font-weight: 600;
528
+ cursor: pointer;
529
+ transition: color var(--motion-default) var(--ease-standard), background var(--motion-default) var(--ease-standard), box-shadow var(--motion-default) var(--ease-standard), transform var(--motion-fast) var(--ease-standard);
530
+ }
531
+ .busiverse-theme-mode-button:hover {
532
+ color: hsl(var(--foreground));
533
+ background: hsl(var(--muted) / .70);
534
+ }
535
+ .busiverse-theme-mode-button:active {
536
+ transform: scale(.98);
537
+ }
538
+ .busiverse-theme-mode-button:focus-visible {
539
+ outline: 2px solid var(--color-brand-blue);
540
+ outline-offset: 2px;
541
+ }
542
+ .busiverse-theme-mode-button.is-active {
543
+ color: hsl(var(--primary-foreground));
544
+ background: linear-gradient(90deg, #2563EB 0%, #7C3AED 100%);
545
+ box-shadow: 0 12px 32px rgb(37 99 235 / .20);
546
+ }
@@ -0,0 +1,10 @@
1
+ import * as React from "react";
2
+ export type BusiverseThemeMode = "light" | "dark" | "system";
3
+ export interface ThemeModeSelectProps {
4
+ className?: string;
5
+ buttonClassName?: string;
6
+ label?: string;
7
+ compact?: boolean;
8
+ }
9
+ export declare function ThemeModeSelect({ className, buttonClassName, label, compact }: ThemeModeSelectProps): React.JSX.Element;
10
+ //# sourceMappingURL=ThemeModeSelect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ThemeModeSelect.d.ts","sourceRoot":"","sources":["../../src/theme/ThemeModeSelect.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;AAE7D,MAAM,WAAW,oBAAoB;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAqCD,wBAAgB,eAAe,CAAC,EAAE,SAAS,EAAE,eAAe,EAAE,KAAe,EAAE,OAAe,EAAE,EAAE,oBAAoB,qBA4BrH"}
@@ -0,0 +1,2 @@
1
+ export * from "./ThemeModeSelect";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/theme/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export * from "./theme";
2
+ //# sourceMappingURL=theme-public.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme-public.d.ts","sourceRoot":"","sources":["../src/theme-public.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC"}
@@ -0,0 +1,7 @@
1
+ import {
2
+ ThemeModeSelect
3
+ } from "./chunk-BJYKR462.js";
4
+ import "./chunk-PYZVP4NI.js";
5
+ export {
6
+ ThemeModeSelect
7
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@busiverse/ui",
3
- "version": "0.2.11",
3
+ "version": "0.2.12",
4
4
  "author": "Joel Julius Okoromi <okmarq@gmail.com> (https://busiversehq.com)",
5
5
  "description": "BUSIVERSE shared React UI, design tokens, pricing, i18n, auth helpers, and product shell. Network-neutral core; Gateway transport is app-injected.",
6
6
  "type": "module",
@@ -56,11 +56,16 @@
56
56
  "types": "./dist/assets-public.d.ts",
57
57
  "import": "./dist/assets-public.js",
58
58
  "default": "./dist/assets-public.js"
59
+ },
60
+ "./theme": {
61
+ "types": "./dist/theme-public.d.ts",
62
+ "import": "./dist/theme-public.js",
63
+ "default": "./dist/theme-public.js"
59
64
  }
60
65
  },
61
66
  "scripts": {
62
67
  "clean": "node scripts/clean.mjs",
63
- "build": "npm run clean && tsup src/index.ts src/tailwind-public.ts src/brand.ts src/i18n-public.ts src/billing.ts src/social.ts src/marketing.ts src/assets-public.ts --format esm --target es2022 && tsc -p tsconfig.build.json && node scripts/copy-static.mjs",
68
+ "build": "npm run clean && tsup src/index.ts src/tailwind-public.ts src/brand.ts src/i18n-public.ts src/billing.ts src/social.ts src/marketing.ts src/assets-public.ts src/theme-public.ts --format esm --target es2022 && tsc -p tsconfig.build.json && node scripts/copy-static.mjs",
64
69
  "typecheck": "tsc --noEmit",
65
70
  "pack:local": "npm pack",
66
71
  "prepublishOnly": "npm run typecheck && npm run build && npm pack --dry-run"
@@ -68,7 +73,8 @@
68
73
  "peerDependencies": {
69
74
  "react": "^19.2.7",
70
75
  "react-dom": "^19.2.7",
71
- "tailwindcss": "^4.3.1"
76
+ "tailwindcss": "^4.3.1",
77
+ "next-themes": "^0.4.6"
72
78
  },
73
79
  "devDependencies": {
74
80
  "@types/react": "19.2.17",
@@ -77,7 +83,8 @@
77
83
  "react-dom": "19.2.7",
78
84
  "tailwindcss": "4.3.1",
79
85
  "tsup": "8.5.1",
80
- "typescript": "6.0.3"
86
+ "typescript": "6.0.3",
87
+ "next-themes": "0.4.6"
81
88
  },
82
89
  "publishConfig": {
83
90
  "registry": "https://registry.npmjs.org/",