@bloomkit/react 0.1.6 → 0.2.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
@@ -259,17 +259,108 @@ const style = useBreathing({ duration: 6 });
259
259
 
260
260
  ## Dark Mode
261
261
 
262
- Add the `dark` class or `data-theme="dark"` attribute to `<html>`:
262
+ Wrap your app with `ThemeProvider` and use the `useTheme` hook:
263
263
 
264
- ```html
265
- <html class="dark">
264
+ ```tsx
265
+ import { ThemeProvider, useTheme } from "@bloomkit/react";
266
+
267
+ // Wrap your app — defaults to OS preference
268
+ <ThemeProvider defaultColorMode="system">
269
+ <App />
270
+ </ThemeProvider>
271
+
272
+ // Inside any component
273
+ function ThemeToggle() {
274
+ const { resolvedMode, toggleColorMode, setColorMode } = useTheme();
275
+
276
+ return (
277
+ <button onClick={toggleColorMode}>
278
+ {resolvedMode === "dark" ? "Light mode" : "Dark mode"}
279
+ </button>
280
+ );
281
+ }
282
+
283
+ // Or set explicitly
284
+ setColorMode("dark"); // force dark
285
+ setColorMode("light"); // force light
286
+ setColorMode("system"); // follow OS
287
+ ```
288
+
289
+ The provider manages the `.dark` / `.light` class on `<html>` automatically and persists the user's choice to `localStorage`.
290
+
291
+ **Without the provider:** add the `dark` class or `data-theme="dark"` attribute to `<html>` manually, or let it follow the OS preference automatically (no setup needed).
292
+
293
+ ## Palettes
294
+
295
+ Define multiple palettes and let users switch between them at runtime. Each palette can have its own light and dark mode colors, fonts, radius, and shadows.
296
+
297
+ ```tsx
298
+ import { ThemeProvider, useTheme, type BloomPalette } from "@bloomkit/react";
299
+
300
+ const myPalettes: BloomPalette[] = [
301
+ {
302
+ name: "ocean",
303
+ light: {
304
+ "--bloom-font": "'Nunito', sans-serif",
305
+ "--bloom-font-display": "'Space Grotesk', sans-serif",
306
+ "--bloom-bg": "#F4F8FA",
307
+ "--bloom-surface": "#E8F0F4",
308
+ "--bloom-surface2": "#D4E2EA",
309
+ "--bloom-text": "#1A2E3A",
310
+ "--bloom-text-secondary": "#5E7A8C",
311
+ "--bloom-accent1": "#6AB8C4",
312
+ "--bloom-accent1-deep": "#3A96A8",
313
+ "--bloom-accent2": "#E0A860",
314
+ "--bloom-accent2-deep": "#C08840",
315
+ "--bloom-accent3": "#7CA0D4",
316
+ "--bloom-accent3-deep": "#5A80B8",
317
+ "--bloom-accent4": "#D47A7A",
318
+ "--bloom-accent4-deep": "#B85A5A",
319
+ },
320
+ dark: {
321
+ "--bloom-font": "'Nunito', sans-serif",
322
+ "--bloom-font-display": "'Space Grotesk', sans-serif",
323
+ "--bloom-bg": "#0E1A20",
324
+ "--bloom-surface": "#162228",
325
+ "--bloom-surface2": "#1E2E36",
326
+ "--bloom-text": "#D8E8EE",
327
+ "--bloom-text-secondary": "#7A9AAC",
328
+ "--bloom-accent1": "#4A9AAC",
329
+ "--bloom-accent1-deep": "#2A7A8C",
330
+ "--bloom-accent2": "#C89048",
331
+ "--bloom-accent2-deep": "#A87030",
332
+ "--bloom-accent3": "#5A80B8",
333
+ "--bloom-accent3-deep": "#3A60A0",
334
+ "--bloom-accent4": "#B85A5A",
335
+ "--bloom-accent4-deep": "#983A3A",
336
+ },
337
+ },
338
+ ];
339
+
340
+ // Pass palettes to the provider
341
+ <ThemeProvider palettes={myPalettes}>
342
+ <App />
343
+ </ThemeProvider>
344
+
345
+ // Switch palettes from anywhere
346
+ function PaletteSwitcher() {
347
+ const { palette, setPalette, palettes } = useTheme();
348
+
349
+ return (
350
+ <select value={palette} onChange={(e) => setPalette(e.target.value)}>
351
+ {palettes.map((name) => (
352
+ <option key={name} value={name}>{name}</option>
353
+ ))}
354
+ </select>
355
+ );
356
+ }
266
357
  ```
267
358
 
268
- Or let it follow the OS preference automaticallyno extra setup needed.
359
+ The `"bloom"` palette is always available as the default. Custom palettes override the default tokens any token you don't specify falls back to Bloom's defaults. Both color mode and palette choice are persisted to `localStorage`.
269
360
 
270
- ## Theming
361
+ ## Theming with CSS
271
362
 
272
- All styles use CSS custom properties. Create a custom theme by overriding them in your CSS — every component updates instantly.
363
+ You can also theme with pure CSS instead of the provider override tokens directly in your stylesheet:
273
364
 
274
365
  ### Example: "Ocean Mist" theme
275
366
 
package/dist/index.cjs CHANGED
@@ -577,13 +577,13 @@ var progressTrackVariants = _classvarianceauthority.cva.call(void 0, [
577
577
  "relative w-full overflow-hidden",
578
578
  "rounded-[var(--bloom-radius-pill)]",
579
579
  "bg-[var(--bloom-surface2)]",
580
- "h-[6px]"
580
+ "h-[8px]"
581
581
  ]);
582
582
  var progressFillVariants = _classvarianceauthority.cva.call(void 0, [
583
583
  "h-full rounded-[var(--bloom-radius-pill)]",
584
- "transition-all duration-[var(--bloom-duration)] ease-[var(--bloom-ease)]",
585
- "bg-gradient-to-r from-[var(--bloom-accent1-deep)] to-[var(--bloom-accent1)]",
586
- "bg-[length:200%_100%] animate-[bloom-shimmer_3s_ease-in-out_infinite]"
584
+ "transition-[width] duration-[var(--bloom-duration-slow)] ease-[var(--bloom-ease)]",
585
+ "bg-[var(--bloom-accent1-deep)]",
586
+ "relative"
587
587
  ]);
588
588
 
589
589
  // src/components/progress/progress.tsx
@@ -605,7 +605,19 @@ var Progress = _react.forwardRef.call(void 0,
605
605
  "div",
606
606
  {
607
607
  className: progressFillVariants(),
608
- style: { width: `${clampedValue}%` }
608
+ style: { width: `${clampedValue}%` },
609
+ children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
610
+ "div",
611
+ {
612
+ className: "absolute inset-0 rounded-[var(--bloom-radius-pill)] overflow-hidden",
613
+ children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
614
+ "div",
615
+ {
616
+ className: "absolute inset-y-0 w-[60%] bg-gradient-to-r from-transparent via-white/25 to-transparent animate-[bloom-sweep_2.5s_ease-in-out_infinite]"
617
+ }
618
+ )
619
+ }
620
+ )
609
621
  }
610
622
  )
611
623
  }
@@ -630,6 +642,49 @@ var ProgressCircular = _react.forwardRef.call(void 0,
630
642
  className: cn("inline-flex", className),
631
643
  ...props,
632
644
  children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "svg", { width: size, height: size, viewBox: `0 0 ${size} ${size}`, children: [
645
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "defs", { children: [
646
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
647
+ "linearGradient",
648
+ {
649
+ id: `bloom-sweep-grad-${size}`,
650
+ gradientUnits: "userSpaceOnUse",
651
+ x1: size / 2,
652
+ y1: "0",
653
+ x2: size / 2,
654
+ y2: size,
655
+ children: [
656
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "stop", { offset: "0%", stopColor: "white", stopOpacity: "0" }),
657
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "stop", { offset: "50%", stopColor: "white", stopOpacity: "0.3" }),
658
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "stop", { offset: "100%", stopColor: "white", stopOpacity: "0" }),
659
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
660
+ "animateTransform",
661
+ {
662
+ attributeName: "gradientTransform",
663
+ type: "rotate",
664
+ values: `0 ${size / 2} ${size / 2};360 ${size / 2} ${size / 2}`,
665
+ dur: "2.5s",
666
+ repeatCount: "indefinite"
667
+ }
668
+ )
669
+ ]
670
+ }
671
+ ),
672
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "mask", { id: `bloom-sweep-mask-${size}`, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
673
+ "circle",
674
+ {
675
+ cx: size / 2,
676
+ cy: size / 2,
677
+ r: radius,
678
+ fill: "none",
679
+ stroke: "white",
680
+ strokeWidth,
681
+ strokeLinecap: "round",
682
+ strokeDasharray: circumference,
683
+ strokeDashoffset: offset,
684
+ transform: `rotate(-90 ${size / 2} ${size / 2})`
685
+ }
686
+ ) })
687
+ ] }),
633
688
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
634
689
  "circle",
635
690
  {
@@ -655,9 +710,21 @@ var ProgressCircular = _react.forwardRef.call(void 0,
655
710
  strokeDashoffset: offset,
656
711
  transform: `rotate(-90 ${size / 2} ${size / 2})`,
657
712
  style: {
658
- transition: `stroke-dashoffset var(--bloom-duration) var(--bloom-ease)`
713
+ transition: `stroke-dashoffset var(--bloom-duration-slow) var(--bloom-ease)`
659
714
  }
660
715
  }
716
+ ),
717
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
718
+ "circle",
719
+ {
720
+ cx: size / 2,
721
+ cy: size / 2,
722
+ r: radius,
723
+ fill: "none",
724
+ stroke: `url(#bloom-sweep-grad-${size})`,
725
+ strokeWidth,
726
+ mask: `url(#bloom-sweep-mask-${size})`
727
+ }
661
728
  )
