@djangocfg/ui-core 2.1.209 → 2.1.210

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
@@ -65,35 +65,58 @@ pnpm add @djangocfg/ui-core
65
65
 
66
66
  ## Theme Palette Hooks
67
67
 
68
- Hooks for accessing theme colors from CSS variables (useful for charts, diagrams, etc.):
68
+ Hooks for accessing theme colors from CSS variables (useful for Canvas, SVG, charts, diagrams, etc.):
69
69
 
70
- | Hook | Description |
71
- |------|-------------|
72
- | `useThemePalette()` | Full color palette from CSS variables |
73
- | `useStylePresets()` | Pre-built style configs for diagrams |
74
- | `useBoxColors()` | Colors for boxes/containers |
70
+ | Hook / Util | Description |
71
+ |-------------|-------------|
72
+ | `useThemePalette()` | Full hex color palette from CSS variables |
73
+ | `useThemeColor(var, opacity?)` | Single color by CSS var name — lighter alternative |
74
+ | `useStylePresets()` | Pre-built `{ fill, stroke, color }` configs for diagrams |
75
+ | `useBoxColors()` | Semi-transparent RGBA colors for boxes/containers |
76
+ | `alpha(hex, opacity)` | Convert hex color to `rgba()` string |
75
77
 
76
78
  ```tsx
77
- import { useStylePresets, useBoxColors } from '@djangocfg/ui-core/styles/palette';
79
+ import {
80
+ useThemePalette,
81
+ useThemeColor,
82
+ useStylePresets,
83
+ useBoxColors,
84
+ alpha,
85
+ } from '@djangocfg/ui-core/styles/palette';
86
+
87
+ function MyCanvas() {
88
+ const palette = useThemePalette();
89
+
90
+ // Use in Canvas / inline styles
91
+ ctx.fillStyle = palette.primary; // '#a855f7' (theme-aware)
92
+ ctx.fillStyle = alpha(palette.primary, 0.3); // 'rgba(168, 85, 247, 0.3)'
93
+ }
94
+
95
+ function MyWaveform() {
96
+ // Lighter: only subscribes to 'primary', not the entire palette
97
+ const primary = useThemeColor('primary'); // '#a855f7'
98
+ const primaryFaded = useThemeColor('primary', 0.3); // 'rgba(168, 85, 247, 0.3)'
99
+ const errorBackground = useThemeColor('destructive', 0.1);
100
+ }
78
101
 
79
102
  function MyChart() {
80
103
  const presets = useStylePresets();
81
- // presets.success = { fill: '#22c55e', stroke: '#16a34a', color: '#ffffff' }
82
- // presets.warning = { fill: '#f59e0b', stroke: '#d97706', color: '#000000' }
83
- // presets.danger = { fill: '#ef4444', stroke: '#dc2626', color: '#ffffff' }
84
- // presets.info = { fill: '#3b82f6', stroke: '#2563eb', color: '#ffffff' }
85
- // presets.primary = { fill: '#...', stroke: '#...', color: '#...' }
104
+ // presets.primary = { fill: '#a855f7', stroke: '#a855f7', color: '#fff' }
105
+ // presets.success = { fill: '#22c55e', stroke: '#22c55e', color: '#fff' }
106
+ // presets.danger = { fill: '#ef4444', stroke: '#ef4444', color: '#fff' }
107
+ // presets.warning = { fill: '#f59e0b', stroke: '#f59e0b', color: '#fff' }
108
+ // presets.info = { fill: '#3b82f6', stroke: '#3b82f6', color: '#fff' }
86
109
 
87
110
  const boxes = useBoxColors();
88
- // boxes.primary = 'rgba(59, 130, 246, 0.2)'
89
- // boxes.success = 'rgba(34, 197, 94, 0.2)'
111
+ // boxes.primary = 'rgba(168, 85, 247, 0.15)' (0.3 in dark mode)
112
+ // boxes.success = 'rgba(34, 197, 94, 0.15)'
90
113
  // etc.
91
114
 
92
115
  return <Chart colors={[presets.success.fill, presets.warning.fill]} />;
93
116
  }
94
117
  ```
95
118
 
96
- ### Color Utilities
119
+ ### Color Utilities (HSL conversion)
97
120
 
