startx 0.1.5 → 0.1.6
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/.editorconfig +20 -20
- package/.github/workflows/publish.yml +48 -0
- package/LICENSE +21 -21
- package/configs/eslint-config/plugins.d.ts +1 -1
- package/configs/eslint-config/src/rules/no-argument-spread.ts +96 -96
- package/configs/eslint-config/src/rules/no-internal-package-import.ts +40 -40
- package/configs/eslint-config/src/rules/no-interpolation-in-regular-string.ts +32 -32
- package/configs/eslint-config/src/rules/no-skipped-tests.ts +61 -61
- package/configs/eslint-config/src/rules/no-top-level-relative-imports-in-backend-module.ts +27 -27
- package/configs/eslint-config/src/rules/no-type-unsafe-event-emitter.ts +33 -33
- package/configs/eslint-config/src/rules/no-uncaught-json-parse.test.ts +21 -21
- package/configs/eslint-config/src/rules/no-untyped-config-class-field.ts +26 -26
- package/configs/eslint-config/src/rules/no-unused-param-catch-clause.ts +33 -33
- package/configs/eslint-config/src/rules/no-useless-catch-throw.test.ts +34 -34
- package/configs/eslint-config/src/rules/no-useless-catch-throw.ts +47 -47
- package/configs/eslint-config/src/utils/json.ts +21 -21
- package/package.json +34 -35
- package/packages/@repo/constants/src/api.ts +1 -1
- package/packages/@repo/constants/src/time.ts +23 -23
- package/packages/@repo/db/src/schema/index.ts +1 -1
- package/packages/@repo/lib/src/error-handlers-module/index.ts +11 -11
- package/packages/cli/dist/index.mjs +38 -165
- package/packages/cli/tsdown.config.ts +1 -0
- package/packages/ui/src/components/custom/grid-component.tsx +23 -23
- package/packages/ui/src/components/custom/hover-tool.tsx +38 -38
- package/packages/ui/src/components/custom/image-picker.tsx +109 -109
- package/packages/ui/src/components/custom/no-content.tsx +37 -37
- package/packages/ui/src/components/custom/page-container.tsx +24 -24
- package/packages/ui/src/components/custom/simple-popover.tsx +29 -29
- package/packages/ui/src/components/custom/switch-component.tsx +20 -20
- package/packages/ui/src/components/custom/theme-provider.tsx +74 -74
- package/packages/ui/src/components/hooks/event/use-click.tsx +39 -39
- package/packages/ui/src/components/hooks/time/useDebounce.tsx +21 -21
- package/packages/ui/src/components/hooks/time/useInterval.tsx +35 -35
- package/packages/ui/src/components/hooks/time/useTimeout.tsx +19 -19
- package/packages/ui/src/components/hooks/time/useTimer.tsx +51 -51
- package/packages/ui/src/components/hooks/use-media-query.tsx +19 -19
- package/packages/ui/src/components/hooks/use-persistent-storage.tsx +52 -52
- package/packages/ui/src/components/hooks/use-window-dimension.tsx +30 -30
- package/packages/ui/src/components/sonner.tsx +1 -1
- package/packages/ui/src/components/ui/button.tsx +96 -96
- package/packages/ui/src/components/ui/dropdown-menu.tsx +226 -226
- package/packages/ui/src/components/ui/label.tsx +24 -24
- package/packages/ui/src/components/ui/popover.tsx +42 -42
- package/packages/ui/src/components/ui/select.tsx +170 -170
- package/packages/ui/src/components/ui/separator.tsx +28 -28
- package/packages/ui/src/components/ui/sheet.tsx +130 -130
- package/packages/ui/src/components/ui/skeleton.tsx +13 -13
- package/packages/ui/src/components/ui/spinner.tsx +16 -16
- package/packages/ui/src/components/ui/switch.tsx +28 -28
- package/packages/ui/src/components/ui/tabs.tsx +54 -54
- package/packages/ui/src/components/ui/tooltip.tsx +30 -30
- package/packages/ui/src/components/util/n-formattor.ts +22 -22
- package/packages/ui/src/components/util/storage.ts +37 -37
- package/packages/ui/src/globals.css +87 -87
- package/configs/vitest-config/dist/base.mjs +0 -1
- package/configs/vitest-config/dist/frontend.mjs +0 -1
- package/configs/vitest-config/dist/node.mjs +0 -1
- package/packages/@repo/redis/dist/index.d.mts +0 -3
- package/packages/@repo/redis/dist/index.mjs +0 -5
- package/packages/@repo/redis/dist/lib/redis-client.d.mts +0 -7
- package/packages/@repo/redis/dist/lib/redis-client.mjs +0 -25
- package/packages/@repo/redis/dist/lib/redis-client.mjs.map +0 -1
- package/packages/@repo/redis/dist/lib/redis-module.d.mts +0 -5
- package/packages/@repo/redis/dist/lib/redis-module.mjs +0 -6
- package/packages/@repo/redis/dist/lib/redis-module.mjs.map +0 -1
- /package/{apps/core-server/.env.example → .env.example} +0 -0
|
@@ -1,74 +1,74 @@
|
|
|
1
|
-
import { createContext, useContext, useEffect, useState } from 'react';
|
|
2
|
-
import { Toaster } from 'sonner';
|
|
3
|
-
type Theme = 'dark' | 'light' | 'system';
|
|
4
|
-
|
|
5
|
-
type ThemeProviderProps = {
|
|
6
|
-
children: React.ReactNode;
|
|
7
|
-
defaultTheme?: Theme;
|
|
8
|
-
storageKey?: string;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
type ThemeProviderState = {
|
|
12
|
-
theme: Theme;
|
|
13
|
-
setTheme: (theme: Theme) => void;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
const initialState: ThemeProviderState = {
|
|
17
|
-
theme: 'system',
|
|
18
|
-
setTheme: () => null,
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const ThemeProviderContext = createContext<ThemeProviderState>(initialState);
|
|
22
|
-
|
|
23
|
-
export const ThemeProvider = ({
|
|
24
|
-
children,
|
|
25
|
-
defaultTheme = 'dark',
|
|
26
|
-
storageKey = 'vite-ui-theme',
|
|
27
|
-
...props
|
|
28
|
-
}: ThemeProviderProps) => {
|
|
29
|
-
const [theme, setTheme] = useState<Theme>(
|
|
30
|
-
() =>
|
|
31
|
-
(typeof window !== 'undefined' && (localStorage.getItem(storageKey) as Theme)) ||
|
|
32
|
-
defaultTheme,
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
useEffect(() => {
|
|
36
|
-
const root = window.document.documentElement;
|
|
37
|
-
|
|
38
|
-
root.classList.remove('light', 'dark');
|
|
39
|
-
|
|
40
|
-
if (theme === 'system') {
|
|
41
|
-
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
42
|
-
? 'dark'
|
|
43
|
-
: 'light';
|
|
44
|
-
|
|
45
|
-
root.classList.add(systemTheme);
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
root.classList.add(theme);
|
|
50
|
-
}, [theme]);
|
|
51
|
-
|
|
52
|
-
const value = {
|
|
53
|
-
theme,
|
|
54
|
-
setTheme: (theme: Theme) => {
|
|
55
|
-
localStorage.setItem(storageKey, theme);
|
|
56
|
-
setTheme(theme);
|
|
57
|
-
},
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
return (
|
|
61
|
-
<ThemeProviderContext.Provider {...props} value={value}>
|
|
62
|
-
<Toaster richColors />
|
|
63
|
-
{children}
|
|
64
|
-
</ThemeProviderContext.Provider>
|
|
65
|
-
);
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
export const useTheme = () => {
|
|
69
|
-
const context = useContext(ThemeProviderContext);
|
|
70
|
-
|
|
71
|
-
if (context === undefined) throw new Error('useTheme must be used within a ThemeProvider');
|
|
72
|
-
|
|
73
|
-
return context;
|
|
74
|
-
};
|
|
1
|
+
import { createContext, useContext, useEffect, useState } from 'react';
|
|
2
|
+
import { Toaster } from 'sonner';
|
|
3
|
+
type Theme = 'dark' | 'light' | 'system';
|
|
4
|
+
|
|
5
|
+
type ThemeProviderProps = {
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
defaultTheme?: Theme;
|
|
8
|
+
storageKey?: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
type ThemeProviderState = {
|
|
12
|
+
theme: Theme;
|
|
13
|
+
setTheme: (theme: Theme) => void;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const initialState: ThemeProviderState = {
|
|
17
|
+
theme: 'system',
|
|
18
|
+
setTheme: () => null,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const ThemeProviderContext = createContext<ThemeProviderState>(initialState);
|
|
22
|
+
|
|
23
|
+
export const ThemeProvider = ({
|
|
24
|
+
children,
|
|
25
|
+
defaultTheme = 'dark',
|
|
26
|
+
storageKey = 'vite-ui-theme',
|
|
27
|
+
...props
|
|
28
|
+
}: ThemeProviderProps) => {
|
|
29
|
+
const [theme, setTheme] = useState<Theme>(
|
|
30
|
+
() =>
|
|
31
|
+
(typeof window !== 'undefined' && (localStorage.getItem(storageKey) as Theme)) ||
|
|
32
|
+
defaultTheme,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
const root = window.document.documentElement;
|
|
37
|
+
|
|
38
|
+
root.classList.remove('light', 'dark');
|
|
39
|
+
|
|
40
|
+
if (theme === 'system') {
|
|
41
|
+
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
42
|
+
? 'dark'
|
|
43
|
+
: 'light';
|
|
44
|
+
|
|
45
|
+
root.classList.add(systemTheme);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
root.classList.add(theme);
|
|
50
|
+
}, [theme]);
|
|
51
|
+
|
|
52
|
+
const value = {
|
|
53
|
+
theme,
|
|
54
|
+
setTheme: (theme: Theme) => {
|
|
55
|
+
localStorage.setItem(storageKey, theme);
|
|
56
|
+
setTheme(theme);
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<ThemeProviderContext.Provider {...props} value={value}>
|
|
62
|
+
<Toaster richColors />
|
|
63
|
+
{children}
|
|
64
|
+
</ThemeProviderContext.Provider>
|
|
65
|
+
);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const useTheme = () => {
|
|
69
|
+
const context = useContext(ThemeProviderContext);
|
|
70
|
+
|
|
71
|
+
if (context === undefined) throw new Error('useTheme must be used within a ThemeProvider');
|
|
72
|
+
|
|
73
|
+
return context;
|
|
74
|
+
};
|
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
/* eslint-disable id-denylist */
|
|
2
|
-
import { type RefObject, useEffect } from 'react';
|
|
3
|
-
|
|
4
|
-
function useOutsideClick<T extends HTMLElement>(
|
|
5
|
-
ref: RefObject<T | null>,
|
|
6
|
-
callback: () => void,
|
|
7
|
-
): void {
|
|
8
|
-
useEffect(() => {
|
|
9
|
-
const handleClickOrTouch = (event: Event) => {
|
|
10
|
-
if (ref.current && !ref.current.contains(event.target as Node)) {
|
|
11
|
-
callback();
|
|
12
|
-
}
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
document.addEventListener('mousedown', handleClickOrTouch);
|
|
16
|
-
|
|
17
|
-
return () => {
|
|
18
|
-
document.removeEventListener('mousedown', handleClickOrTouch);
|
|
19
|
-
};
|
|
20
|
-
}, [ref, callback]);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function useInsideClick<T extends HTMLElement>(ref: RefObject<T>, callback: () => void): void {
|
|
24
|
-
useEffect(() => {
|
|
25
|
-
const handleClickOrTouch = (event: Event) => {
|
|
26
|
-
if (ref.current && !ref.current.contains(event.target as Node)) {
|
|
27
|
-
callback();
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
document.addEventListener('mousedown', handleClickOrTouch);
|
|
32
|
-
|
|
33
|
-
return () => {
|
|
34
|
-
document.removeEventListener('mousedown', handleClickOrTouch);
|
|
35
|
-
};
|
|
36
|
-
}, [ref, callback]);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export { useInsideClick, useOutsideClick };
|
|
1
|
+
/* eslint-disable id-denylist */
|
|
2
|
+
import { type RefObject, useEffect } from 'react';
|
|
3
|
+
|
|
4
|
+
function useOutsideClick<T extends HTMLElement>(
|
|
5
|
+
ref: RefObject<T | null>,
|
|
6
|
+
callback: () => void,
|
|
7
|
+
): void {
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
const handleClickOrTouch = (event: Event) => {
|
|
10
|
+
if (ref.current && !ref.current.contains(event.target as Node)) {
|
|
11
|
+
callback();
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
document.addEventListener('mousedown', handleClickOrTouch);
|
|
16
|
+
|
|
17
|
+
return () => {
|
|
18
|
+
document.removeEventListener('mousedown', handleClickOrTouch);
|
|
19
|
+
};
|
|
20
|
+
}, [ref, callback]);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function useInsideClick<T extends HTMLElement>(ref: RefObject<T>, callback: () => void): void {
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
const handleClickOrTouch = (event: Event) => {
|
|
26
|
+
if (ref.current && !ref.current.contains(event.target as Node)) {
|
|
27
|
+
callback();
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
document.addEventListener('mousedown', handleClickOrTouch);
|
|
32
|
+
|
|
33
|
+
return () => {
|
|
34
|
+
document.removeEventListener('mousedown', handleClickOrTouch);
|
|
35
|
+
};
|
|
36
|
+
}, [ref, callback]);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export { useInsideClick, useOutsideClick };
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import { useState, useEffect } from 'react';
|
|
2
|
-
|
|
3
|
-
function useDebounce<T>(value: T, delay: number): T {
|
|
4
|
-
const [debouncedValue, setDebouncedValue] = useState<T>(value);
|
|
5
|
-
|
|
6
|
-
useEffect(() => {
|
|
7
|
-
// Update debounced value after delay
|
|
8
|
-
const handler = setTimeout(() => {
|
|
9
|
-
setDebouncedValue(value);
|
|
10
|
-
}, delay);
|
|
11
|
-
|
|
12
|
-
// Clear timeout on cleanup to prevent memory leaks
|
|
13
|
-
return () => {
|
|
14
|
-
clearTimeout(handler);
|
|
15
|
-
};
|
|
16
|
-
}, [value, delay]);
|
|
17
|
-
|
|
18
|
-
return debouncedValue;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export { useDebounce };
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
function useDebounce<T>(value: T, delay: number): T {
|
|
4
|
+
const [debouncedValue, setDebouncedValue] = useState<T>(value);
|
|
5
|
+
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
// Update debounced value after delay
|
|
8
|
+
const handler = setTimeout(() => {
|
|
9
|
+
setDebouncedValue(value);
|
|
10
|
+
}, delay);
|
|
11
|
+
|
|
12
|
+
// Clear timeout on cleanup to prevent memory leaks
|
|
13
|
+
return () => {
|
|
14
|
+
clearTimeout(handler);
|
|
15
|
+
};
|
|
16
|
+
}, [value, delay]);
|
|
17
|
+
|
|
18
|
+
return debouncedValue;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export { useDebounce };
|
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
/* eslint-disable id-denylist */
|
|
2
|
-
import { useRef, useCallback, useEffect } from 'react';
|
|
3
|
-
|
|
4
|
-
const useInterval = (callback: () => void, interval: number) => {
|
|
5
|
-
const intervalIdRef = useRef<number | null>(null);
|
|
6
|
-
const savedCallback = useRef<() => void>(null);
|
|
7
|
-
|
|
8
|
-
// Remember the latest callback.
|
|
9
|
-
useEffect(() => {
|
|
10
|
-
savedCallback.current = callback;
|
|
11
|
-
}, [callback]);
|
|
12
|
-
|
|
13
|
-
const start = useCallback(() => {
|
|
14
|
-
if (intervalIdRef.current !== null) return; // Prevent multiple intervals
|
|
15
|
-
intervalIdRef.current = window.setInterval(() => {
|
|
16
|
-
if (savedCallback.current) savedCallback.current();
|
|
17
|
-
}, interval);
|
|
18
|
-
}, [interval]);
|
|
19
|
-
|
|
20
|
-
const clear = useCallback(() => {
|
|
21
|
-
if (intervalIdRef.current !== null) {
|
|
22
|
-
clearInterval(intervalIdRef.current);
|
|
23
|
-
intervalIdRef.current = null;
|
|
24
|
-
}
|
|
25
|
-
}, []);
|
|
26
|
-
|
|
27
|
-
// Clear interval on component unmount
|
|
28
|
-
useEffect(() => {
|
|
29
|
-
return () => clear();
|
|
30
|
-
}, [clear]);
|
|
31
|
-
|
|
32
|
-
return { start, clear };
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
export { useInterval };
|
|
1
|
+
/* eslint-disable id-denylist */
|
|
2
|
+
import { useRef, useCallback, useEffect } from 'react';
|
|
3
|
+
|
|
4
|
+
const useInterval = (callback: () => void, interval: number) => {
|
|
5
|
+
const intervalIdRef = useRef<number | null>(null);
|
|
6
|
+
const savedCallback = useRef<() => void>(null);
|
|
7
|
+
|
|
8
|
+
// Remember the latest callback.
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
savedCallback.current = callback;
|
|
11
|
+
}, [callback]);
|
|
12
|
+
|
|
13
|
+
const start = useCallback(() => {
|
|
14
|
+
if (intervalIdRef.current !== null) return; // Prevent multiple intervals
|
|
15
|
+
intervalIdRef.current = window.setInterval(() => {
|
|
16
|
+
if (savedCallback.current) savedCallback.current();
|
|
17
|
+
}, interval);
|
|
18
|
+
}, [interval]);
|
|
19
|
+
|
|
20
|
+
const clear = useCallback(() => {
|
|
21
|
+
if (intervalIdRef.current !== null) {
|
|
22
|
+
clearInterval(intervalIdRef.current);
|
|
23
|
+
intervalIdRef.current = null;
|
|
24
|
+
}
|
|
25
|
+
}, []);
|
|
26
|
+
|
|
27
|
+
// Clear interval on component unmount
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
return () => clear();
|
|
30
|
+
}, [clear]);
|
|
31
|
+
|
|
32
|
+
return { start, clear };
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export { useInterval };
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
|
|
3
|
-
export function useMediaQuery(query: string) {
|
|
4
|
-
const [value, setValue] = React.useState(false);
|
|
5
|
-
|
|
6
|
-
React.useEffect(() => {
|
|
7
|
-
function onChange(event: MediaQueryListEvent) {
|
|
8
|
-
setValue(event.matches);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const result = matchMedia(query);
|
|
12
|
-
result.addEventListener('change', onChange);
|
|
13
|
-
setValue(result.matches);
|
|
14
|
-
|
|
15
|
-
return () => result.removeEventListener('change', onChange);
|
|
16
|
-
}, [query]);
|
|
17
|
-
|
|
18
|
-
return value;
|
|
19
|
-
}
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export function useMediaQuery(query: string) {
|
|
4
|
+
const [value, setValue] = React.useState(false);
|
|
5
|
+
|
|
6
|
+
React.useEffect(() => {
|
|
7
|
+
function onChange(event: MediaQueryListEvent) {
|
|
8
|
+
setValue(event.matches);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const result = matchMedia(query);
|
|
12
|
+
result.addEventListener('change', onChange);
|
|
13
|
+
setValue(result.matches);
|
|
14
|
+
|
|
15
|
+
return () => result.removeEventListener('change', onChange);
|
|
16
|
+
}, [query]);
|
|
17
|
+
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
import { useState, useRef, useEffect } from "react";
|
|
2
|
-
|
|
3
|
-
type TimerHook = {
|
|
4
|
-
counter: number;
|
|
5
|
-
start: () => void;
|
|
6
|
-
reset: () => void;
|
|
7
|
-
clear: () => void;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
export function useTimer(initialSeconds: number): TimerHook {
|
|
11
|
-
const [counter, setCounter] = useState(initialSeconds);
|
|
12
|
-
const timerRef = useRef<NodeJS.Timeout | null>(null);
|
|
13
|
-
|
|
14
|
-
useEffect(() => {
|
|
15
|
-
// Cleanup on component unmount or when counter changes
|
|
16
|
-
return () => {
|
|
17
|
-
if (timerRef.current) {
|
|
18
|
-
clearTimeout(timerRef.current);
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
}, []);
|
|
22
|
-
|
|
23
|
-
const start = () => {
|
|
24
|
-
if (timerRef.current) {
|
|
25
|
-
clearTimeout(timerRef.current);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
timerRef.current = setTimeout(() => {
|
|
29
|
-
setCounter((prev) => (prev > 0 ? prev - 1 : 0));
|
|
30
|
-
}, 1000);
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const reset = () => {
|
|
34
|
-
setCounter(initialSeconds);
|
|
35
|
-
clear();
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const clear = () => {
|
|
39
|
-
if (timerRef.current) {
|
|
40
|
-
clearTimeout(timerRef.current);
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
useEffect(() => {
|
|
45
|
-
if (counter > 0) {
|
|
46
|
-
start();
|
|
47
|
-
}
|
|
48
|
-
}, [counter]);
|
|
49
|
-
|
|
50
|
-
return { counter, start, reset, clear };
|
|
51
|
-
}
|
|
1
|
+
import { useState, useRef, useEffect } from "react";
|
|
2
|
+
|
|
3
|
+
type TimerHook = {
|
|
4
|
+
counter: number;
|
|
5
|
+
start: () => void;
|
|
6
|
+
reset: () => void;
|
|
7
|
+
clear: () => void;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function useTimer(initialSeconds: number): TimerHook {
|
|
11
|
+
const [counter, setCounter] = useState(initialSeconds);
|
|
12
|
+
const timerRef = useRef<NodeJS.Timeout | null>(null);
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
// Cleanup on component unmount or when counter changes
|
|
16
|
+
return () => {
|
|
17
|
+
if (timerRef.current) {
|
|
18
|
+
clearTimeout(timerRef.current);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
}, []);
|
|
22
|
+
|
|
23
|
+
const start = () => {
|
|
24
|
+
if (timerRef.current) {
|
|
25
|
+
clearTimeout(timerRef.current);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
timerRef.current = setTimeout(() => {
|
|
29
|
+
setCounter((prev) => (prev > 0 ? prev - 1 : 0));
|
|
30
|
+
}, 1000);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const reset = () => {
|
|
34
|
+
setCounter(initialSeconds);
|
|
35
|
+
clear();
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const clear = () => {
|
|
39
|
+
if (timerRef.current) {
|
|
40
|
+
clearTimeout(timerRef.current);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (counter > 0) {
|
|
46
|
+
start();
|
|
47
|
+
}
|
|
48
|
+
}, [counter]);
|
|
49
|
+
|
|
50
|
+
return { counter, start, reset, clear };
|
|
51
|
+
}
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
|
|
3
|
-
export function useMediaQuery(query: string) {
|
|
4
|
-
const [value, setValue] = React.useState(false);
|
|
5
|
-
|
|
6
|
-
React.useEffect(() => {
|
|
7
|
-
function onChange(event: MediaQueryListEvent) {
|
|
8
|
-
setValue(event.matches);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const result = matchMedia(query);
|
|
12
|
-
result.addEventListener("change", onChange);
|
|
13
|
-
setValue(result.matches);
|
|
14
|
-
|
|
15
|
-
return () => result.removeEventListener("change", onChange);
|
|
16
|
-
}, [query]);
|
|
17
|
-
|
|
18
|
-
return value;
|
|
19
|
-
}
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
export function useMediaQuery(query: string) {
|
|
4
|
+
const [value, setValue] = React.useState(false);
|
|
5
|
+
|
|
6
|
+
React.useEffect(() => {
|
|
7
|
+
function onChange(event: MediaQueryListEvent) {
|
|
8
|
+
setValue(event.matches);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const result = matchMedia(query);
|
|
12
|
+
result.addEventListener("change", onChange);
|
|
13
|
+
setValue(result.matches);
|
|
14
|
+
|
|
15
|
+
return () => result.removeEventListener("change", onChange);
|
|
16
|
+
}, [query]);
|
|
17
|
+
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
@@ -1,52 +1,52 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { useState, useEffect, type Dispatch, type SetStateAction } from 'react';
|
|
3
|
-
|
|
4
|
-
export function useLocalStorage<T>(key: string, initialValue: T): [T, Dispatch<SetStateAction<T>>] {
|
|
5
|
-
// Retrieve stored value from localStorage, or use initialValue if none is found
|
|
6
|
-
const getStoredValue = (): T => {
|
|
7
|
-
// if (typeof window === "undefined") return initialValue;
|
|
8
|
-
const storedValue = localStorage.getItem(key);
|
|
9
|
-
if (storedValue !== null) {
|
|
10
|
-
try {
|
|
11
|
-
return JSON.parse(storedValue) as T;
|
|
12
|
-
} catch (error) {
|
|
13
|
-
console.error('Error parsing stored value:', error);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
return initialValue;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
const [value, setValue] = useState<T>(getStoredValue);
|
|
20
|
-
|
|
21
|
-
useEffect(() => {
|
|
22
|
-
localStorage.setItem(key, JSON.stringify(value));
|
|
23
|
-
}, [key, value]);
|
|
24
|
-
|
|
25
|
-
return [value, setValue];
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function useSessionStorage<T>(
|
|
29
|
-
key: string,
|
|
30
|
-
initialValue: T,
|
|
31
|
-
): [T, Dispatch<SetStateAction<T>>] {
|
|
32
|
-
// Retrieve stored value from localStorage, or use initialValue if none is found
|
|
33
|
-
const getStoredValue = (): T => {
|
|
34
|
-
const storedValue = sessionStorage.getItem(key);
|
|
35
|
-
if (storedValue !== null) {
|
|
36
|
-
try {
|
|
37
|
-
return JSON.parse(storedValue) as T;
|
|
38
|
-
} catch (error) {
|
|
39
|
-
console.error('Error parsing stored value:', error);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return initialValue;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const [value, setValue] = useState<T>(getStoredValue);
|
|
46
|
-
|
|
47
|
-
useEffect(() => {
|
|
48
|
-
sessionStorage.setItem(key, JSON.stringify(value));
|
|
49
|
-
}, [key, value]);
|
|
50
|
-
|
|
51
|
-
return [value, setValue];
|
|
52
|
-
}
|
|
1
|
+
'use client';
|
|
2
|
+
import { useState, useEffect, type Dispatch, type SetStateAction } from 'react';
|
|
3
|
+
|
|
4
|
+
export function useLocalStorage<T>(key: string, initialValue: T): [T, Dispatch<SetStateAction<T>>] {
|
|
5
|
+
// Retrieve stored value from localStorage, or use initialValue if none is found
|
|
6
|
+
const getStoredValue = (): T => {
|
|
7
|
+
// if (typeof window === "undefined") return initialValue;
|
|
8
|
+
const storedValue = localStorage.getItem(key);
|
|
9
|
+
if (storedValue !== null) {
|
|
10
|
+
try {
|
|
11
|
+
return JSON.parse(storedValue) as T;
|
|
12
|
+
} catch (error) {
|
|
13
|
+
console.error('Error parsing stored value:', error);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return initialValue;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const [value, setValue] = useState<T>(getStoredValue);
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
localStorage.setItem(key, JSON.stringify(value));
|
|
23
|
+
}, [key, value]);
|
|
24
|
+
|
|
25
|
+
return [value, setValue];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function useSessionStorage<T>(
|
|
29
|
+
key: string,
|
|
30
|
+
initialValue: T,
|
|
31
|
+
): [T, Dispatch<SetStateAction<T>>] {
|
|
32
|
+
// Retrieve stored value from localStorage, or use initialValue if none is found
|
|
33
|
+
const getStoredValue = (): T => {
|
|
34
|
+
const storedValue = sessionStorage.getItem(key);
|
|
35
|
+
if (storedValue !== null) {
|
|
36
|
+
try {
|
|
37
|
+
return JSON.parse(storedValue) as T;
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error('Error parsing stored value:', error);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return initialValue;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const [value, setValue] = useState<T>(getStoredValue);
|
|
46
|
+
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
sessionStorage.setItem(key, JSON.stringify(value));
|
|
49
|
+
}, [key, value]);
|
|
50
|
+
|
|
51
|
+
return [value, setValue];
|
|
52
|
+
}
|