662
729
  ] })
663
730
  }
@@ -1214,6 +1281,146 @@ var Skeleton = _react.forwardRef.call(void 0,
1214
1281
  );
1215
1282
  Skeleton.displayName = "Skeleton";
1216
1283
 
1284
+ // src/components/theme/theme.tsx
1285
+
1286
+
1287
+
1288
+
1289
+
1290
+
1291
+
1292
+
1293
+
1294
+ var ThemeContext = _react.createContext.call(void 0, null);
1295
+ function useTheme() {
1296
+ const ctx = _react.useContext.call(void 0, ThemeContext);
1297
+ if (!ctx) throw new Error("useTheme must be used within <ThemeProvider>");
1298
+ return ctx;
1299
+ }
1300
+ function getSystemPreference() {
1301
+ if (typeof window === "undefined") return "light";
1302
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
1303
+ }
1304
+ function resolveMode(mode) {
1305
+ if (mode === "system") return getSystemPreference();
1306
+ return mode;
1307
+ }
1308
+ function ThemeProvider({
1309
+ children,
1310
+ defaultColorMode = "system",
1311
+ defaultPalette = "bloom",
1312
+ palettes = [],
1313
+ storageKey = "bloom-theme"
1314
+ }) {
1315
+ const paletteMap = _react.useRef.call(void 0,
1316
+ new Map([
1317
+ ["bloom", { name: "bloom" }],
1318
+ ...palettes.map((p) => [p.name, p])
1319
+ ])
1320
+ );
1321
+ const [colorMode, setColorModeState] = _react.useState.call(void 0, () => {
1322
+ if (typeof window === "undefined") return defaultColorMode;
1323
+ const stored = localStorage.getItem(`${storageKey}-mode`);
1324
+ if (stored === "light" || stored === "dark" || stored === "system") return stored;
1325
+ return defaultColorMode;
1326
+ });
1327
+ const [palette, setPaletteState] = _react.useState.call(void 0, () => {
1328
+ if (typeof window === "undefined") return defaultPalette;
1329
+ const stored = localStorage.getItem(`${storageKey}-palette`);
1330
+ if (stored && paletteMap.current.has(stored)) return stored;
1331
+ return defaultPalette;
1332
+ });
1333
+ const [resolvedMode, setResolvedMode] = _react.useState.call(void 0,
1334
+ () => resolveMode(colorMode)
1335
+ );
1336
+ const setColorMode = _react.useCallback.call(void 0,
1337
+ (mode) => {
1338
+ setColorModeState(mode);
1339
+ localStorage.setItem(`${storageKey}-mode`, mode);
1340
+ },
1341
+ [storageKey]
1342
+ );
1343
+ const setPalette = _react.useCallback.call(void 0,
1344
+ (name) => {
1345
+ if (!paletteMap.current.has(name)) return;
1346
+ setPaletteState(name);
1347
+ localStorage.setItem(`${storageKey}-palette`, name);
1348
+ },
1349
+ [storageKey]
1350
+ );
1351
+ const toggleColorMode = _react.useCallback.call(void 0, () => {
1352
+ setColorMode(resolvedMode === "light" ? "dark" : "light");
1353
+ }, [resolvedMode, setColorMode]);
1354
+ _react.useEffect.call(void 0, () => {
1355
+ const root = document.documentElement;
1356
+ const resolved = resolveMode(colorMode);
1357
+ setResolvedMode(resolved);
1358
+ root.classList.remove("light", "dark");
1359
+ root.classList.add(resolved);
1360
+ const allKeys = /* @__PURE__ */ new Set();
1361
+ paletteMap.current.forEach((p) => {
1362
+ if (p.light) Object.keys(p.light).forEach((k) => allKeys.add(k));
1363
+ if (p.dark) Object.keys(p.dark).forEach((k) => allKeys.add(k));
1364
+ });
1365
+ allKeys.forEach((key) => root.style.removeProperty(key));
1366
+ const currentPalette = paletteMap.current.get(palette);
1367
+ if (currentPalette) {
1368
+ const vars = resolved === "dark" ? currentPalette.dark : currentPalette.light;
1369
+ if (vars) {
1370
+ Object.entries(vars).forEach(([key, value]) => {
1371
+ root.style.setProperty(key, value);
1372
+ });
1373
+ }
1374
+ }
1375
+ }, [colorMode, palette]);
1376
+ _react.useEffect.call(void 0, () => {
1377
+ if (colorMode !== "system") return;
1378
+ const mq = window.matchMedia("(prefers-color-scheme: dark)");
1379
+ const handler = () => {
1380
+ const resolved = resolveMode("system");
1381
+ setResolvedMode(resolved);
1382
+ const root = document.documentElement;
1383
+ root.classList.remove("light", "dark");
1384
+ root.classList.add(resolved);
1385
+ const allKeys = /* @__PURE__ */ new Set();
1386
+ paletteMap.current.forEach((p) => {
1387
+ if (p.light) Object.keys(p.light).forEach((k) => allKeys.add(k));
1388
+ if (p.dark) Object.keys(p.dark).forEach((k) => allKeys.add(k));
1389
+ });
1390
+ allKeys.forEach((key) => root.style.removeProperty(key));
1391
+ const currentPalette = paletteMap.current.get(palette);
1392
+ if (currentPalette) {
1393
+ const vars = resolved === "dark" ? currentPalette.dark : currentPalette.light;
1394
+ if (vars) {
1395
+ Object.entries(vars).forEach(([key, value]) => {
1396
+ root.style.setProperty(key, value);
1397
+ });
1398
+ }
1399
+ }
1400
+ };
1401
+ mq.addEventListener("change", handler);
1402
+ return () => mq.removeEventListener("change", handler);
1403
+ }, [colorMode, palette]);
1404
+ const paletteNames = Array.from(paletteMap.current.keys());
1405
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1406
+ ThemeContext.Provider,
1407
+ {
1408
+ value: {
1409
+ colorMode,
1410
+ resolvedMode,
1411
+ setColorMode,
1412
+ toggleColorMode,
1413
+ palette,
1414
+ setPalette,
1415
+ palettes: paletteNames
1416
+ },
1417
+ children
1418
+ }
1419
+ );
1420
+ }
1421
+
1422
+
1423
+
1217
1424
 