98
121
  ```tsx
99
122
  import { hslToHex, hslToRgbString, hslToRgba } from '@djangocfg/ui-core/styles/palette';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/ui-core",
3
- "version": "2.1.209",
3
+ "version": "2.1.210",
4
4
  "description": "Pure React UI component library without Next.js dependencies - for Electron, Vite, CRA apps",
5
5
  "keywords": [
6
6
  "ui-components",
@@ -81,7 +81,7 @@
81
81
  "playground": "playground dev"
82
82
  },
83
83
  "peerDependencies": {
84
- "@djangocfg/i18n": "^2.1.209",
84
+ "@djangocfg/i18n": "^2.1.210",
85
85
  "react-device-detect": "^2.2.3",
86
86
  "consola": "^3.4.2",
87
87
  "lucide-react": "^0.545.0",
@@ -143,9 +143,9 @@
143
143
  "vaul": "1.1.2"
144
144
  },
145
145
  "devDependencies": {
146
- "@djangocfg/i18n": "^2.1.209",
146
+ "@djangocfg/i18n": "^2.1.210",
147
147
  "@djangocfg/playground": "workspace:*",
148
- "@djangocfg/typescript-config": "^2.1.209",
148
+ "@djangocfg/typescript-config": "^2.1.210",
149
149
  "@types/node": "^24.7.2",
150
150
  "@types/react": "^19.1.0",
151
151
  "@types/react-dom": "^19.1.0",
@@ -11,6 +11,11 @@ styles/
11
11
  ├── base.css # Base element styles
12
12
  ├── utilities.css # Custom utility classes
13
13
  ├── sources.css # Source detection for Tailwind v4 monorepo
14
+ ├── palette/ # Theme palette hooks & utilities
15
+ │ ├── index.ts # Exports
16
+ │ ├── types.ts # ThemePalette, StylePresets, BoxColors interfaces
17
+ │ ├── useThemePalette.ts # useThemePalette, useStylePresets, useBoxColors, alpha
18
+ │ └── utils.ts # hslToHex, hslToRgbString, hslToRgba
14
19
  └── theme/
15
20
  ├── tokens.css # @theme block with CSS custom properties
16
21
  ├── light.css # Light theme variables
@@ -204,6 +209,51 @@ Z-index utilities (`z-50`, `z-100`) require `--z-index-*` variables:
204
209
  <div className="aspect-square rounded-full overflow-hidden w-10 h-10">
205
210
  ```
206
211
 
212
+ ## Theme Palette (Programmatic Color Access)
213
+
214
+ For Canvas, SVG, Mermaid, and other contexts that don't support CSS variables, use the palette hooks:
215
+
216
+ ```tsx
217
+ import {
218
+ useThemePalette,
219
+ useStylePresets,
220
+ useBoxColors,
221
+ alpha,
222
+ } from '@djangocfg/ui-core/styles/palette';
223
+
224
+ // Hex colors from CSS variables (updates on theme switch)
225
+ const palette = useThemePalette();
226
+ ctx.fillStyle = palette.primary; // '#a855f7'
227
+ ctx.fillStyle = alpha(palette.primary, 0.3); // 'rgba(168, 85, 247, 0.3)'
228
+
229
+ // Pre-built { fill, stroke, color } for diagrams
230
+ const presets = useStylePresets();
231
+ presets.success // { fill: '#22c55e', stroke: '#22c55e', color: '#fff' }
232
+ presets.danger // { fill: '#ef4444', stroke: '#ef4444', color: '#fff' }
233
+
234
+ // Semi-transparent backgrounds (adapts opacity to light/dark)
235
+ const boxes = useBoxColors();
236
+ boxes.primary // 'rgba(168, 85, 247, 0.15)' in light, 0.3 in dark
237
+ ```
238
+
239
+ ### `alpha(hex, opacity)`
240
+
241
+ Convert any hex color to `rgba()` — works with any value from `ThemePalette`:
242
+
243
+ ```tsx
244
+ alpha(palette.primary, 0.3) // 'rgba(168, 85, 247, 0.3)'
245
+ alpha(palette.destructive, 0.1) // useful for error backgrounds
246
+ ```
247
+
248
+ ### Raw HSL Utilities
249
+
250
+ ```tsx
251
+ import { hslToHex, hslToRgbString, hslToRgba } from '@djangocfg/ui-core/styles/palette';
252
+
253
+ hslToHex('265 89% 78%') // '#c084fc'
254
+ hslToRgba('265 89% 78%', 0.2) // 'rgba(192, 132, 252, 0.2)'
255
+ ```
256
+
207
257
  ## What to Avoid
