hazo_ui 4.7.0 → 4.9.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/CHANGE_LOG.md CHANGED
@@ -5,6 +5,40 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## v4.9.0 (2026-07-06)
9
+
10
+ ### New
11
+ - **Theme Kit** — packaged, SSR-safe dark/light theme using the shadcn
12
+ `.dark`-class convention (toggles a `dark` class on `<html>`). New exports:
13
+ - `HazoThemeProvider` — context provider that keeps `<html class="dark">` in
14
+ sync with the resolved theme. Props `defaultTheme` (`"light" | "dark" |
15
+ "system"`, default `"system"`) and `storageKey` (default `"theme"`).
16
+ `window`/`document` are only touched inside `useEffect`, so no hydration
17
+ mismatch.
18
+ - `useTheme()` — `{ theme, setTheme, resolvedTheme, toggleTheme }`; throws
19
+ outside the provider.
20
+ - `ThemeToggle` — accessible Sun/Moon button; renders a stable icon until
21
+ mounted to avoid a first-render hydration mismatch.
22
+ - `ThemeScript` — inline no-flash script for the root `<head>`; sets the
23
+ `dark` class before first paint. Wrapped in try/catch so it can never
24
+ block rendering.
25
+ - Types: `Theme`, `ThemeProviderProps`, `ThemeContextValue`,
26
+ `ThemeScriptProps`, `ThemeToggleProps`.
27
+
28
+ ```tsx
29
+ // app/layout.tsx
30
+ import { HazoThemeProvider, ThemeScript } from 'hazo_ui';
31
+
32
+ <html lang="en" suppressHydrationWarning>
33
+ <head><ThemeScript storageKey="theme" /></head>
34
+ <body>
35
+ <HazoThemeProvider defaultTheme="system" storageKey="theme">
36
+ {children}
37
+ </HazoThemeProvider>
38
+ </body>
39
+ </html>
40
+ ```
41
+
8
42
  ## v4.7.0 (2026-07-01)
9
43
 
10
44
  ### New
package/README.md CHANGED
@@ -214,6 +214,8 @@ The following components support both global config and prop-level color overrid
214
214
 