1218
1425
 
1219
1426
 
@@ -1269,5 +1476,5 @@ Skeleton.displayName = "Skeleton";
1269
1476
 
1270
1477
 
1271
1478
 
1272
- exports.Alert = Alert; exports.AlertDescription = AlertDescription; exports.AlertTitle = AlertTitle; exports.Avatar = Avatar; exports.AvatarGroup = AvatarGroup; exports.Badge = Badge; exports.Button = Button; exports.Card = Card; exports.CardContent = CardContent; exports.CardDescription = CardDescription; exports.CardFooter = CardFooter; exports.CardHeader = CardHeader; exports.CardTitle = CardTitle; exports.DatePicker = DatePicker; exports.Dropdown = Dropdown; exports.DropdownItem = DropdownItem; exports.DropdownSeparator = DropdownSeparator; exports.Input = Input; exports.Modal = Modal; exports.Progress = Progress; exports.ProgressCircular = ProgressCircular; exports.Skeleton = Skeleton; exports.Slider = Slider; exports.Tabs = Tabs; exports.TabsContent = TabsContent; exports.TabsList = TabsList; exports.TabsTrigger = TabsTrigger; exports.Textarea = Textarea; exports.ToastProvider = ToastProvider; exports.Toggle = Toggle; exports.Tooltip = Tooltip; exports.TooltipProvider = TooltipProvider; exports.alertVariants = alertVariants; exports.avatarVariants = avatarVariants; exports.badgeVariants = badgeVariants; exports.bloomSpring = bloomSpring; exports.bloomTransition = bloomTransition; exports.bloomTransitionFast = bloomTransitionFast; exports.bloomTransitionSlow = bloomTransitionSlow; exports.buttonVariants = buttonVariants; exports.cardHoverLift = cardHoverLift; exports.cardVariants = cardVariants; exports.cn = cn; exports.fadeIn = fadeIn; exports.hoverLift = hoverLift; exports.inputVariants = inputVariants; exports.progressFillVariants = progressFillVariants; exports.progressTrackVariants = progressTrackVariants; exports.skeletonVariants = skeletonVariants; exports.slideUp = slideUp; exports.tabsListVariants = tabsListVariants; exports.toastVariants = toastVariants; exports.useBreathing = useBreathing; exports.useReducedMotion = useReducedMotion; exports.useToast = useToast;
1479
+ exports.Alert = Alert; exports.AlertDescription = AlertDescription; exports.AlertTitle = AlertTitle; exports.Avatar = Avatar; exports.AvatarGroup = AvatarGroup; exports.Badge = Badge; exports.Button = Button; exports.Card = Card; exports.CardContent = CardContent; exports.CardDescription = CardDescription; exports.CardFooter = CardFooter; exports.CardHeader = CardHeader; exports.CardTitle = CardTitle; exports.DatePicker = DatePicker; exports.Dropdown = Dropdown; exports.DropdownItem = DropdownItem; exports.DropdownSeparator = DropdownSeparator; exports.Input = Input; exports.Modal = Modal; exports.Progress = Progress; exports.ProgressCircular = ProgressCircular; exports.Skeleton = Skeleton; exports.Slider = Slider; exports.Tabs = Tabs; exports.TabsContent = TabsContent; exports.TabsList = TabsList; exports.TabsTrigger = TabsTrigger; exports.Textarea = Textarea; exports.ThemeProvider = ThemeProvider; exports.ToastProvider = ToastProvider; exports.Toggle = Toggle; exports.Tooltip = Tooltip; exports.TooltipProvider = TooltipProvider; exports.alertVariants = alertVariants; exports.avatarVariants = avatarVariants; exports.badgeVariants = badgeVariants; exports.bloomSpring = bloomSpring; exports.bloomTransition = bloomTransition; exports.bloomTransitionFast = bloomTransitionFast; exports.bloomTransitionSlow = bloomTransitionSlow; exports.buttonVariants = buttonVariants; exports.cardHoverLift = cardHoverLift; exports.cardVariants = cardVariants; exports.cn = cn; exports.fadeIn = fadeIn; exports.hoverLift = hoverLift; exports.inputVariants = inputVariants; exports.progressFillVariants = progressFillVariants; exports.progressTrackVariants = progressTrackVariants; exports.skeletonVariants = skeletonVariants; exports.slideUp = slideUp; exports.tabsListVariants = tabsListVariants; exports.toastVariants = toastVariants; exports.useBreathing = useBreathing; exports.useReducedMotion = useReducedMotion; exports.useTheme = useTheme; exports.useToast = useToast;
1273
1480
  //# sourceMappingURL=index.cjs.map