@prismiq/react 0.1.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 +88 -0
- package/dist/CustomSQLEditor-BXB4rf1q.d.cts +1297 -0
- package/dist/CustomSQLEditor-DYeId0Gp.d.ts +1297 -0
- package/dist/DashboardDialog-B3vYC5Gs.d.ts +1106 -0
- package/dist/DashboardDialog-LHmrtNQU.d.cts +1106 -0
- package/dist/accessibility-2yy5yqRR.d.cts +145 -0
- package/dist/accessibility-2yy5yqRR.d.ts +145 -0
- package/dist/charts/index.cjs +110 -0
- package/dist/charts/index.cjs.map +1 -0
- package/dist/charts/index.d.cts +2 -0
- package/dist/charts/index.d.ts +2 -0
- package/dist/charts/index.js +5 -0
- package/dist/charts/index.js.map +1 -0
- package/dist/chunk-2H5WTH4K.js +2409 -0
- package/dist/chunk-2H5WTH4K.js.map +1 -0
- package/dist/chunk-4AVL6GQK.cjs +470 -0
- package/dist/chunk-4AVL6GQK.cjs.map +1 -0
- package/dist/chunk-EX74SI67.js +455 -0
- package/dist/chunk-EX74SI67.js.map +1 -0
- package/dist/chunk-FEABEF3J.cjs +7543 -0
- package/dist/chunk-FEABEF3J.cjs.map +1 -0
- package/dist/chunk-JTCBZDHY.js +126 -0
- package/dist/chunk-JTCBZDHY.js.map +1 -0
- package/dist/chunk-LMTG3LRC.cjs +326 -0
- package/dist/chunk-LMTG3LRC.cjs.map +1 -0
- package/dist/chunk-MDXGGZSW.cjs +273 -0
- package/dist/chunk-MDXGGZSW.cjs.map +1 -0
- package/dist/chunk-MOAEEF5P.js +7510 -0
- package/dist/chunk-MOAEEF5P.js.map +1 -0
- package/dist/chunk-NK7HKX2J.cjs +2459 -0
- package/dist/chunk-NK7HKX2J.cjs.map +1 -0
- package/dist/chunk-NY6TZLST.cjs +8781 -0
- package/dist/chunk-NY6TZLST.cjs.map +1 -0
- package/dist/chunk-T6STUE7E.js +321 -0
- package/dist/chunk-T6STUE7E.js.map +1 -0
- package/dist/chunk-TRW7DKLP.cjs +141 -0
- package/dist/chunk-TRW7DKLP.cjs.map +1 -0
- package/dist/chunk-UPYINBZU.js +8706 -0
- package/dist/chunk-UPYINBZU.js.map +1 -0
- package/dist/chunk-WWTT2OJ5.js +246 -0
- package/dist/chunk-WWTT2OJ5.js.map +1 -0
- package/dist/components/index.cjs +222 -0
- package/dist/components/index.cjs.map +1 -0
- package/dist/components/index.d.cts +207 -0
- package/dist/components/index.d.ts +207 -0
- package/dist/components/index.js +5 -0
- package/dist/components/index.js.map +1 -0
- package/dist/dashboard/index.cjs +140 -0
- package/dist/dashboard/index.cjs.map +1 -0
- package/dist/dashboard/index.d.cts +302 -0
- package/dist/dashboard/index.d.ts +302 -0
- package/dist/dashboard/index.js +7 -0
- package/dist/dashboard/index.js.map +1 -0
- package/dist/export/index.cjs +32 -0
- package/dist/export/index.cjs.map +1 -0
- package/dist/export/index.d.cts +197 -0
- package/dist/export/index.d.ts +197 -0
- package/dist/export/index.js +3 -0
- package/dist/export/index.js.map +1 -0
- package/dist/index-C-Qcuu4Y.d.cts +821 -0
- package/dist/index-rPc7ijt8.d.ts +821 -0
- package/dist/index.cjs +1486 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1435 -0
- package/dist/index.d.ts +1435 -0
- package/dist/index.js +926 -0
- package/dist/index.js.map +1 -0
- package/dist/ssr/index.cjs +64 -0
- package/dist/ssr/index.cjs.map +1 -0
- package/dist/ssr/index.d.cts +213 -0
- package/dist/ssr/index.d.ts +213 -0
- package/dist/ssr/index.js +3 -0
- package/dist/ssr/index.js.map +1 -0
- package/dist/types-WrCbOeAV.d.cts +569 -0
- package/dist/types-WrCbOeAV.d.ts +569 -0
- package/dist/utils/index.cjs +64 -0
- package/dist/utils/index.cjs.map +1 -0
- package/dist/utils/index.d.cts +112 -0
- package/dist/utils/index.d.ts +112 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +110 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import { createContext, useState, useMemo, useCallback, useEffect, useContext } from 'react';
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
// src/theme/defaults.ts
|
|
5
|
+
var lightTheme = {
|
|
6
|
+
name: "light",
|
|
7
|
+
colors: {
|
|
8
|
+
primary: "#3b82f6",
|
|
9
|
+
primaryHover: "#2563eb",
|
|
10
|
+
background: "#ffffff",
|
|
11
|
+
surface: "#f9fafb",
|
|
12
|
+
surfaceHover: "#f3f4f6",
|
|
13
|
+
text: "#111827",
|
|
14
|
+
textMuted: "#6b7280",
|
|
15
|
+
textInverse: "#ffffff",
|
|
16
|
+
border: "#e5e7eb",
|
|
17
|
+
borderFocus: "#3b82f6",
|
|
18
|
+
success: "#10b981",
|
|
19
|
+
warning: "#f59e0b",
|
|
20
|
+
error: "#ef4444",
|
|
21
|
+
info: "#3b82f6"
|
|
22
|
+
},
|
|
23
|
+
fonts: {
|
|
24
|
+
sans: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
|
25
|
+
mono: '"SF Mono", "Fira Code", "Fira Mono", Menlo, Consolas, monospace'
|
|
26
|
+
},
|
|
27
|
+
fontSizes: {
|
|
28
|
+
xs: "10px",
|
|
29
|
+
sm: "12px",
|
|
30
|
+
base: "14px",
|
|
31
|
+
lg: "16px",
|
|
32
|
+
xl: "18px",
|
|
33
|
+
"2xl": "20px"
|
|
34
|
+
},
|
|
35
|
+
spacing: {
|
|
36
|
+
xs: "4px",
|
|
37
|
+
sm: "8px",
|
|
38
|
+
md: "12px",
|
|
39
|
+
lg: "16px",
|
|
40
|
+
xl: "24px"
|
|
41
|
+
},
|
|
42
|
+
radius: {
|
|
43
|
+
none: "0",
|
|
44
|
+
sm: "2px",
|
|
45
|
+
md: "4px",
|
|
46
|
+
lg: "8px",
|
|
47
|
+
full: "9999px"
|
|
48
|
+
},
|
|
49
|
+
shadows: {
|
|
50
|
+
sm: "0 1px 2px 0 rgba(0, 0, 0, 0.05)",
|
|
51
|
+
md: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)",
|
|
52
|
+
lg: "0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)"
|
|
53
|
+
},
|
|
54
|
+
chart: {
|
|
55
|
+
colors: [
|
|
56
|
+
"#3b82f6",
|
|
57
|
+
// blue
|
|
58
|
+
"#10b981",
|
|
59
|
+
// emerald
|
|
60
|
+
"#f59e0b",
|
|
61
|
+
// amber
|
|
62
|
+
"#ef4444",
|
|
63
|
+
// red
|
|
64
|
+
"#8b5cf6",
|
|
65
|
+
// violet
|
|
66
|
+
"#06b6d4",
|
|
67
|
+
// cyan
|
|
68
|
+
"#f97316",
|
|
69
|
+
// orange
|
|
70
|
+
"#84cc16",
|
|
71
|
+
// lime
|
|
72
|
+
"#ec4899",
|
|
73
|
+
// pink
|
|
74
|
+
"#6366f1"
|
|
75
|
+
// indigo
|
|
76
|
+
],
|
|
77
|
+
gridColor: "#e5e7eb",
|
|
78
|
+
axisColor: "#6b7280",
|
|
79
|
+
tooltipBackground: "#111827"
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
var darkTheme = {
|
|
83
|
+
name: "dark",
|
|
84
|
+
colors: {
|
|
85
|
+
primary: "#60a5fa",
|
|
86
|
+
primaryHover: "#3b82f6",
|
|
87
|
+
background: "#111827",
|
|
88
|
+
surface: "#1f2937",
|
|
89
|
+
surfaceHover: "#374151",
|
|
90
|
+
text: "#f9fafb",
|
|
91
|
+
textMuted: "#9ca3af",
|
|
92
|
+
textInverse: "#111827",
|
|
93
|
+
border: "#374151",
|
|
94
|
+
borderFocus: "#60a5fa",
|
|
95
|
+
success: "#34d399",
|
|
96
|
+
warning: "#fbbf24",
|
|
97
|
+
error: "#f87171",
|
|
98
|
+
info: "#60a5fa"
|
|
99
|
+
},
|
|
100
|
+
fonts: {
|
|
101
|
+
sans: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
|
102
|
+
mono: '"SF Mono", "Fira Code", "Fira Mono", Menlo, Consolas, monospace'
|
|
103
|
+
},
|
|
104
|
+
fontSizes: {
|
|
105
|
+
xs: "10px",
|
|
106
|
+
sm: "12px",
|
|
107
|
+
base: "14px",
|
|
108
|
+
lg: "16px",
|
|
109
|
+
xl: "18px",
|
|
110
|
+
"2xl": "20px"
|
|
111
|
+
},
|
|
112
|
+
spacing: {
|
|
113
|
+
xs: "4px",
|
|
114
|
+
sm: "8px",
|
|
115
|
+
md: "12px",
|
|
116
|
+
lg: "16px",
|
|
117
|
+
xl: "24px"
|
|
118
|
+
},
|
|
119
|
+
radius: {
|
|
120
|
+
none: "0",
|
|
121
|
+
sm: "2px",
|
|
122
|
+
md: "4px",
|
|
123
|
+
lg: "8px",
|
|
124
|
+
full: "9999px"
|
|
125
|
+
},
|
|
126
|
+
shadows: {
|
|
127
|
+
sm: "0 1px 2px 0 rgba(0, 0, 0, 0.3)",
|
|
128
|
+
md: "0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -1px rgba(0, 0, 0, 0.3)",
|
|
129
|
+
lg: "0 10px 15px -3px rgba(0, 0, 0, 0.4), 0 4px 6px -2px rgba(0, 0, 0, 0.3)"
|
|
130
|
+
},
|
|
131
|
+
chart: {
|
|
132
|
+
colors: [
|
|
133
|
+
"#60a5fa",
|
|
134
|
+
// blue
|
|
135
|
+
"#34d399",
|
|
136
|
+
// emerald
|
|
137
|
+
"#fbbf24",
|
|
138
|
+
// amber
|
|
139
|
+
"#f87171",
|
|
140
|
+
// red
|
|
141
|
+
"#a78bfa",
|
|
142
|
+
// violet
|
|
143
|
+
"#22d3ee",
|
|
144
|
+
// cyan
|
|
145
|
+
"#fb923c",
|
|
146
|
+
// orange
|
|
147
|
+
"#a3e635",
|
|
148
|
+
// lime
|
|
149
|
+
"#f472b6",
|
|
150
|
+
// pink
|
|
151
|
+
"#818cf8"
|
|
152
|
+
// indigo
|
|
153
|
+
],
|
|
154
|
+
gridColor: "#374151",
|
|
155
|
+
axisColor: "#9ca3af",
|
|
156
|
+
tooltipBackground: "#1f2937"
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
var STORAGE_KEY = "prismiq-theme-mode";
|
|
160
|
+
var CSS_VAR_PREFIX = "--prismiq";
|
|
161
|
+
var ThemeContext = createContext(null);
|
|
162
|
+
function deepMerge(target, source) {
|
|
163
|
+
const result = { ...target };
|
|
164
|
+
for (const key in source) {
|
|
165
|
+
const sourceValue = source[key];
|
|
166
|
+
const targetValue = target[key];
|
|
167
|
+
if (sourceValue !== void 0 && typeof sourceValue === "object" && sourceValue !== null && !Array.isArray(sourceValue) && typeof targetValue === "object" && targetValue !== null && !Array.isArray(targetValue)) {
|
|
168
|
+
result[key] = deepMerge(
|
|
169
|
+
targetValue,
|
|
170
|
+
sourceValue
|
|
171
|
+
);
|
|
172
|
+
} else if (sourceValue !== void 0) {
|
|
173
|
+
result[key] = sourceValue;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
function themeToCSSVariables(theme) {
|
|
179
|
+
const vars = [];
|
|
180
|
+
for (const [key, value] of Object.entries(theme.colors)) {
|
|
181
|
+
vars.push(`${CSS_VAR_PREFIX}-color-${kebabCase(key)}: ${value}`);
|
|
182
|
+
}
|
|
183
|
+
vars.push(`${CSS_VAR_PREFIX}-font-sans: ${theme.fonts.sans}`);
|
|
184
|
+
vars.push(`${CSS_VAR_PREFIX}-font-mono: ${theme.fonts.mono}`);
|
|
185
|
+
for (const [key, value] of Object.entries(theme.fontSizes)) {
|
|
186
|
+
vars.push(`${CSS_VAR_PREFIX}-font-size-${key}: ${value}`);
|
|
187
|
+
}
|
|
188
|
+
for (const [key, value] of Object.entries(theme.spacing)) {
|
|
189
|
+
vars.push(`${CSS_VAR_PREFIX}-spacing-${key}: ${value}`);
|
|
190
|
+
}
|
|
191
|
+
for (const [key, value] of Object.entries(theme.radius)) {
|
|
192
|
+
vars.push(`${CSS_VAR_PREFIX}-radius-${key}: ${value}`);
|
|
193
|
+
}
|
|
194
|
+
for (const [key, value] of Object.entries(theme.shadows)) {
|
|
195
|
+
vars.push(`${CSS_VAR_PREFIX}-shadow-${key}: ${value}`);
|
|
196
|
+
}
|
|
197
|
+
theme.chart.colors.forEach((color, index) => {
|
|
198
|
+
vars.push(`${CSS_VAR_PREFIX}-chart-color-${index + 1}: ${color}`);
|
|
199
|
+
});
|
|
200
|
+
vars.push(`${CSS_VAR_PREFIX}-chart-grid: ${theme.chart.gridColor}`);
|
|
201
|
+
vars.push(`${CSS_VAR_PREFIX}-chart-axis: ${theme.chart.axisColor}`);
|
|
202
|
+
vars.push(`${CSS_VAR_PREFIX}-chart-tooltip-bg: ${theme.chart.tooltipBackground}`);
|
|
203
|
+
return vars.join(";\n") + ";";
|
|
204
|
+
}
|
|
205
|
+
function kebabCase(str) {
|
|
206
|
+
return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
207
|
+
}
|
|
208
|
+
function getSystemTheme() {
|
|
209
|
+
if (typeof window === "undefined") return "light";
|
|
210
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
211
|
+
}
|
|
212
|
+
function getStoredMode() {
|
|
213
|
+
if (typeof window === "undefined") return null;
|
|
214
|
+
try {
|
|
215
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
216
|
+
if (stored === "light" || stored === "dark" || stored === "system") {
|
|
217
|
+
return stored;
|
|
218
|
+
}
|
|
219
|
+
} catch {
|
|
220
|
+
}
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
function storeMode(mode) {
|
|
224
|
+
if (typeof window === "undefined") return;
|
|
225
|
+
try {
|
|
226
|
+
localStorage.setItem(STORAGE_KEY, mode);
|
|
227
|
+
} catch {
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function ThemeProvider({
|
|
231
|
+
children,
|
|
232
|
+
defaultMode,
|
|
233
|
+
lightTheme: customLightTheme,
|
|
234
|
+
darkTheme: customDarkTheme,
|
|
235
|
+
className
|
|
236
|
+
}) {
|
|
237
|
+
const [mode, setModeState] = useState(() => {
|
|
238
|
+
const stored = getStoredMode();
|
|
239
|
+
return stored ?? defaultMode ?? "system";
|
|
240
|
+
});
|
|
241
|
+
const [systemTheme, setSystemTheme] = useState(getSystemTheme);
|
|
242
|
+
const resolvedMode = mode === "system" ? systemTheme : mode;
|
|
243
|
+
const mergedLightTheme = useMemo(
|
|
244
|
+
() => customLightTheme ? deepMerge(lightTheme, customLightTheme) : lightTheme,
|
|
245
|
+
[customLightTheme]
|
|
246
|
+
);
|
|
247
|
+
const mergedDarkTheme = useMemo(
|
|
248
|
+
() => customDarkTheme ? deepMerge(darkTheme, customDarkTheme) : darkTheme,
|
|
249
|
+
[customDarkTheme]
|
|
250
|
+
);
|
|
251
|
+
const theme = resolvedMode === "dark" ? mergedDarkTheme : mergedLightTheme;
|
|
252
|
+
const setMode = useCallback((newMode) => {
|
|
253
|
+
setModeState(newMode);
|
|
254
|
+
storeMode(newMode);
|
|
255
|
+
}, []);
|
|
256
|
+
useEffect(() => {
|
|
257
|
+
if (typeof window === "undefined") return;
|
|
258
|
+
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
259
|
+
const handleChange = (e) => {
|
|
260
|
+
setSystemTheme(e.matches ? "dark" : "light");
|
|
261
|
+
};
|
|
262
|
+
if (mediaQuery.addEventListener) {
|
|
263
|
+
mediaQuery.addEventListener("change", handleChange);
|
|
264
|
+
return () => mediaQuery.removeEventListener("change", handleChange);
|
|
265
|
+
}
|
|
266
|
+
mediaQuery.addListener(handleChange);
|
|
267
|
+
return () => mediaQuery.removeListener(handleChange);
|
|
268
|
+
}, []);
|
|
269
|
+
useEffect(() => {
|
|
270
|
+
if (typeof document === "undefined") return;
|
|
271
|
+
const styleId = "prismiq-theme-vars";
|
|
272
|
+
let styleElement = document.getElementById(styleId);
|
|
273
|
+
if (!styleElement) {
|
|
274
|
+
styleElement = document.createElement("style");
|
|
275
|
+
styleElement.id = styleId;
|
|
276
|
+
document.head.appendChild(styleElement);
|
|
277
|
+
}
|
|
278
|
+
const cssVars = themeToCSSVariables(theme);
|
|
279
|
+
styleElement.textContent = `:root {
|
|
280
|
+
${cssVars}
|
|
281
|
+
}`;
|
|
282
|
+
return () => {
|
|
283
|
+
};
|
|
284
|
+
}, [theme]);
|
|
285
|
+
const contextValue = useMemo(
|
|
286
|
+
() => ({
|
|
287
|
+
theme,
|
|
288
|
+
mode,
|
|
289
|
+
setMode,
|
|
290
|
+
resolvedMode
|
|
291
|
+
}),
|
|
292
|
+
[theme, mode, setMode, resolvedMode]
|
|
293
|
+
);
|
|
294
|
+
return /* @__PURE__ */ jsx(ThemeContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(
|
|
295
|
+
"div",
|
|
296
|
+
{
|
|
297
|
+
className,
|
|
298
|
+
"data-prismiq-theme": resolvedMode,
|
|
299
|
+
style: {
|
|
300
|
+
fontFamily: "var(--prismiq-font-sans)",
|
|
301
|
+
fontSize: "var(--prismiq-font-size-base)",
|
|
302
|
+
color: "var(--prismiq-color-text)",
|
|
303
|
+
backgroundColor: "var(--prismiq-color-background)"
|
|
304
|
+
},
|
|
305
|
+
children
|
|
306
|
+
}
|
|
307
|
+
) });
|
|
308
|
+
}
|
|
309
|
+
function useTheme() {
|
|
310
|
+
const context = useContext(ThemeContext);
|
|
311
|
+
if (context === null) {
|
|
312
|
+
throw new Error(
|
|
313
|
+
"useTheme must be used within a ThemeProvider. Wrap your component tree with <ThemeProvider>."
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
return context;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export { ThemeProvider, darkTheme, lightTheme, useTheme };
|
|
320
|
+
//# sourceMappingURL=chunk-T6STUE7E.js.map
|
|
321
|
+
//# sourceMappingURL=chunk-T6STUE7E.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/theme/defaults.ts","../src/theme/ThemeProvider.tsx","../src/theme/useTheme.ts"],"names":[],"mappings":"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","file":"chunk-T6STUE7E.js","sourcesContent":["/**\n * Default theme definitions for Prismiq.\n */\n\nimport type { PrismiqTheme } from './types';\n\n/**\n * Light theme - the default Prismiq theme.\n */\nexport const lightTheme: PrismiqTheme = {\n name: 'light',\n colors: {\n primary: '#3b82f6',\n primaryHover: '#2563eb',\n background: '#ffffff',\n surface: '#f9fafb',\n surfaceHover: '#f3f4f6',\n text: '#111827',\n textMuted: '#6b7280',\n textInverse: '#ffffff',\n border: '#e5e7eb',\n borderFocus: '#3b82f6',\n success: '#10b981',\n warning: '#f59e0b',\n error: '#ef4444',\n info: '#3b82f6',\n },\n fonts: {\n sans: '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif',\n mono: '\"SF Mono\", \"Fira Code\", \"Fira Mono\", Menlo, Consolas, monospace',\n },\n fontSizes: {\n xs: '10px',\n sm: '12px',\n base: '14px',\n lg: '16px',\n xl: '18px',\n '2xl': '20px',\n },\n spacing: {\n xs: '4px',\n sm: '8px',\n md: '12px',\n lg: '16px',\n xl: '24px',\n },\n radius: {\n none: '0',\n sm: '2px',\n md: '4px',\n lg: '8px',\n full: '9999px',\n },\n shadows: {\n sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',\n md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',\n lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',\n },\n chart: {\n colors: [\n '#3b82f6', // blue\n '#10b981', // emerald\n '#f59e0b', // amber\n '#ef4444', // red\n '#8b5cf6', // violet\n '#06b6d4', // cyan\n '#f97316', // orange\n '#84cc16', // lime\n '#ec4899', // pink\n '#6366f1', // indigo\n ],\n gridColor: '#e5e7eb',\n axisColor: '#6b7280',\n tooltipBackground: '#111827',\n },\n};\n\n/**\n * Dark theme for Prismiq.\n */\nexport const darkTheme: PrismiqTheme = {\n name: 'dark',\n colors: {\n primary: '#60a5fa',\n primaryHover: '#3b82f6',\n background: '#111827',\n surface: '#1f2937',\n surfaceHover: '#374151',\n text: '#f9fafb',\n textMuted: '#9ca3af',\n textInverse: '#111827',\n border: '#374151',\n borderFocus: '#60a5fa',\n success: '#34d399',\n warning: '#fbbf24',\n error: '#f87171',\n info: '#60a5fa',\n },\n fonts: {\n sans: '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif',\n mono: '\"SF Mono\", \"Fira Code\", \"Fira Mono\", Menlo, Consolas, monospace',\n },\n fontSizes: {\n xs: '10px',\n sm: '12px',\n base: '14px',\n lg: '16px',\n xl: '18px',\n '2xl': '20px',\n },\n spacing: {\n xs: '4px',\n sm: '8px',\n md: '12px',\n lg: '16px',\n xl: '24px',\n },\n radius: {\n none: '0',\n sm: '2px',\n md: '4px',\n lg: '8px',\n full: '9999px',\n },\n shadows: {\n sm: '0 1px 2px 0 rgba(0, 0, 0, 0.3)',\n md: '0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -1px rgba(0, 0, 0, 0.3)',\n lg: '0 10px 15px -3px rgba(0, 0, 0, 0.4), 0 4px 6px -2px rgba(0, 0, 0, 0.3)',\n },\n chart: {\n colors: [\n '#60a5fa', // blue\n '#34d399', // emerald\n '#fbbf24', // amber\n '#f87171', // red\n '#a78bfa', // violet\n '#22d3ee', // cyan\n '#fb923c', // orange\n '#a3e635', // lime\n '#f472b6', // pink\n '#818cf8', // indigo\n ],\n gridColor: '#374151',\n axisColor: '#9ca3af',\n tooltipBackground: '#1f2937',\n },\n};\n","/**\n * Theme Provider for Prismiq.\n *\n * Provides theme context and injects CSS variables into the document.\n */\n\nimport {\n createContext,\n useCallback,\n useEffect,\n useMemo,\n useState,\n type ReactNode,\n} from 'react';\n\nimport { darkTheme, lightTheme } from './defaults';\nimport type { DeepPartial, PrismiqTheme, ThemeContextValue, ThemeMode } from './types';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst STORAGE_KEY = 'prismiq-theme-mode';\nconst CSS_VAR_PREFIX = '--prismiq';\n\n// ============================================================================\n// Context\n// ============================================================================\n\nexport const ThemeContext = createContext<ThemeContextValue | null>(null);\n\n// ============================================================================\n// Utilities\n// ============================================================================\n\n/**\n * Deep merge two objects.\n */\nfunction deepMerge<T extends object>(target: T, source: DeepPartial<T>): T {\n const result = { ...target };\n\n for (const key in source) {\n const sourceValue = source[key];\n const targetValue = target[key];\n\n if (\n sourceValue !== undefined &&\n typeof sourceValue === 'object' &&\n sourceValue !== null &&\n !Array.isArray(sourceValue) &&\n typeof targetValue === 'object' &&\n targetValue !== null &&\n !Array.isArray(targetValue)\n ) {\n (result as Record<string, unknown>)[key] = deepMerge(\n targetValue as object,\n sourceValue as DeepPartial<object>\n );\n } else if (sourceValue !== undefined) {\n (result as Record<string, unknown>)[key] = sourceValue;\n }\n }\n\n return result;\n}\n\n/**\n * Convert a theme object to CSS variable declarations.\n */\nfunction themeToCSSVariables(theme: PrismiqTheme): string {\n const vars: string[] = [];\n\n // Colors\n for (const [key, value] of Object.entries(theme.colors)) {\n vars.push(`${CSS_VAR_PREFIX}-color-${kebabCase(key)}: ${value}`);\n }\n\n // Fonts\n vars.push(`${CSS_VAR_PREFIX}-font-sans: ${theme.fonts.sans}`);\n vars.push(`${CSS_VAR_PREFIX}-font-mono: ${theme.fonts.mono}`);\n\n // Font sizes\n for (const [key, value] of Object.entries(theme.fontSizes)) {\n vars.push(`${CSS_VAR_PREFIX}-font-size-${key}: ${value}`);\n }\n\n // Spacing\n for (const [key, value] of Object.entries(theme.spacing)) {\n vars.push(`${CSS_VAR_PREFIX}-spacing-${key}: ${value}`);\n }\n\n // Radius\n for (const [key, value] of Object.entries(theme.radius)) {\n vars.push(`${CSS_VAR_PREFIX}-radius-${key}: ${value}`);\n }\n\n // Shadows\n for (const [key, value] of Object.entries(theme.shadows)) {\n vars.push(`${CSS_VAR_PREFIX}-shadow-${key}: ${value}`);\n }\n\n // Chart colors\n theme.chart.colors.forEach((color, index) => {\n vars.push(`${CSS_VAR_PREFIX}-chart-color-${index + 1}: ${color}`);\n });\n vars.push(`${CSS_VAR_PREFIX}-chart-grid: ${theme.chart.gridColor}`);\n vars.push(`${CSS_VAR_PREFIX}-chart-axis: ${theme.chart.axisColor}`);\n vars.push(`${CSS_VAR_PREFIX}-chart-tooltip-bg: ${theme.chart.tooltipBackground}`);\n\n return vars.join(';\\n') + ';';\n}\n\n/**\n * Convert camelCase to kebab-case.\n */\nfunction kebabCase(str: string): string {\n return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();\n}\n\n/**\n * Detect system color scheme preference.\n */\nfunction getSystemTheme(): 'light' | 'dark' {\n if (typeof window === 'undefined') return 'light';\n return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';\n}\n\n/**\n * Get stored theme mode from localStorage.\n */\nfunction getStoredMode(): ThemeMode | null {\n if (typeof window === 'undefined') return null;\n try {\n const stored = localStorage.getItem(STORAGE_KEY);\n if (stored === 'light' || stored === 'dark' || stored === 'system') {\n return stored;\n }\n } catch {\n // Ignore localStorage errors\n }\n return null;\n}\n\n/**\n * Store theme mode to localStorage.\n */\nfunction storeMode(mode: ThemeMode): void {\n if (typeof window === 'undefined') return;\n try {\n localStorage.setItem(STORAGE_KEY, mode);\n } catch {\n // Ignore localStorage errors\n }\n}\n\n// ============================================================================\n// Provider Props\n// ============================================================================\n\nexport interface ThemeProviderProps {\n /** Child components. */\n children: ReactNode;\n /** Initial theme mode. If not set, uses stored mode or system preference. */\n defaultMode?: ThemeMode;\n /** Custom theme overrides for light mode. */\n lightTheme?: DeepPartial<PrismiqTheme>;\n /** Custom theme overrides for dark mode. */\n darkTheme?: DeepPartial<PrismiqTheme>;\n /** Custom class name for the wrapper element. */\n className?: string;\n}\n\n// ============================================================================\n// Provider Component\n// ============================================================================\n\n/**\n * Theme provider that manages theme mode and injects CSS variables.\n *\n * @example\n * ```tsx\n * function App() {\n * return (\n * <ThemeProvider defaultMode=\"system\">\n * <Dashboard />\n * </ThemeProvider>\n * );\n * }\n * ```\n */\nexport function ThemeProvider({\n children,\n defaultMode,\n lightTheme: customLightTheme,\n darkTheme: customDarkTheme,\n className,\n}: ThemeProviderProps): JSX.Element {\n // Initialize mode from stored value, prop, or default to system\n const [mode, setModeState] = useState<ThemeMode>(() => {\n const stored = getStoredMode();\n return stored ?? defaultMode ?? 'system';\n });\n\n // Track system theme preference\n const [systemTheme, setSystemTheme] = useState<'light' | 'dark'>(getSystemTheme);\n\n // Compute resolved mode\n const resolvedMode = mode === 'system' ? systemTheme : mode;\n\n // Build merged themes\n const mergedLightTheme = useMemo(\n () => (customLightTheme ? deepMerge(lightTheme, customLightTheme) : lightTheme),\n [customLightTheme]\n );\n\n const mergedDarkTheme = useMemo(\n () => (customDarkTheme ? deepMerge(darkTheme, customDarkTheme) : darkTheme),\n [customDarkTheme]\n );\n\n // Get current theme based on resolved mode\n const theme = resolvedMode === 'dark' ? mergedDarkTheme : mergedLightTheme;\n\n // Set mode and persist to localStorage\n const setMode = useCallback((newMode: ThemeMode) => {\n setModeState(newMode);\n storeMode(newMode);\n }, []);\n\n // Listen for system theme changes\n useEffect(() => {\n if (typeof window === 'undefined') return;\n\n const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');\n\n const handleChange = (e: MediaQueryListEvent) => {\n setSystemTheme(e.matches ? 'dark' : 'light');\n };\n\n // Modern browsers\n if (mediaQuery.addEventListener) {\n mediaQuery.addEventListener('change', handleChange);\n return () => mediaQuery.removeEventListener('change', handleChange);\n }\n\n // Legacy browsers\n mediaQuery.addListener(handleChange);\n return () => mediaQuery.removeListener(handleChange);\n }, []);\n\n // Inject CSS variables\n useEffect(() => {\n if (typeof document === 'undefined') return;\n\n const styleId = 'prismiq-theme-vars';\n let styleElement = document.getElementById(styleId) as HTMLStyleElement | null;\n\n if (!styleElement) {\n styleElement = document.createElement('style');\n styleElement.id = styleId;\n document.head.appendChild(styleElement);\n }\n\n const cssVars = themeToCSSVariables(theme);\n styleElement.textContent = `:root {\\n${cssVars}\\n}`;\n\n return () => {\n // Don't remove on cleanup - another instance may need it\n };\n }, [theme]);\n\n // Memoize context value\n const contextValue = useMemo<ThemeContextValue>(\n () => ({\n theme,\n mode,\n setMode,\n resolvedMode,\n }),\n [theme, mode, setMode, resolvedMode]\n );\n\n return (\n <ThemeContext.Provider value={contextValue}>\n <div\n className={className}\n data-prismiq-theme={resolvedMode}\n style={{\n fontFamily: 'var(--prismiq-font-sans)',\n fontSize: 'var(--prismiq-font-size-base)',\n color: 'var(--prismiq-color-text)',\n backgroundColor: 'var(--prismiq-color-background)',\n }}\n >\n {children}\n </div>\n </ThemeContext.Provider>\n );\n}\n","/**\n * useTheme hook.\n *\n * Provides access to the current theme and mode.\n */\n\nimport { useContext } from 'react';\n\nimport { ThemeContext } from './ThemeProvider';\nimport type { ThemeContextValue } from './types';\n\n/**\n * Hook to access the theme context.\n *\n * Must be used within a ThemeProvider.\n *\n * @throws Error if used outside of ThemeProvider.\n *\n * @example\n * ```tsx\n * function ThemeToggle() {\n * const { mode, setMode, resolvedMode } = useTheme();\n *\n * return (\n * <button onClick={() => setMode(mode === 'dark' ? 'light' : 'dark')}>\n * Current: {resolvedMode}\n * </button>\n * );\n * }\n * ```\n */\nexport function useTheme(): ThemeContextValue {\n const context = useContext(ThemeContext);\n\n if (context === null) {\n throw new Error(\n 'useTheme must be used within a ThemeProvider. ' +\n 'Wrap your component tree with <ThemeProvider>.'\n );\n }\n\n return context;\n}\n"]}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
|
|
5
|
+
// src/ssr/index.ts
|
|
6
|
+
function useIsClient() {
|
|
7
|
+
const [isClient, setIsClient] = react.useState(false);
|
|
8
|
+
react.useEffect(() => {
|
|
9
|
+
setIsClient(true);
|
|
10
|
+
}, []);
|
|
11
|
+
return isClient;
|
|
12
|
+
}
|
|
13
|
+
function ClientOnly({ children, fallback = null }) {
|
|
14
|
+
const isClient = useIsClient();
|
|
15
|
+
if (!isClient) {
|
|
16
|
+
return fallback;
|
|
17
|
+
}
|
|
18
|
+
return children;
|
|
19
|
+
}
|
|
20
|
+
function getWindowWidth(defaultWidth = 1200) {
|
|
21
|
+
if (typeof window === "undefined") {
|
|
22
|
+
return defaultWidth;
|
|
23
|
+
}
|
|
24
|
+
return window.innerWidth;
|
|
25
|
+
}
|
|
26
|
+
function getWindowHeight(defaultHeight = 800) {
|
|
27
|
+
if (typeof window === "undefined") {
|
|
28
|
+
return defaultHeight;
|
|
29
|
+
}
|
|
30
|
+
return window.innerHeight;
|
|
31
|
+
}
|
|
32
|
+
function isBrowser() {
|
|
33
|
+
return typeof window !== "undefined" && typeof document !== "undefined";
|
|
34
|
+
}
|
|
35
|
+
function isServer() {
|
|
36
|
+
return !isBrowser();
|
|
37
|
+
}
|
|
38
|
+
function getLocalStorage(key, defaultValue) {
|
|
39
|
+
if (typeof window === "undefined") {
|
|
40
|
+
return defaultValue;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const stored = localStorage.getItem(key);
|
|
44
|
+
if (stored === null) {
|
|
45
|
+
return defaultValue;
|
|
46
|
+
}
|
|
47
|
+
return JSON.parse(stored);
|
|
48
|
+
} catch {
|
|
49
|
+
return defaultValue;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function setLocalStorage(key, value) {
|
|
53
|
+
if (typeof window === "undefined") {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
localStorage.setItem(key, JSON.stringify(value));
|
|
58
|
+
} catch {
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function removeLocalStorage(key) {
|
|
62
|
+
if (typeof window === "undefined") {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
localStorage.removeItem(key);
|
|
67
|
+
} catch {
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function useWindowSize(defaultSize = { width: 1200, height: 800 }) {
|
|
71
|
+
const [size, setSize] = react.useState(defaultSize);
|
|
72
|
+
react.useEffect(() => {
|
|
73
|
+
setSize({
|
|
74
|
+
width: window.innerWidth,
|
|
75
|
+
height: window.innerHeight
|
|
76
|
+
});
|
|
77
|
+
const handleResize = () => {
|
|
78
|
+
setSize({
|
|
79
|
+
width: window.innerWidth,
|
|
80
|
+
height: window.innerHeight
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
window.addEventListener("resize", handleResize);
|
|
84
|
+
return () => window.removeEventListener("resize", handleResize);
|
|
85
|
+
}, []);
|
|
86
|
+
return size;
|
|
87
|
+
}
|
|
88
|
+
function useMediaQuery(query, defaultValue = false) {
|
|
89
|
+
const [matches, setMatches] = react.useState(defaultValue);
|
|
90
|
+
react.useEffect(() => {
|
|
91
|
+
const mediaQuery = window.matchMedia(query);
|
|
92
|
+
setMatches(mediaQuery.matches);
|
|
93
|
+
const handleChange = (event) => {
|
|
94
|
+
setMatches(event.matches);
|
|
95
|
+
};
|
|
96
|
+
if (mediaQuery.addEventListener) {
|
|
97
|
+
mediaQuery.addEventListener("change", handleChange);
|
|
98
|
+
return () => mediaQuery.removeEventListener("change", handleChange);
|
|
99
|
+
}
|
|
100
|
+
mediaQuery.addListener(handleChange);
|
|
101
|
+
return () => mediaQuery.removeListener(handleChange);
|
|
102
|
+
}, [query]);
|
|
103
|
+
return matches;
|
|
104
|
+
}
|
|
105
|
+
var BREAKPOINTS = {
|
|
106
|
+
xs: 0,
|
|
107
|
+
sm: 640,
|
|
108
|
+
md: 768,
|
|
109
|
+
lg: 1024,
|
|
110
|
+
xl: 1280,
|
|
111
|
+
"2xl": 1536
|
|
112
|
+
};
|
|
113
|
+
function useBreakpoint(defaultBreakpoint = "lg") {
|
|
114
|
+
const { width } = useWindowSize({ width: BREAKPOINTS[defaultBreakpoint], height: 800 });
|
|
115
|
+
if (width >= BREAKPOINTS["2xl"]) return "2xl";
|
|
116
|
+
if (width >= BREAKPOINTS.xl) return "xl";
|
|
117
|
+
if (width >= BREAKPOINTS.lg) return "lg";
|
|
118
|
+
if (width >= BREAKPOINTS.md) return "md";
|
|
119
|
+
if (width >= BREAKPOINTS.sm) return "sm";
|
|
120
|
+
return "xs";
|
|
121
|
+
}
|
|
122
|
+
function useIsBreakpoint(breakpoint, defaultValue = true) {
|
|
123
|
+
return useMediaQuery(`(min-width: ${BREAKPOINTS[breakpoint]}px)`, defaultValue);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
exports.BREAKPOINTS = BREAKPOINTS;
|
|
127
|
+
exports.ClientOnly = ClientOnly;
|
|
128
|
+
exports.getLocalStorage = getLocalStorage;
|
|
129
|
+
exports.getWindowHeight = getWindowHeight;
|
|
130
|
+
exports.getWindowWidth = getWindowWidth;
|
|
131
|
+
exports.isBrowser = isBrowser;
|
|
132
|
+
exports.isServer = isServer;
|
|
133
|
+
exports.removeLocalStorage = removeLocalStorage;
|
|
134
|
+
exports.setLocalStorage = setLocalStorage;
|
|
135
|
+
exports.useBreakpoint = useBreakpoint;
|
|
136
|
+
exports.useIsBreakpoint = useIsBreakpoint;
|
|
137
|
+
exports.useIsClient = useIsClient;
|
|
138
|
+
exports.useMediaQuery = useMediaQuery;
|
|
139
|
+
exports.useWindowSize = useWindowSize;
|
|
140
|
+
//# sourceMappingURL=chunk-TRW7DKLP.cjs.map
|
|
141
|
+
//# sourceMappingURL=chunk-TRW7DKLP.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ssr/index.ts"],"names":["useState","useEffect"],"mappings":"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","file":"chunk-TRW7DKLP.cjs","sourcesContent":["'use client';\n\n/**\n * SSR (Server-Side Rendering) utilities for Next.js compatibility.\n *\n * These utilities help components work correctly with:\n * - Next.js App Router\n * - React Server Components\n * - Hydration\n *\n * @example\n * ```tsx\n * import { useIsClient, ClientOnly } from '@prismiq/react/ssr';\n *\n * function MyComponent() {\n * const isClient = useIsClient();\n *\n * if (!isClient) {\n * return <Skeleton />;\n * }\n *\n * return <Chart data={data} />;\n * }\n *\n * // Or use ClientOnly wrapper\n * <ClientOnly fallback={<Skeleton />}>\n * <Chart data={data} />\n * </ClientOnly>\n * ```\n */\n\nimport { useEffect, useState, type ReactNode } from 'react';\n\n// ============================================================================\n// useIsClient Hook\n// ============================================================================\n\n/**\n * Hook that returns true when running in the browser.\n *\n * Useful for:\n * - Conditionally rendering browser-only components\n * - Avoiding hydration mismatches\n * - Waiting for window/document access\n *\n * @returns true when running in browser, false during SSR\n *\n * @example\n * ```tsx\n * function WindowSize() {\n * const isClient = useIsClient();\n *\n * if (!isClient) {\n * return <span>Loading...</span>;\n * }\n *\n * return <span>{window.innerWidth}px</span>;\n * }\n * ```\n */\nexport function useIsClient(): boolean {\n const [isClient, setIsClient] = useState(false);\n\n useEffect(() => {\n setIsClient(true);\n }, []);\n\n return isClient;\n}\n\n// ============================================================================\n// ClientOnly Component\n// ============================================================================\n\nexport interface ClientOnlyProps {\n /** Content to render on the client. */\n children: ReactNode;\n /** Fallback content to show during SSR. */\n fallback?: ReactNode;\n}\n\n/**\n * Component that only renders its children on the client.\n *\n * Use this to wrap components that:\n * - Access window or document\n * - Use browser-only APIs\n * - Would cause hydration mismatches\n *\n * @example\n * ```tsx\n * <ClientOnly fallback={<SkeletonChart type=\"bar\" />}>\n * <BarChart data={data} />\n * </ClientOnly>\n * ```\n */\nexport function ClientOnly({ children, fallback = null }: ClientOnlyProps): ReactNode {\n const isClient = useIsClient();\n\n if (!isClient) {\n return fallback;\n }\n\n return children;\n}\n\n// ============================================================================\n// Safe Window/Document Access\n// ============================================================================\n\n/**\n * Safely get window width (returns default during SSR).\n *\n * @param defaultWidth - Default width to return during SSR\n * @returns Current window width or default\n */\nexport function getWindowWidth(defaultWidth = 1200): number {\n if (typeof window === 'undefined') {\n return defaultWidth;\n }\n return window.innerWidth;\n}\n\n/**\n * Safely get window height (returns default during SSR).\n *\n * @param defaultHeight - Default height to return during SSR\n * @returns Current window height or default\n */\nexport function getWindowHeight(defaultHeight = 800): number {\n if (typeof window === 'undefined') {\n return defaultHeight;\n }\n return window.innerHeight;\n}\n\n/**\n * Check if code is running in browser environment.\n *\n * @returns true if running in browser\n */\nexport function isBrowser(): boolean {\n return typeof window !== 'undefined' && typeof document !== 'undefined';\n}\n\n/**\n * Check if code is running in server environment.\n *\n * @returns true if running on server\n */\nexport function isServer(): boolean {\n return !isBrowser();\n}\n\n// ============================================================================\n// Hydration-Safe Storage\n// ============================================================================\n\n/**\n * Safely get a value from localStorage.\n *\n * @param key - Storage key\n * @param defaultValue - Default value if key doesn't exist or during SSR\n * @returns Stored value or default\n */\nexport function getLocalStorage<T>(key: string, defaultValue: T): T {\n if (typeof window === 'undefined') {\n return defaultValue;\n }\n\n try {\n const stored = localStorage.getItem(key);\n if (stored === null) {\n return defaultValue;\n }\n return JSON.parse(stored) as T;\n } catch {\n return defaultValue;\n }\n}\n\n/**\n * Safely set a value in localStorage.\n *\n * @param key - Storage key\n * @param value - Value to store\n */\nexport function setLocalStorage<T>(key: string, value: T): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n try {\n localStorage.setItem(key, JSON.stringify(value));\n } catch {\n // Storage might be full or disabled\n }\n}\n\n/**\n * Safely remove a value from localStorage.\n *\n * @param key - Storage key to remove\n */\nexport function removeLocalStorage(key: string): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n try {\n localStorage.removeItem(key);\n } catch {\n // Storage might be disabled\n }\n}\n\n// ============================================================================\n// useWindowSize Hook\n// ============================================================================\n\nexport interface WindowSize {\n width: number;\n height: number;\n}\n\n/**\n * Hook to get and track window size with SSR support.\n *\n * @param defaultSize - Default size to use during SSR\n * @returns Current window size\n *\n * @example\n * ```tsx\n * function ResponsiveComponent() {\n * const { width } = useWindowSize();\n *\n * if (width < 768) {\n * return <MobileView />;\n * }\n *\n * return <DesktopView />;\n * }\n * ```\n */\nexport function useWindowSize(\n defaultSize: WindowSize = { width: 1200, height: 800 }\n): WindowSize {\n const [size, setSize] = useState<WindowSize>(defaultSize);\n\n useEffect(() => {\n // Update to actual window size on mount\n setSize({\n width: window.innerWidth,\n height: window.innerHeight,\n });\n\n const handleResize = () => {\n setSize({\n width: window.innerWidth,\n height: window.innerHeight,\n });\n };\n\n window.addEventListener('resize', handleResize);\n return () => window.removeEventListener('resize', handleResize);\n }, []);\n\n return size;\n}\n\n// ============================================================================\n// useMediaQuery Hook\n// ============================================================================\n\n/**\n * Hook to check if a media query matches with SSR support.\n *\n * @param query - CSS media query string\n * @param defaultValue - Default value during SSR\n * @returns Whether the media query matches\n *\n * @example\n * ```tsx\n * function ResponsiveComponent() {\n * const isMobile = useMediaQuery('(max-width: 768px)');\n * const prefersDark = useMediaQuery('(prefers-color-scheme: dark)');\n *\n * return isMobile ? <MobileView /> : <DesktopView />;\n * }\n * ```\n */\nexport function useMediaQuery(query: string, defaultValue = false): boolean {\n const [matches, setMatches] = useState(defaultValue);\n\n useEffect(() => {\n const mediaQuery = window.matchMedia(query);\n setMatches(mediaQuery.matches);\n\n const handleChange = (event: MediaQueryListEvent) => {\n setMatches(event.matches);\n };\n\n // Modern browsers\n if (mediaQuery.addEventListener) {\n mediaQuery.addEventListener('change', handleChange);\n return () => mediaQuery.removeEventListener('change', handleChange);\n }\n\n // Legacy browsers\n mediaQuery.addListener(handleChange);\n return () => mediaQuery.removeListener(handleChange);\n }, [query]);\n\n return matches;\n}\n\n// ============================================================================\n// Breakpoint Hooks\n// ============================================================================\n\n/**\n * Default breakpoints matching common design systems.\n */\nexport const BREAKPOINTS = {\n xs: 0,\n sm: 640,\n md: 768,\n lg: 1024,\n xl: 1280,\n '2xl': 1536,\n} as const;\n\nexport type Breakpoint = keyof typeof BREAKPOINTS;\n\n/**\n * Hook to get current breakpoint with SSR support.\n *\n * @param defaultBreakpoint - Default breakpoint during SSR\n * @returns Current breakpoint\n *\n * @example\n * ```tsx\n * function ResponsiveComponent() {\n * const breakpoint = useBreakpoint();\n *\n * const columns = {\n * xs: 1,\n * sm: 2,\n * md: 3,\n * lg: 4,\n * xl: 6,\n * '2xl': 6,\n * }[breakpoint];\n *\n * return <Grid columns={columns} />;\n * }\n * ```\n */\nexport function useBreakpoint(defaultBreakpoint: Breakpoint = 'lg'): Breakpoint {\n const { width } = useWindowSize({ width: BREAKPOINTS[defaultBreakpoint], height: 800 });\n\n if (width >= BREAKPOINTS['2xl']) return '2xl';\n if (width >= BREAKPOINTS.xl) return 'xl';\n if (width >= BREAKPOINTS.lg) return 'lg';\n if (width >= BREAKPOINTS.md) return 'md';\n if (width >= BREAKPOINTS.sm) return 'sm';\n return 'xs';\n}\n\n/**\n * Hook to check if current width is at or above a breakpoint.\n *\n * @param breakpoint - Breakpoint to check\n * @param defaultValue - Default value during SSR\n * @returns Whether width is at or above breakpoint\n */\nexport function useIsBreakpoint(breakpoint: Breakpoint, defaultValue = true): boolean {\n return useMediaQuery(`(min-width: ${BREAKPOINTS[breakpoint]}px)`, defaultValue);\n}\n"]}
|