208
258
 
209
259
  - Custom utilities like: `section-padding`, `animate-*`, `shadow-brand`
@@ -45,6 +45,8 @@ export {
45
45
  useThemePalette,
46
46
  useStylePresets,
47
47
  useBoxColors,
48
+ useThemeColor,
49
+ alpha,
48
50
  } from './useThemePalette';
49
51
 
50
52
  // Utils (for advanced use)
@@ -11,19 +11,53 @@
11
11
 
12
12
  import { useMemo } from 'react';
13
13
  import { useResolvedTheme } from '../../hooks/useResolvedTheme';
14
- import { hslToHex, hslToRgba } from './utils';
14
+ import { hslToHex } from './utils';
15
15
  import type { ThemePalette, StylePresets, BoxColors } from './types';
16
16
 
17
17
  /**
18
- * Read a CSS variable from document and convert to hex
18
+ * SSR-safe fallback palette (light theme defaults).
19
+ * Used when document is not available (server-side rendering).
20
+ */
21
+ const SSR_FALLBACK: Record<string, string> = {
22
+ 'background': '#f5f5f5',
23
+ 'foreground': '#171717',
24
+ 'card': '#ffffff',
25
+ 'card-foreground': '#171717',
26
+ 'popover': '#ffffff',
27
+ 'popover-foreground': '#171717',
28
+ 'muted': '#e5e5e5',
29
+ 'muted-foreground': '#737373',
30
+ 'border': '#e5e5e5',
31
+ 'input': '#e5e5e5',
32
+ 'ring': '#3b82f6',
33
+ 'primary': '#3b82f6',
34
+ 'primary-foreground': '#ffffff',
35
+ 'secondary': '#f5f5f5',
36
+ 'secondary-foreground': '#171717',
37
+ 'accent': '#f5f5f5',
38
+ 'accent-foreground': '#171717',
39
+ 'destructive': '#ef4444',
40
+ 'destructive-foreground': '#ffffff',
41
+ 'chart-1': '#3b82f6',
42
+ 'chart-2': '#22c55e',
43
+ 'chart-3': '#8b5cf6',
44
+ 'chart-4': '#f59e0b',
45
+ 'chart-5': '#ef4444',
46
+ };
47
+
48
+ /**
49
+ * Read a CSS variable from document and convert to hex.
50
+ * Falls back to SSR_FALLBACK on the server.
19
51
  */