215
215
  - **[Chart Primitives](#chart-primitives-v2170)** (v2.17.0) - Pure-SVG chart components for KPI cards and trend dashboards: `Sparkline`, `InverseSparkline`, `LineChart`, `MultiLineChart`, `StackedBars`, and `DateRangeSelector`. Zero third-party chart deps. Gap-aware paths, hover tooltips, and per-series endpoint markers built in.
216
216
 
217
+ - **[Theme Kit](#theme-kit-v490)** (v4.9.0) - SSR-safe dark/light theme using the shadcn `.dark`-class convention: `HazoThemeProvider`, `useTheme`, `ThemeToggle`, and a no-flash `ThemeScript`. Persists the chosen mode to `localStorage` and resolves `"system"` against the OS preference.
218
+
217
219
  ### State Primitives (v2.10.0)
218
220
 
219
221
  Lightweight, opinionated components for the four ubiquitous async states: **loading**, **empty**, **error**, and **success**.
@@ -242,6 +244,47 @@ Lightweight, opinionated components for the four ubiquitous async states: **load
242
244
  import { Slider, InputAffix, HazoUiProgressBar, HazoUiEtaProgress, useEtaProgress } from 'hazo_ui';
243
245
  ```
244
246
 
247
+ ### Theme Kit (v4.9.0)
248
+
249
+ SSR-safe dark/light theme using the shadcn `.dark`-class convention (toggles a `dark` class on `<html>`). No hydration mismatch: `window`/`document` are only touched inside `useEffect`, and an inline `ThemeScript` sets the class before first paint to avoid a flash.
250
+
251
+ **Exports:** `HazoThemeProvider`, `useTheme`, `ThemeToggle`, `ThemeScript` and types `Theme`, `ThemeProviderProps`, `ThemeContextValue`, `ThemeScriptProps`, `ThemeToggleProps`.
252
+
253
+ ```tsx
254
+ // app/layout.tsx
255
+ import { HazoThemeProvider, ThemeScript } from 'hazo_ui';
256
+
257
+ export default function RootLayout({ children }) {
258
+ return (
259
+ <html lang="en" suppressHydrationWarning>
260
+ <head>
261
+ <ThemeScript storageKey="theme" />
262
+ </head>
263
+ <body>
264
+ <HazoThemeProvider defaultTheme="system" storageKey="theme">
265
+ {children}
266
+ </HazoThemeProvider>
267
+ </body>
268
+ </html>
269
+ );
270
+ }
271
+ ```
272
+
273
+ ```tsx
274
+ // anywhere under the provider
275
+ import { ThemeToggle, useTheme } from 'hazo_ui';
276
+
277
+ function Header() {
278
+ const { theme, resolvedTheme, setTheme } = useTheme();
279
+ return <ThemeToggle />; // Sun/Moon button that flips light/dark
280
+ }
281
+ ```
282
+
283
+ - `HazoThemeProvider` — context provider; keeps `<html class="dark">` in sync with the resolved theme. Props: `defaultTheme` (`"light" | "dark" | "system"`, default `"system"`), `storageKey` (default `"theme"`).
284
+ - `useTheme()` — `{ theme, setTheme, resolvedTheme, toggleTheme }`. Throws if used outside the provider.
285
+ - `ThemeToggle` — accessible Sun/Moon button; guards against a first-render hydration mismatch by rendering a stable icon until mounted.
286
+ - `ThemeScript` — inline no-flash script; render in the root `<head>` before content, with a `storageKey` matching the provider.
287
+
245
288
  ### shadcn/ui Primitive Re-exports
246
289
 
247
290
  All shadcn/ui base components are re-exported from hazo_ui, so sibling hazo_* packages (and consumers) can import UI primitives from a single source without installing shadcn/ui separately:
package/dist/index.cjs CHANGED
@@ -11391,6 +11391,67 @@ function EntryRow({ entry, on_select, onDelete }) {
11391
11391
  }
11392
11392
  );
11393
11393
  }
11394
+ var ThemeContext = React26__namespace.createContext(void 0);
11395
+ function HazoThemeProvider({
11396
+ children,
11397
+ defaultTheme = "system",
11398
+ storageKey = "theme"
11399
+ }) {
11400
+ const [theme, setTheme] = useLocalStorage(storageKey, defaultTheme);
11401
+ const prefersDark = useMediaQuery("(prefers-color-scheme: dark)");
11402
+ const resolvedTheme = theme === "system" ? prefersDark ? "dark" : "light" : theme;
11403
+ React26__namespace.useEffect(() => {
11404
+ document.documentElement.classList.toggle("dark", resolvedTheme === "dark");
11405
+ }, [resolvedTheme]);
11406
+ const toggleTheme = React26__namespace.useCallback(() => {
11407
+ setTheme(resolvedTheme === "dark" ? "light" : "dark");
11408
+ }, [resolvedTheme, setTheme]);
11409
+ const value = React26__namespace.useMemo(
11410
+ () => ({ theme, setTheme, resolvedTheme, toggleTheme }),
11411
+ [theme, setTheme, resolvedTheme, toggleTheme]
11412
+ );
11413
+ return /* @__PURE__ */ jsxRuntime.jsx(ThemeContext.Provider, { value, children });
11414
+ }
11415
+ function useTheme() {
11416
+ const context = React26__namespace.useContext(ThemeContext);
11417
+ if (context === void 0) {
11418
+ throw new Error("useTheme must be used within a <HazoThemeProvider>");
11419
+ }
11420
+ return context;
11421
+ }
11422
+ function ThemeScript({ storageKey = "theme" }) {
11423
+ const script = `(function(){try{var k=${JSON.stringify(
11424
+ storageKey
11425
+ )};var raw=localStorage.getItem(k);var t="system";if(raw){try{t=JSON.parse(raw);}catch(e){t=String(raw).replace(/^"|"$/g,"");}}var m=window.matchMedia("(prefers-color-scheme: dark)").matches;var isDark=t==="dark"||((t==="system"||!t)&&m);var c=document.documentElement.classList;if(isDark){c.add("dark");}else{c.remove("dark");}}catch(e){}})();`;
11426
+ return /* @__PURE__ */ jsxRuntime.jsx("script", { dangerouslySetInnerHTML: { __html: script } });
11427
+ }
11428
+ var ThemeToggle = React26__namespace.forwardRef(
11429
+ ({ className, ...props }, ref) => {
11430
+ const { resolvedTheme, toggleTheme } = useTheme();
11431
+ const [mounted, setMounted] = React26__namespace.useState(false);
11432
+ React26__namespace.useEffect(() => {
11433
+ setMounted(true);
11434
+ }, []);
11435
+ const isDark = mounted && resolvedTheme === "dark";
11436
+ const label = isDark ? "Switch to light theme" : "Switch to dark theme";
11437
+ return /* @__PURE__ */ jsxRuntime.jsx(
11438
+ "button",
11439
+ {
11440
+ type: "button",
11441
+ ref,
11442
+ onClick: toggleTheme,
11443
+ "aria-label": label,
11444
+ className: cn(
11445
+ "inline-flex h-9 w-9 items-center justify-center rounded-full text-foreground transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50",
11446
+ className
11447
+ ),
11448
+ ...props,
11449
+ children: isDark ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sun, { className: "h-4 w-4" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Moon, { className: "h-4 w-4" })
11450
+ }
11451
+ );
11452
+ }
11453
+ );
11454
+ ThemeToggle.displayName = "ThemeToggle";
11394
11455
 
11395
11456
  Object.defineProperty(exports, "rawToast", {
11396
11457
  enumerable: true,
@@ -11464,6 +11525,7 @@ exports.ErrorBanner = ErrorBanner;
11464
11525
  exports.ErrorPage = ErrorPage;
11465
11526
  exports.FunnelChart = FunnelChart;
11466
11527
  exports.HazoContextProvider = HazoContextProvider;
11528
+ exports.HazoThemeProvider = HazoThemeProvider;
11467
11529
  exports.HazoUiConfirmDialog = HazoUiConfirmDialog;
11468
11530
  exports.HazoUiDialog = HazoUiDialog;
11469
11531
  exports.HazoUiDialogClose = DialogClose;
@@ -11553,6 +11615,8 @@ exports.TabsContent = TabsContent;
11553
11615
  exports.TabsList = TabsList;
11554
11616
  exports.TabsTrigger = TabsTrigger;
11555
11617
  exports.Textarea = Textarea;
11618
+ exports.ThemeScript = ThemeScript;
11619
+ exports.ThemeToggle = ThemeToggle;
11556
11620
  exports.Toggle = Toggle;
11557
11621
  exports.ToggleGroup = ToggleGroup;
11558
11622
  exports.ToggleGroupItem = ToggleGroupItem;
@@ -11594,6 +11658,7 @@ exports.useLoadingState = useLoadingState;
11594
11658
  exports.useLocalStorage = useLocalStorage;
11595
11659
  exports.useMediaQuery = useMediaQuery;
11596
11660
  exports.useSessionStorage = useSessionStorage;
11661
+ exports.useTheme = useTheme;
11597
11662
  exports.useViewport = useViewport;
11598
11663
  exports.useWakeLock = useWakeLock;
11599
11664
  exports.use_fullscreen = use_fullscreen;