@pol-studios/ui 1.0.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.
@@ -0,0 +1,345 @@
1
+ "use client";
2
+
3
+ // src/hooks/useKeyboardShortcuts.ts
4
+ import { useEffect, useCallback, useRef } from "react";
5
+ function useKeyboardShortcuts({ shortcuts, enabled = true }) {
6
+ const shortcutsRef = useRef(shortcuts);
7
+ useEffect(() => {
8
+ shortcutsRef.current = shortcuts;
9
+ }, [shortcuts]);
10
+ const handleKeyDown = useCallback(
11
+ (event) => {
12
+ if (!enabled) return;
13
+ const target = event.target;
14
+ if (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable || target.tagName === "SELECT" && !event.ctrlKey && !event.metaKey) {
15
+ if (!event.ctrlKey && !event.metaKey && !event.altKey) {
16
+ return;
17
+ }
18
+ }
19
+ for (const shortcut of shortcutsRef.current) {
20
+ if (shortcut.enabled === false) continue;
21
+ const keyMatches = shortcut.key.toLowerCase() === event.key.toLowerCase();
22
+ const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0;
23
+ const hasCtrlOrCmd = event.ctrlKey || event.metaKey;
24
+ let ctrlMatches = true;
25
+ if (shortcut.ctrl !== void 0) {
26
+ ctrlMatches = shortcut.ctrl === (isMac ? event.metaKey : event.ctrlKey);
27
+ }
28
+ let metaMatches = true;
29
+ if (shortcut.meta !== void 0) {
30
+ metaMatches = shortcut.meta === event.metaKey;
31
+ }
32
+ if (shortcut.ctrl !== void 0 && shortcut.meta === void 0 && event.metaKey && !isMac) {
33
+ ctrlMatches = false;
34
+ }
35
+ if (shortcut.meta !== void 0 && shortcut.ctrl === void 0 && event.ctrlKey) {
36
+ metaMatches = false;
37
+ }
38
+ const shiftMatches = shortcut.shift === void 0 ? true : shortcut.shift === event.shiftKey;
39
+ const altMatches = shortcut.alt === void 0 ? true : shortcut.alt === event.altKey;
40
+ if (keyMatches && ctrlMatches && metaMatches && shiftMatches && altMatches) {
41
+ if (shortcut.preventDefault !== false) {
42
+ event.preventDefault();
43
+ }
44
+ shortcut.action();
45
+ break;
46
+ }
47
+ }
48
+ },
49
+ [enabled]
50
+ );
51
+ useEffect(() => {
52
+ if (!enabled) return;
53
+ window.addEventListener("keydown", handleKeyDown);
54
+ return () => {
55
+ window.removeEventListener("keydown", handleKeyDown);
56
+ };
57
+ }, [handleKeyDown, enabled]);
58
+ }
59
+ function formatShortcut(shortcut) {
60
+ const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0;
61
+ const parts = [];
62
+ if (shortcut.ctrl || shortcut.meta) {
63
+ parts.push(isMac ? "\u2318" : "Ctrl");
64
+ }
65
+ if (shortcut.alt) {
66
+ parts.push(isMac ? "\u2325" : "Alt");
67
+ }
68
+ if (shortcut.shift) {
69
+ parts.push(isMac ? "\u21E7" : "Shift");
70
+ }
71
+ let key = shortcut.key;
72
+ if (key.length === 1) {
73
+ key = key.toUpperCase();
74
+ } else {
75
+ key = key.charAt(0).toUpperCase() + key.slice(1).toLowerCase();
76
+ }
77
+ parts.push(key);
78
+ return parts.join(isMac ? "" : "+");
79
+ }
80
+
81
+ // src/hooks/useMultistepForm.tsx
82
+ import { useState } from "react";
83
+ function useMultistepForm(steps) {
84
+ const [currentStepIndex, setCurrentStepIndex] = useState(0);
85
+ function goNext() {
86
+ setCurrentStepIndex((x) => {
87
+ if (x >= steps.length - 1) return x;
88
+ return x + 1;
89
+ });
90
+ }
91
+ function goBack() {
92
+ setCurrentStepIndex((x) => {
93
+ if (x === 0) return x;
94
+ return x - 1;
95
+ });
96
+ }
97
+ function goTo(index) {
98
+ setCurrentStepIndex(index);
99
+ }
100
+ return {
101
+ currentStepIndex,
102
+ currentStep: steps[currentStepIndex],
103
+ steps,
104
+ goTo,
105
+ goNext,
106
+ goBack
107
+ };
108
+ }
109
+
110
+ // src/hooks/usePOLNavigate.tsx
111
+ import { useContext as useContext2 } from "react";
112
+ import { useNavigate as useTanstackNavigate } from "@tanstack/react-router";
113
+
114
+ // src/providers/router/RouterContext.tsx
115
+ import { createContext, useContext } from "react";
116
+ import { jsx } from "react/jsx-runtime";
117
+ var RouterContext = createContext(null);
118
+
119
+ // src/hooks/usePOLNavigate.tsx
120
+ function usePolNavigate() {
121
+ const routerContext = useContext2(RouterContext);
122
+ let tanstackNavigate = null;
123
+ try {
124
+ tanstackNavigate = useTanstackNavigate();
125
+ } catch {
126
+ }
127
+ if (tanstackNavigate) {
128
+ return tanstackNavigate;
129
+ }
130
+ if (routerContext) {
131
+ return (options) => {
132
+ const searchValue = typeof options.search === "object" && options.search !== null ? options.search : void 0;
133
+ const adapterOptions = {
134
+ to: typeof options.to === "string" ? options.to : String(options.to),
135
+ params: options.params,
136
+ search: searchValue,
137
+ replace: options.replace
138
+ };
139
+ routerContext.navigate(adapterOptions);
140
+ };
141
+ }
142
+ throw new Error("usePOLNavigate requires either a RouterProvider or TanStack Router context");
143
+ }
144
+
145
+ // src/hooks/use-mobile.tsx
146
+ import * as React2 from "react";
147
+ var MOBILE_BREAKPOINT = 768;
148
+ function useIsMobile() {
149
+ const [isMobile, setIsMobile] = React2.useState(void 0);
150
+ React2.useEffect(() => {
151
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
152
+ const onChange = () => {
153
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
154
+ };
155
+ mql.addEventListener("change", onChange);
156
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
157
+ return () => mql.removeEventListener("change", onChange);
158
+ }, []);
159
+ return !!isMobile;
160
+ }
161
+
162
+ // src/hooks/use-auto-height.tsx
163
+ import * as React3 from "react";
164
+ function useAutoHeight(deps = [], options = {
165
+ includeParentBox: true,
166
+ includeSelfBox: false
167
+ }) {
168
+ const ref = React3.useRef(null);
169
+ const roRef = React3.useRef(null);
170
+ const [height, setHeight] = React3.useState(0);
171
+ const measure = React3.useCallback(() => {
172
+ const el = ref.current;
173
+ if (!el) return 0;
174
+ const base = el.getBoundingClientRect().height || 0;
175
+ let extra = 0;
176
+ if (options.includeParentBox && el.parentElement) {
177
+ const cs = getComputedStyle(el.parentElement);
178
+ const paddingY = (parseFloat(cs.paddingTop || "0") || 0) + (parseFloat(cs.paddingBottom || "0") || 0);
179
+ const borderY = (parseFloat(cs.borderTopWidth || "0") || 0) + (parseFloat(cs.borderBottomWidth || "0") || 0);
180
+ const isBorderBox = cs.boxSizing === "border-box";
181
+ if (isBorderBox) {
182
+ extra += paddingY + borderY;
183
+ }
184
+ }
185
+ if (options.includeSelfBox) {
186
+ const cs = getComputedStyle(el);
187
+ const paddingY = (parseFloat(cs.paddingTop || "0") || 0) + (parseFloat(cs.paddingBottom || "0") || 0);
188
+ const borderY = (parseFloat(cs.borderTopWidth || "0") || 0) + (parseFloat(cs.borderBottomWidth || "0") || 0);
189
+ const isBorderBox = cs.boxSizing === "border-box";
190
+ if (isBorderBox) {
191
+ extra += paddingY + borderY;
192
+ }
193
+ }
194
+ const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
195
+ const total = Math.ceil((base + extra) * dpr) / dpr;
196
+ return total;
197
+ }, [options.includeParentBox, options.includeSelfBox]);
198
+ React3.useLayoutEffect(() => {
199
+ const el = ref.current;
200
+ if (!el) return;
201
+ setHeight(measure());
202
+ if (roRef.current) {
203
+ roRef.current.disconnect();
204
+ roRef.current = null;
205
+ }
206
+ const ro = new ResizeObserver(() => {
207
+ const next = measure();
208
+ requestAnimationFrame(() => setHeight(next));
209
+ });
210
+ ro.observe(el);
211
+ if (options.includeParentBox && el.parentElement) {
212
+ ro.observe(el.parentElement);
213
+ }
214
+ roRef.current = ro;
215
+ return () => {
216
+ ro.disconnect();
217
+ roRef.current = null;
218
+ };
219
+ }, deps);
220
+ React3.useLayoutEffect(() => {
221
+ if (height === 0) {
222
+ const next = measure();
223
+ if (next !== 0) setHeight(next);
224
+ }
225
+ }, [height, measure]);
226
+ return { ref, height };
227
+ }
228
+
229
+ // src/hooks/use-controlled-state.tsx
230
+ import * as React4 from "react";
231
+ function useControlledState(props) {
232
+ const { value, defaultValue, onChange } = props;
233
+ const [state, setInternalState] = React4.useState(
234
+ value !== void 0 ? value : defaultValue
235
+ );
236
+ React4.useEffect(() => {
237
+ if (value !== void 0) setInternalState(value);
238
+ }, [value]);
239
+ const setState = React4.useCallback(
240
+ (next, ...args) => {
241
+ setInternalState(next);
242
+ onChange?.(next, ...args);
243
+ },
244
+ [onChange]
245
+ );
246
+ return [state, setState];
247
+ }
248
+
249
+ // src/hooks/use-data-state.tsx
250
+ import * as React5 from "react";
251
+ function parseDatasetValue(value) {
252
+ if (value === null) return null;
253
+ if (value === "" || value === "true") return true;
254
+ if (value === "false") return false;
255
+ return value;
256
+ }
257
+ function useDataState(key, forwardedRef, onChange) {
258
+ const localRef = React5.useRef(null);
259
+ React5.useImperativeHandle(forwardedRef, () => localRef.current);
260
+ const getSnapshot = () => {
261
+ const el = localRef.current;
262
+ return el ? parseDatasetValue(el.getAttribute(`data-${key}`)) : null;
263
+ };
264
+ const subscribe = (callback) => {
265
+ const el = localRef.current;
266
+ if (!el) return () => {
267
+ };
268
+ const observer = new MutationObserver((records) => {
269
+ for (const record of records) {
270
+ if (record.attributeName === `data-${key}`) {
271
+ callback();
272
+ break;
273
+ }
274
+ }
275
+ });
276
+ observer.observe(el, {
277
+ attributes: true,
278
+ attributeFilter: [`data-${key}`]
279
+ });
280
+ return () => observer.disconnect();
281
+ };
282
+ const value = React5.useSyncExternalStore(subscribe, getSnapshot);
283
+ React5.useEffect(() => {
284
+ if (onChange) onChange(value);
285
+ }, [value, onChange]);
286
+ return [value, localRef];
287
+ }
288
+
289
+ // src/hooks/use-is-in-view.tsx
290
+ import * as React6 from "react";
291
+ import { useInView } from "motion/react";
292
+ function useIsInView(ref, options = {}) {
293
+ const { inView, inViewOnce = false, inViewMargin = "0px" } = options;
294
+ const localRef = React6.useRef(null);
295
+ React6.useImperativeHandle(ref, () => localRef.current);
296
+ const inViewResult = useInView(localRef, {
297
+ once: inViewOnce,
298
+ margin: inViewMargin
299
+ });
300
+ const isInView = !inView || inViewResult;
301
+ return { ref: localRef, isInView };
302
+ }
303
+
304
+ // src/providers/alert/AlertContext.tsx
305
+ import { createContext as createContext2, useContext as useContext3, useState as useState5 } from "react";
306
+ import { jsx as jsx2 } from "react/jsx-runtime";
307
+ var defaultAlertState = {
308
+ isOpen: false,
309
+ title: "",
310
+ description: "",
311
+ confirmTitle: "Confirm",
312
+ cancelTitle: "Cancel",
313
+ variant: "default",
314
+ onConfirm: () => {
315
+ },
316
+ onCancel: () => {
317
+ }
318
+ };
319
+ var defaultContextValue = {
320
+ showAlert: async () => false,
321
+ hideAlert: () => {
322
+ },
323
+ alertState: defaultAlertState
324
+ };
325
+ var AlertContext = createContext2(defaultContextValue);
326
+ var useAlert = () => {
327
+ const context = useContext3(AlertContext);
328
+ if (!context) {
329
+ throw new Error("useAlert must be used within an AlertProvider");
330
+ }
331
+ return { hideAlert: context.hideAlert, showAlert: context.showAlert };
332
+ };
333
+ export {
334
+ formatShortcut,
335
+ useAlert,
336
+ useAutoHeight,
337
+ useControlledState,
338
+ useDataState,
339
+ useIsInView,
340
+ useIsMobile,
341
+ useKeyboardShortcuts,
342
+ useMultistepForm,
343
+ usePolNavigate as useNavigate,
344
+ usePolNavigate
345
+ };