20
52
  function getCssColorAsHex(varName: string): string {
21
- if (typeof document === 'undefined') return '#000000';
53
+ if (typeof document === 'undefined') {
54
+ return SSR_FALLBACK[varName] ?? '#000000';
55
+ }
22
56
 
23
57
  const style = getComputedStyle(document.documentElement);
24
58
  const value = style.getPropertyValue(`--${varName}`).trim();
25
59
 
26
- if (!value) return '#000000';
60
+ if (!value) return SSR_FALLBACK[varName] ?? '#000000';
27
61
 
28
62
  // If already hex, return as-is
29
63
  if (value.startsWith('#')) return value;
@@ -32,35 +66,31 @@ function getCssColorAsHex(varName: string): string {
32
66
  return hslToHex(value);
33
67
  }
34
68
 
69
+
35
70
  /**
36
- * Read a CSS variable from document and convert to rgba with opacity
71
+ * Convert a hex color to rgba string with given opacity.
72
+ * Works with any hex returned from ThemePalette.
73
+ *
74
+ * @example
75
+ * const palette = useThemePalette();
76
+ * ctx.fillStyle = alpha(palette.primary, 0.3);
37
77
  */
38
- function getCssColorAsRgba(varName: string, alpha: number): string {
39
- if (typeof document === 'undefined') return `rgba(0, 0, 0, ${alpha})`;
40
-
41
- const style = getComputedStyle(document.documentElement);
42
- const value = style.getPropertyValue(`--${varName}`).trim();
43
-
44
- if (!value) return `rgba(0, 0, 0, ${alpha})`;
45
-
46
- return hslToRgba(value, alpha);
78
+ export function alpha(hexColor: string, opacity: number): string {
79
+ const hex = hexColor.replace('#', '');
80
+ const r = parseInt(hex.substring(0, 2), 16);
81
+ const g = parseInt(hex.substring(2, 4), 16);
82
+ const b = parseInt(hex.substring(4, 6), 16);
83
+ return `rgba(${r}, ${g}, ${b}, ${opacity})`;
47
84
  }
48
85
 
49
86
  /**
50
- * Hook to get the full theme palette from CSS variables
87
+ * Hook to get the full theme palette from CSS variables.
88
+ * Returns hex colors for use in Canvas, SVG, Mermaid, and inline styles.
51
89
  *
52
90
  * @example
53
- * ```tsx
54
- * function MyComponent() {
55
- * const palette = useThemePalette();
56
- *
57
- * // Use in canvas
58
- * ctx.fillStyle = palette.primary;
59
- *
60
- * // Use in inline styles
61
- * <div style={{ backgroundColor: palette.success }} />
62
- * }
63
- * ```
91
+ * const palette = useThemePalette();
92
+ * ctx.fillStyle = palette.primary;
93
+ * ctx.fillStyle = alpha(palette.primary, 0.3);
64
94
  */
65
95
  export function useThemePalette(): ThemePalette {
66
96
  const theme = useResolvedTheme();
@@ -216,18 +246,43 @@ export function useStylePresets(): StylePresets {
216
246
  */
217
247
  export function useBoxColors(): BoxColors {
218
248
  const theme = useResolvedTheme();
249
+ const palette = useThemePalette();
219
250
 
220
251
  return useMemo(() => {
221
- // Semi-transparent backgrounds for boxes
222
- const alpha = theme === 'dark' ? 0.3 : 0.15;
252
+ const opacity = theme === 'dark' ? 0.3 : 0.15;
223
253
 
224
254
  return {
225
- primary: getCssColorAsRgba('primary', alpha),
226
- success: getCssColorAsRgba('chart-2', alpha),
227
- warning: getCssColorAsRgba('chart-4', alpha),
228
- danger: getCssColorAsRgba('destructive', alpha),
229
- info: getCssColorAsRgba('chart-3', alpha),
230
- muted: getCssColorAsRgba('muted', alpha),
255
+ primary: alpha(palette.primary, opacity),
256
+ success: alpha(palette.success, opacity),
257
+ warning: alpha(palette.warning, opacity),
258
+ danger: alpha(palette.destructive, opacity),
259
+ info: alpha(palette.info, opacity),
260
+ muted: alpha(palette.muted, opacity),
231
261
  };
232
- }, [theme]);
262
+ }, [theme, palette]);
263
+ }
264
+
265
+ /**
266
+ * Hook to get a single theme color by CSS variable name.
267
+ * Lighter alternative to useThemePalette() when you only need 1-3 colors.
268
+ *
269
+ * @param varName - CSS variable name without `--` prefix (e.g. 'primary', 'card-foreground')
270
+ * @param opacity - Optional opacity 0-1. If provided, returns rgba() string instead of hex.
271
+ *
272
+ * @example
273
+ * // Hex color
274
+ * const primary = useThemeColor('primary'); // '#a855f7'
275
+ *
276
+ * // With opacity
277
+ * const wave = useThemeColor('primary', 0.3); // 'rgba(168, 85, 247, 0.3)'
278
+ * const bg = useThemeColor('destructive', 0.1); // useful for error backgrounds
279
+ */
280
+ export function useThemeColor(varName: string, opacity?: number): string {
281
+ const theme = useResolvedTheme();
282
+
283
+ return useMemo(() => {
284
+ const hex = getCssColorAsHex(varName);
285
+ return opacity !== undefined ? alpha(hex, opacity) : hex;
286
+ // eslint-disable-next-line react-hooks/exhaustive-deps
287
+ }, [theme, varName, opacity]);
233
288
  }