@pelatform/starter.ui 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/dist/hooks.js ADDED
@@ -0,0 +1,722 @@
1
+ "use client";
2
+ import {
3
+ useCopyToClipboard,
4
+ useIsMobile
5
+ } from "./chunk-HHNNYCNT.js";
6
+
7
+ // src/hooks/use-body-class.ts
8
+ import { useEffect } from "react";
9
+ var useBodyClasses = (className) => {
10
+ useEffect(() => {
11
+ if (!className.trim()) return;
12
+ const classList = className.split(/\s+/).filter(Boolean);
13
+ classList.forEach((cls) => {
14
+ document.body.classList.add(cls);
15
+ });
16
+ return () => {
17
+ classList.forEach((cls) => {
18
+ document.body.classList.remove(cls);
19
+ });
20
+ };
21
+ }, [className]);
22
+ };
23
+
24
+ // src/hooks/use-file-upload.ts
25
+ import {
26
+ useCallback,
27
+ useRef,
28
+ useState
29
+ } from "react";
30
+ var useFileUpload = (options = {}) => {
31
+ const {
32
+ maxFiles = Number.POSITIVE_INFINITY,
33
+ maxSize = Number.POSITIVE_INFINITY,
34
+ accept = "*",
35
+ multiple = false,
36
+ initialFiles = [],
37
+ onFilesChange,
38
+ onFilesAdded,
39
+ onError
40
+ } = options;
41
+ const [state, setState] = useState({
42
+ files: initialFiles.map((file) => ({
43
+ file,
44
+ id: file.id,
45
+ preview: file.url
46
+ })),
47
+ isDragging: false,
48
+ errors: []
49
+ });
50
+ const inputRef = useRef(null);
51
+ const validateFile = useCallback(
52
+ (file) => {
53
+ if (file instanceof File) {
54
+ if (file.size > maxSize) {
55
+ return `File "${file.name}" exceeds the maximum size of ${formatBytes(maxSize)}.`;
56
+ }
57
+ } else {
58
+ if (file.size > maxSize) {
59
+ return `File "${file.name}" exceeds the maximum size of ${formatBytes(maxSize)}.`;
60
+ }
61
+ }
62
+ if (accept !== "*") {
63
+ const acceptedTypes = accept.split(",").map((type) => type.trim());
64
+ const fileType = file instanceof File ? file.type || "" : file.type;
65
+ const fileExtension = `.${file instanceof File ? file.name.split(".").pop() : file.name.split(".").pop()}`;
66
+ const isAccepted = acceptedTypes.some((type) => {
67
+ if (type.startsWith(".")) {
68
+ return fileExtension.toLowerCase() === type.toLowerCase();
69
+ }
70
+ if (type.endsWith("/*")) {
71
+ const baseType = type.split("/")[0];
72
+ return fileType.startsWith(`${baseType}/`);
73
+ }
74
+ return fileType === type;
75
+ });
76
+ if (!isAccepted) {
77
+ return `File "${file instanceof File ? file.name : file.name}" is not an accepted file type.`;
78
+ }
79
+ }
80
+ return null;
81
+ },
82
+ [accept, maxSize]
83
+ );
84
+ const createPreview = useCallback((file) => {
85
+ if (file instanceof File) {
86
+ return URL.createObjectURL(file);
87
+ }
88
+ return file.url;
89
+ }, []);
90
+ const generateUniqueId = useCallback((file) => {
91
+ if (file instanceof File) {
92
+ return `${file.name}-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
93
+ }
94
+ return file.id;
95
+ }, []);
96
+ const clearFiles = useCallback(() => {
97
+ setState((prev) => {
98
+ for (const file of prev.files) {
99
+ if (file.preview && file.file instanceof File && file.file.type.startsWith("image/")) {
100
+ URL.revokeObjectURL(file.preview);
101
+ }
102
+ }
103
+ if (inputRef.current) {
104
+ inputRef.current.value = "";
105
+ }
106
+ const newState = {
107
+ ...prev,
108
+ files: [],
109
+ errors: []
110
+ };
111
+ onFilesChange?.(newState.files);
112
+ return newState;
113
+ });
114
+ }, [onFilesChange]);
115
+ const addFiles = useCallback(
116
+ (newFiles) => {
117
+ if (!newFiles || newFiles.length === 0) return;
118
+ const newFilesArray = Array.from(newFiles);
119
+ const errors = [];
120
+ setState((prev) => ({ ...prev, errors: [] }));
121
+ if (!multiple) {
122
+ clearFiles();
123
+ }
124
+ if (multiple && maxFiles !== Number.POSITIVE_INFINITY && state.files.length + newFilesArray.length > maxFiles) {
125
+ errors.push(`You can only upload a maximum of ${maxFiles} files.`);
126
+ onError?.(errors);
127
+ setState((prev) => ({ ...prev, errors }));
128
+ return;
129
+ }
130
+ const validFiles = [];
131
+ for (const file of newFilesArray) {
132
+ if (multiple) {
133
+ const isDuplicate = state.files.some(
134
+ (existingFile) => existingFile.file.name === file.name && existingFile.file.size === file.size
135
+ );
136
+ if (isDuplicate) {
137
+ return;
138
+ }
139
+ }
140
+ if (file.size > maxSize) {
141
+ errors.push(
142
+ multiple ? `Some files exceed the maximum size of ${formatBytes(maxSize)}.` : `File exceeds the maximum size of ${formatBytes(maxSize)}.`
143
+ );
144
+ continue;
145
+ }
146
+ const error = validateFile(file);
147
+ if (error) {
148
+ errors.push(error);
149
+ } else {
150
+ validFiles.push({
151
+ file,
152
+ id: generateUniqueId(file),
153
+ preview: createPreview(file)
154
+ });
155
+ }
156
+ }
157
+ if (validFiles.length > 0) {
158
+ onFilesAdded?.(validFiles);
159
+ setState((prev) => {
160
+ const newFiles2 = !multiple ? validFiles : [...prev.files, ...validFiles];
161
+ onFilesChange?.(newFiles2);
162
+ return {
163
+ ...prev,
164
+ files: newFiles2,
165
+ errors
166
+ };
167
+ });
168
+ } else if (errors.length > 0) {
169
+ onError?.(errors);
170
+ setState((prev) => ({
171
+ ...prev,
172
+ errors
173
+ }));
174
+ }
175
+ if (inputRef.current) {
176
+ inputRef.current.value = "";
177
+ }
178
+ },
179
+ [
180
+ state.files,
181
+ maxFiles,
182
+ multiple,
183
+ maxSize,
184
+ validateFile,
185
+ createPreview,
186
+ generateUniqueId,
187
+ clearFiles,
188
+ onFilesChange,
189
+ onFilesAdded,
190
+ onError
191
+ ]
192
+ );
193
+ const removeFile = useCallback(
194
+ (id) => {
195
+ setState((prev) => {
196
+ const fileToRemove = prev.files.find((file) => file.id === id);
197
+ if (fileToRemove?.preview && fileToRemove.file instanceof File && fileToRemove.file.type.startsWith("image/")) {
198
+ URL.revokeObjectURL(fileToRemove.preview);
199
+ }
200
+ const newFiles = prev.files.filter((file) => file.id !== id);
201
+ onFilesChange?.(newFiles);
202
+ return {
203
+ ...prev,
204
+ files: newFiles,
205
+ errors: []
206
+ };
207
+ });
208
+ },
209
+ [onFilesChange]
210
+ );
211
+ const clearErrors = useCallback(() => {
212
+ setState((prev) => ({
213
+ ...prev,
214
+ errors: []
215
+ }));
216
+ }, []);
217
+ const handleDragEnter = useCallback((e) => {
218
+ e.preventDefault();
219
+ e.stopPropagation();
220
+ setState((prev) => ({ ...prev, isDragging: true }));
221
+ }, []);
222
+ const handleDragLeave = useCallback((e) => {
223
+ e.preventDefault();
224
+ e.stopPropagation();
225
+ if (e.currentTarget.contains(e.relatedTarget)) {
226
+ return;
227
+ }
228
+ setState((prev) => ({ ...prev, isDragging: false }));
229
+ }, []);
230
+ const handleDragOver = useCallback((e) => {
231
+ e.preventDefault();
232
+ e.stopPropagation();
233
+ }, []);
234
+ const handleDrop = useCallback(
235
+ (e) => {
236
+ e.preventDefault();
237
+ e.stopPropagation();
238
+ setState((prev) => ({ ...prev, isDragging: false }));
239
+ if (inputRef.current?.disabled) {
240
+ return;
241
+ }
242
+ if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
243
+ if (!multiple) {
244
+ const file = e.dataTransfer.files[0];
245
+ addFiles([file]);
246
+ } else {
247
+ addFiles(e.dataTransfer.files);
248
+ }
249
+ }
250
+ },
251
+ [addFiles, multiple]
252
+ );
253
+ const handleFileChange = useCallback(
254
+ (e) => {
255
+ if (e.target.files && e.target.files.length > 0) {
256
+ addFiles(e.target.files);
257
+ }
258
+ },
259
+ [addFiles]
260
+ );
261
+ const openFileDialog = useCallback(() => {
262
+ if (inputRef.current) {
263
+ inputRef.current.click();
264
+ }
265
+ }, []);
266
+ const getInputProps = useCallback(
267
+ (props = {}) => {
268
+ return {
269
+ ...props,
270
+ type: "file",
271
+ onChange: handleFileChange,
272
+ accept: props.accept || accept,
273
+ multiple: props.multiple !== void 0 ? props.multiple : multiple,
274
+ ref: inputRef
275
+ };
276
+ },
277
+ [accept, multiple, handleFileChange]
278
+ );
279
+ return [
280
+ state,
281
+ {
282
+ addFiles,
283
+ removeFile,
284
+ clearFiles,
285
+ clearErrors,
286
+ handleDragEnter,
287
+ handleDragLeave,
288
+ handleDragOver,
289
+ handleDrop,
290
+ handleFileChange,
291
+ openFileDialog,
292
+ getInputProps
293
+ }
294
+ ];
295
+ };
296
+ var formatBytes = (bytes, decimals = 2) => {
297
+ if (bytes === 0) return "0 Bytes";
298
+ const k = 1024;
299
+ const dm = decimals < 0 ? 0 : decimals;
300
+ const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
301
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
302
+ return Number.parseFloat((bytes / k ** i).toFixed(dm)) + sizes[i];
303
+ };
304
+
305
+ // src/hooks/use-hydrated.ts
306
+ import { useSyncExternalStore } from "react";
307
+ function useHydrated() {
308
+ return useSyncExternalStore(
309
+ subscribe,
310
+ () => true,
311
+ () => false
312
+ );
313
+ }
314
+ function subscribe() {
315
+ return () => {
316
+ };
317
+ }
318
+
319
+ // src/hooks/use-intersection-observer.ts
320
+ import * as React from "react";
321
+ var SharedObserver = class {
322
+ observer = null;
323
+ callbacks = /* @__PURE__ */ new Map();
324
+ options;
325
+ constructor(options) {
326
+ this.options = options;
327
+ }
328
+ getObserver() {
329
+ if (!this.observer) {
330
+ this.observer = new IntersectionObserver((entries) => {
331
+ entries.forEach((entry) => {
332
+ const callback = this.callbacks.get(entry.target);
333
+ if (callback) {
334
+ callback(entry.isIntersecting);
335
+ }
336
+ });
337
+ }, this.options);
338
+ }
339
+ return this.observer;
340
+ }
341
+ observe(element, callback) {
342
+ this.callbacks.set(element, callback);
343
+ this.getObserver().observe(element);
344
+ }
345
+ unobserve(element) {
346
+ this.callbacks.delete(element);
347
+ this.observer?.unobserve(element);
348
+ }
349
+ disconnect() {
350
+ this.observer?.disconnect();
351
+ this.callbacks.clear();
352
+ this.observer = null;
353
+ }
354
+ };
355
+ var observers = /* @__PURE__ */ new Map();
356
+ function getSharedObserver(options) {
357
+ const key = JSON.stringify(options);
358
+ if (!observers.has(key)) {
359
+ observers.set(key, new SharedObserver(options));
360
+ }
361
+ return observers.get(key);
362
+ }
363
+ function useIntersectionObserver(elementRef, {
364
+ threshold = 0,
365
+ root = null,
366
+ rootMargin = "0%",
367
+ freezeOnceVisible = false
368
+ } = {}) {
369
+ const [isIntersecting, setIntersecting] = React.useState(false);
370
+ const frozen = React.useRef(false);
371
+ React.useEffect(() => {
372
+ const element = elementRef?.current;
373
+ if (!element || freezeOnceVisible && frozen.current) return;
374
+ const observer = getSharedObserver({ threshold, root, rootMargin });
375
+ observer.observe(element, (intersecting) => {
376
+ setIntersecting(intersecting);
377
+ if (intersecting && freezeOnceVisible) {
378
+ frozen.current = true;
379
+ observer.unobserve(element);
380
+ }
381
+ });
382
+ return () => {
383
+ observer.unobserve(element);
384
+ };
385
+ }, [elementRef, threshold, root, rootMargin, freezeOnceVisible]);
386
+ return isIntersecting;
387
+ }
388
+
389
+ // src/hooks/use-is-mac.ts
390
+ import { useEffect as useEffect3, useState as useState3 } from "react";
391
+ function useIsMac() {
392
+ const [isMac, setIsMac] = useState3(true);
393
+ useEffect3(() => {
394
+ setIsMac(navigator.platform.toUpperCase().includes("MAC"));
395
+ }, []);
396
+ return isMac;
397
+ }
398
+
399
+ // src/hooks/use-media-query.ts
400
+ import { useEffect as useEffect4, useState as useState4 } from "react";
401
+ var getMatches = (query) => {
402
+ if (typeof window !== "undefined") {
403
+ return window.matchMedia(query).matches;
404
+ }
405
+ return false;
406
+ };
407
+ var useMediaQuery = (query) => {
408
+ const [matches, setMatches] = useState4(getMatches(query));
409
+ useEffect4(() => {
410
+ function handleChange() {
411
+ setMatches(getMatches(query));
412
+ }
413
+ const matchMedia = window.matchMedia(query);
414
+ handleChange();
415
+ matchMedia.addEventListener("change", handleChange);
416
+ return () => {
417
+ matchMedia.removeEventListener("change", handleChange);
418
+ };
419
+ }, [query]);
420
+ return matches;
421
+ };
422
+
423
+ // src/hooks/use-menu.ts
424
+ var useMenu = (pathname) => {
425
+ const isActive = (path) => {
426
+ if (path && path === "/") {
427
+ return path === pathname;
428
+ } else {
429
+ return !!path && pathname.startsWith(path);
430
+ }
431
+ };
432
+ const hasActiveChild = (children) => {
433
+ if (!children || !Array.isArray(children)) return false;
434
+ return children.some(
435
+ (child) => child.path && isActive(child.path) || child.children && hasActiveChild(child.children)
436
+ );
437
+ };
438
+ const isItemActive = (item) => {
439
+ return (item.path ? isActive(item.path) : false) || (item.children ? hasActiveChild(item.children) : false);
440
+ };
441
+ const getCurrentItem = (items) => {
442
+ for (const item of items) {
443
+ if (item.path && isActive(item.path)) {
444
+ if (item.children && item.children.length > 0) {
445
+ const childMatch = getCurrentItem(item.children);
446
+ return childMatch || item;
447
+ }
448
+ return item;
449
+ }
450
+ if (item.children && item.children.length > 0) {
451
+ const childMatch = getCurrentItem(item.children);
452
+ if (childMatch) {
453
+ return childMatch;
454
+ }
455
+ }
456
+ }
457
+ return void 0;
458
+ };
459
+ const getBreadcrumb = (items) => {
460
+ const findBreadcrumb = (nodes, breadcrumb2 = []) => {
461
+ for (const item of nodes) {
462
+ const currentBreadcrumb = [...breadcrumb2, item];
463
+ if (item.path && isActive(item.path)) {
464
+ return currentBreadcrumb;
465
+ }
466
+ if (item.children && item.children.length > 0) {
467
+ const childBreadcrumb = findBreadcrumb(item.children, currentBreadcrumb);
468
+ if (childBreadcrumb.length > currentBreadcrumb.length) {
469
+ return childBreadcrumb;
470
+ }
471
+ }
472
+ }
473
+ return breadcrumb2;
474
+ };
475
+ const breadcrumb = findBreadcrumb(items);
476
+ return breadcrumb.length > 0 ? breadcrumb : [];
477
+ };
478
+ const getChildren = (items, level) => {
479
+ const hasActiveChildAtLevel = (items2) => {
480
+ for (const item of items2) {
481
+ if (item.path && (item.path === pathname || item.path !== "/" && item.path !== "" && pathname.startsWith(item.path)) || item.children && hasActiveChildAtLevel(item.children)) {
482
+ return true;
483
+ }
484
+ }
485
+ return false;
486
+ };
487
+ const findChildren = (items2, targetLevel, currentLevel = 0) => {
488
+ for (const item of items2) {
489
+ if (item.children) {
490
+ if (targetLevel === currentLevel && hasActiveChildAtLevel(item.children)) {
491
+ return item.children;
492
+ }
493
+ const children = findChildren(item.children, targetLevel, currentLevel + 1);
494
+ if (children) {
495
+ return children;
496
+ }
497
+ } else if (targetLevel === currentLevel && item.path && (item.path === pathname || item.path !== "/" && item.path !== "" && pathname.startsWith(item.path))) {
498
+ return items2;
499
+ }
500
+ }
501
+ return null;
502
+ };
503
+ return findChildren(items, level);
504
+ };
505
+ return {
506
+ isActive,
507
+ hasActiveChild,
508
+ isItemActive,
509
+ getCurrentItem,
510
+ getBreadcrumb,
511
+ getChildren
512
+ };
513
+ };
514
+
515
+ // src/hooks/use-meta-color.ts
516
+ import * as React2 from "react";
517
+ import { useTheme } from "next-themes";
518
+ import { META_THEME_COLORS, THEME_MODES } from "@pelatform/utils";
519
+ function useMetaColor(defaultColors) {
520
+ const { resolvedTheme } = useTheme();
521
+ const isSystemDarkMode = React2.useCallback(() => {
522
+ if (typeof window === "undefined") return false;
523
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
524
+ }, []);
525
+ const getEffectiveTheme = React2.useCallback(
526
+ (theme) => {
527
+ if (theme === THEME_MODES.SYSTEM) {
528
+ return isSystemDarkMode() ? THEME_MODES.DARK : THEME_MODES.LIGHT;
529
+ }
530
+ return theme;
531
+ },
532
+ [isSystemDarkMode]
533
+ );
534
+ const metaColor = React2.useMemo(() => {
535
+ const colorsToUse = defaultColors ?? META_THEME_COLORS;
536
+ const effectiveTheme = getEffectiveTheme(resolvedTheme);
537
+ if (effectiveTheme === THEME_MODES.DARK) {
538
+ return colorsToUse[THEME_MODES.DARK];
539
+ } else if (effectiveTheme === THEME_MODES.LIGHT) {
540
+ return colorsToUse[THEME_MODES.LIGHT];
541
+ }
542
+ return colorsToUse[THEME_MODES.LIGHT];
543
+ }, [resolvedTheme, defaultColors, getEffectiveTheme]);
544
+ const setMetaColor = React2.useCallback((color) => {
545
+ const metaTag = document.querySelector('meta[name="theme-color"]');
546
+ if (metaTag) {
547
+ metaTag.setAttribute("content", color);
548
+ } else {
549
+ const newMetaTag = document.createElement("meta");
550
+ newMetaTag.name = "theme-color";
551
+ newMetaTag.content = color;
552
+ document.head.appendChild(newMetaTag);
553
+ }
554
+ }, []);
555
+ return {
556
+ /** Current meta theme color based on resolved theme */
557
+ metaColor,
558
+ /** Function to manually set meta theme color */
559
+ setMetaColor
560
+ };
561
+ }
562
+
563
+ // src/hooks/use-mounted.ts
564
+ import * as React3 from "react";
565
+ function useMounted() {
566
+ const [mounted, setMounted] = React3.useState(false);
567
+ React3.useEffect(() => {
568
+ setMounted(true);
569
+ }, []);
570
+ return mounted;
571
+ }
572
+
573
+ // src/hooks/use-mutation-observer.ts
574
+ import * as React4 from "react";
575
+ var DEFAULT_OPTIONS = {
576
+ /** Watch for attribute changes */
577
+ attributes: true,
578
+ /** Watch for text content changes */
579
+ characterData: true,
580
+ /** Watch for child element additions/removals */
581
+ childList: true,
582
+ /** Watch for changes in descendant elements */
583
+ subtree: true
584
+ };
585
+ var useMutationObserver = (ref, callback, options = DEFAULT_OPTIONS) => {
586
+ React4.useEffect(() => {
587
+ if (ref.current) {
588
+ const observer = new MutationObserver(callback);
589
+ observer.observe(ref.current, options);
590
+ return () => {
591
+ observer.disconnect();
592
+ };
593
+ }
594
+ }, [ref, callback, options]);
595
+ };
596
+
597
+ // src/hooks/use-remove-ga-params.ts
598
+ import { useEffect as useEffect7 } from "react";
599
+ function useRemoveGAParams() {
600
+ useEffect7(() => {
601
+ const url = new URL(window.location.href);
602
+ if (url.searchParams.has("_gl")) {
603
+ const timer = setTimeout(() => {
604
+ url.searchParams.delete("_gl");
605
+ window.history.replaceState({}, "", url.toString());
606
+ }, 2e3);
607
+ return () => clearTimeout(timer);
608
+ }
609
+ }, []);
610
+ }
611
+
612
+ // src/hooks/use-scroll-position.ts
613
+ import { useEffect as useEffect8, useState as useState6 } from "react";
614
+ var useScrollPosition = ({ targetRef } = {}) => {
615
+ const [scrollPosition, setScrollPosition] = useState6(0);
616
+ useEffect8(() => {
617
+ const target = targetRef?.current || document;
618
+ const scrollable = target === document ? window : target;
619
+ const updatePosition = () => {
620
+ const scrollY = target === document ? window.scrollY || window.pageYOffset : target.scrollTop;
621
+ setScrollPosition(scrollY);
622
+ };
623
+ scrollable.addEventListener("scroll", updatePosition, { passive: true });
624
+ updatePosition();
625
+ return () => {
626
+ scrollable.removeEventListener("scroll", updatePosition);
627
+ };
628
+ }, [targetRef]);
629
+ return scrollPosition;
630
+ };
631
+
632
+ // src/hooks/use-slider-input.ts
633
+ import { useCallback as useCallback3, useState as useState7 } from "react";
634
+ function useSliderInput({ minValue, maxValue, initialValue }) {
635
+ const [sliderValues, setSliderValues] = useState7(initialValue);
636
+ const [inputValues, setInputValues] = useState7(initialValue);
637
+ const handleSliderChange = useCallback3((values) => {
638
+ setSliderValues(values);
639
+ setInputValues(values);
640
+ }, []);
641
+ const handleInputChange = useCallback3(
642
+ (e, index) => {
643
+ const newValue = parseFloat(e.target.value);
644
+ if (!Number.isNaN(newValue)) {
645
+ const updatedInputs = [...inputValues];
646
+ updatedInputs[index] = newValue;
647
+ setInputValues(updatedInputs);
648
+ }
649
+ },
650
+ [inputValues]
651
+ );
652
+ const validateAndUpdateValue = useCallback3(
653
+ (value, index) => {
654
+ const updatedSlider = [...sliderValues];
655
+ if (index === 0) {
656
+ updatedSlider[0] = Math.max(minValue, Math.min(value, sliderValues[1]));
657
+ } else {
658
+ updatedSlider[1] = Math.min(maxValue, Math.max(value, sliderValues[0]));
659
+ }
660
+ setSliderValues(updatedSlider);
661
+ setInputValues(updatedSlider);
662
+ },
663
+ [sliderValues, minValue, maxValue]
664
+ );
665
+ return {
666
+ /** Function to manually set slider values */
667
+ setSliderValues,
668
+ /** Function to manually set input values */
669
+ setInputValues,
670
+ /** Current slider values [min, max] */
671
+ sliderValues,
672
+ /** Current input values [min, max] */
673
+ inputValues,
674
+ /** Handler for slider value changes */
675
+ handleSliderChange,
676
+ /** Handler for input field changes */
677
+ handleInputChange,
678
+ /** Function to validate and update values from inputs */
679
+ validateAndUpdateValue
680
+ };
681
+ }
682
+
683
+ // src/hooks/use-viewport.ts
684
+ import { useEffect as useEffect9, useState as useState8 } from "react";
685
+ var useViewport = () => {
686
+ const [dimensions, setDimensions] = useState8(() => {
687
+ if (typeof window !== "undefined") {
688
+ return [window.innerHeight, window.innerWidth];
689
+ }
690
+ return [0, 0];
691
+ });
692
+ useEffect9(() => {
693
+ const handleResize = () => {
694
+ setDimensions([window.innerHeight, window.innerWidth]);
695
+ };
696
+ handleResize();
697
+ window.addEventListener("resize", handleResize, { passive: true });
698
+ return () => {
699
+ window.removeEventListener("resize", handleResize);
700
+ };
701
+ }, []);
702
+ return dimensions;
703
+ };
704
+ export {
705
+ formatBytes,
706
+ useBodyClasses,
707
+ useCopyToClipboard,
708
+ useFileUpload,
709
+ useHydrated,
710
+ useIntersectionObserver,
711
+ useIsMac,
712
+ useIsMobile,
713
+ useMediaQuery,
714
+ useMenu,
715
+ useMetaColor,
716
+ useMounted,
717
+ useMutationObserver,
718
+ useRemoveGAParams,
719
+ useScrollPosition,
720
+ useSliderInput,
721
+ useViewport
722
+ };