@bloomkit/react 0.1.6 → 0.2.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/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
@@ -1214,6 +1214,146 @@ var Skeleton = _react.forwardRef.call(void 0,
1214
1214
  );
1215
1215
  Skeleton.displayName = "Skeleton";
1216
1216
 
1217
+ // src/components/theme/theme.tsx
1218
+
1219
+
1220
+
1221
+
1222
+
1223
+
1224
+
1225
+
1226
+
1227
+ var ThemeContext = _react.createContext.call(void 0, null);
1228
+ function useTheme() {
1229
+ const ctx = _react.useContext.call(void 0, ThemeContext);
1230
+ if (!ctx) throw new Error("useTheme must be used within <ThemeProvider>");
1231
+ return ctx;
1232
+ }
1233
+ function getSystemPreference() {
1234
+ if (typeof window === "undefined") return "light";
1235
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
1236
+ }
1237
+ function resolveMode(mode) {
1238
+ if (mode === "system") return getSystemPreference();
1239
+ return mode;
1240
+ }
1241
+ function ThemeProvider({
1242
+ children,
1243
+ defaultColorMode = "system",
1244
+ defaultPalette = "bloom",
1245
+ palettes = [],
1246
+ storageKey = "bloom-theme"
1247
+ }) {
1248
+ const paletteMap = _react.useRef.call(void 0,
1249
+ new Map([
1250
+ ["bloom", { name: "bloom" }],
1251
+ ...palettes.map((p) => [p.name, p])
1252
+ ])
1253
+ );
1254
+ const [colorMode, setColorModeState] = _react.useState.call(void 0, () => {
1255
+ if (typeof window === "undefined") return defaultColorMode;
1256
+ const stored = localStorage.getItem(`${storageKey}-mode`);
1257
+ if (stored === "light" || stored === "dark" || stored === "system") return stored;
1258
+ return defaultColorMode;
1259
+ });
1260
+ const [palette, setPaletteState] = _react.useState.call(void 0, () => {
1261
+ if (typeof window === "undefined") return defaultPalette;
1262
+ const stored = localStorage.getItem(`${storageKey}-palette`);
1263
+ if (stored && paletteMap.current.has(stored)) return stored;
1264
+ return defaultPalette;
1265
+ });
1266
+ const [resolvedMode, setResolvedMode] = _react.useState.call(void 0,
1267
+ () => resolveMode(colorMode)
1268
+ );
1269
+ const setColorMode = _react.useCallback.call(void 0,
1270
+ (mode) => {
1271
+ setColorModeState(mode);
1272
+ localStorage.setItem(`${storageKey}-mode`, mode);
1273
+ },
1274
+ [storageKey]
1275
+ );
1276
+ const setPalette = _react.useCallback.call(void 0,
1277
+ (name) => {
1278
+ if (!paletteMap.current.has(name)) return;
1279
+ setPaletteState(name);
1280
+ localStorage.setItem(`${storageKey}-palette`, name);
1281
+ },
1282
+ [storageKey]
1283
+ );
1284
+ const toggleColorMode = _react.useCallback.call(void 0, () => {
1285
+ setColorMode(resolvedMode === "light" ? "dark" : "light");
1286
+ }, [resolvedMode, setColorMode]);
1287
+ _react.useEffect.call(void 0, () => {
1288
+ const root = document.documentElement;
1289
+ const resolved = resolveMode(colorMode);
1290
+ setResolvedMode(resolved);
1291
+ root.classList.remove("light", "dark");
1292
+ root.classList.add(resolved);
1293
+ const allKeys = /* @__PURE__ */ new Set();
1294
+ paletteMap.current.forEach((p) => {
1295
+ if (p.light) Object.keys(p.light).forEach((k) => allKeys.add(k));
1296
+ if (p.dark) Object.keys(p.dark).forEach((k) => allKeys.add(k));
1297
+ });
1298
+ allKeys.forEach((key) => root.style.removeProperty(key));
1299
+ const currentPalette = paletteMap.current.get(palette);
1300
+ if (currentPalette) {
1301
+ const vars = resolved === "dark" ? currentPalette.dark : currentPalette.light;
1302
+ if (vars) {
1303
+ Object.entries(vars).forEach(([key, value]) => {
1304
+ root.style.setProperty(key, value);
1305
+ });
1306
+ }
1307
+ }
1308
+ }, [colorMode, palette]);
1309
+ _react.useEffect.call(void 0, () => {
1310
+ if (colorMode !== "system") return;
1311
+ const mq = window.matchMedia("(prefers-color-scheme: dark)");
1312
+ const handler = () => {
1313
+ const resolved = resolveMode("system");
1314
+ setResolvedMode(resolved);
1315
+ const root = document.documentElement;
1316
+ root.classList.remove("light", "dark");
1317
+ root.classList.add(resolved);
1318
+ const allKeys = /* @__PURE__ */ new Set();
1319
+ paletteMap.current.forEach((p) => {
1320
+ if (p.light) Object.keys(p.light).forEach((k) => allKeys.add(k));
1321
+ if (p.dark) Object.keys(p.dark).forEach((k) => allKeys.add(k));
1322
+ });
1323
+ allKeys.forEach((key) => root.style.removeProperty(key));
1324
+ const currentPalette = paletteMap.current.get(palette);
1325
+ if (currentPalette) {
1326
+ const vars = resolved === "dark" ? currentPalette.dark : currentPalette.light;
1327
+ if (vars) {
1328
+ Object.entries(vars).forEach(([key, value]) => {
1329
+ root.style.setProperty(key, value);
1330
+ });
1331
+ }
1332
+ }
1333
+ };
1334
+ mq.addEventListener("change", handler);
1335
+ return () => mq.removeEventListener("change", handler);
1336
+ }, [colorMode, palette]);
1337
+ const paletteNames = Array.from(paletteMap.current.keys());
1338
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1339
+ ThemeContext.Provider,
1340
+ {
1341
+ value: {
1342
+ colorMode,
1343
+ resolvedMode,
1344
+ setColorMode,
1345
+ toggleColorMode,
1346
+ palette,
1347
+ setPalette,
1348
+ palettes: paletteNames
1349
+ },
1350
+ children
1351
+ }
1352
+ );
1353
+ }
1354
+
1355
+
1356
+
1217
1357
 
1218
1358
 
1219
1359
 
@@ -1269,5 +1409,5 @@ Skeleton.displayName = "Skeleton";
1269
1409
 
1270
1410
 
1271
1411
 
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;
1412
+ 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
1413
  //# sourceMappingURL=index.cjs.map