startx 0.1.5 → 0.2.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/.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 +2 -2
- 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,24 +1,24 @@
|
|
|
1
|
-
import { type AllHTMLAttributes, forwardRef } from 'react';
|
|
2
|
-
|
|
3
|
-
const PageContainer = forwardRef<HTMLButtonElement, AllHTMLAttributes<HTMLDivElement>>(
|
|
4
|
-
(
|
|
5
|
-
{
|
|
6
|
-
className,
|
|
7
|
-
|
|
8
|
-
...props
|
|
9
|
-
},
|
|
10
|
-
ref,
|
|
11
|
-
) => {
|
|
12
|
-
return (
|
|
13
|
-
<main
|
|
14
|
-
ref={ref}
|
|
15
|
-
{...props}
|
|
16
|
-
className={` px-4 py-4 gap-8 xs:px-8 md:px-16 lg:px-32 max-w-screen-2xl m-auto ${className} `}
|
|
17
|
-
>
|
|
18
|
-
<>{props.children}</>
|
|
19
|
-
</main>
|
|
20
|
-
);
|
|
21
|
-
},
|
|
22
|
-
);
|
|
23
|
-
PageContainer.displayName = 'PageContainer';
|
|
24
|
-
export { PageContainer };
|
|
1
|
+
import { type AllHTMLAttributes, forwardRef } from 'react';
|
|
2
|
+
|
|
3
|
+
const PageContainer = forwardRef<HTMLButtonElement, AllHTMLAttributes<HTMLDivElement>>(
|
|
4
|
+
(
|
|
5
|
+
{
|
|
6
|
+
className,
|
|
7
|
+
|
|
8
|
+
...props
|
|
9
|
+
},
|
|
10
|
+
ref,
|
|
11
|
+
) => {
|
|
12
|
+
return (
|
|
13
|
+
<main
|
|
14
|
+
ref={ref}
|
|
15
|
+
{...props}
|
|
16
|
+
className={` px-4 py-4 gap-8 xs:px-8 md:px-16 lg:px-32 max-w-screen-2xl m-auto ${className} `}
|
|
17
|
+
>
|
|
18
|
+
<>{props.children}</>
|
|
19
|
+
</main>
|
|
20
|
+
);
|
|
21
|
+
},
|
|
22
|
+
);
|
|
23
|
+
PageContainer.displayName = 'PageContainer';
|
|
24
|
+
export { PageContainer };
|
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
import type { ClassNameValue } from 'tailwind-merge';
|
|
2
|
-
|
|
3
|
-
import { Popover, PopoverContent, PopoverTrigger } from '../../components/ui/popover';
|
|
4
|
-
import { cn } from '../lib/utils';
|
|
5
|
-
|
|
6
|
-
interface SimplePopoverProps {
|
|
7
|
-
children: React.ReactNode;
|
|
8
|
-
trigger: React.ReactNode;
|
|
9
|
-
side?: 'top' | 'right' | 'bottom' | 'left';
|
|
10
|
-
sideOffset?: number;
|
|
11
|
-
open?: boolean;
|
|
12
|
-
onOpenChange?: (open: boolean) => void;
|
|
13
|
-
className?: ClassNameValue;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export const SimplePopover = (props: SimplePopoverProps) => {
|
|
17
|
-
return (
|
|
18
|
-
<Popover open={props.open} onOpenChange={props.onOpenChange}>
|
|
19
|
-
<PopoverTrigger asChild>{props.trigger}</PopoverTrigger>
|
|
20
|
-
<PopoverContent
|
|
21
|
-
side={props.side}
|
|
22
|
-
sideOffset={props.sideOffset}
|
|
23
|
-
className={cn(props.className)}
|
|
24
|
-
>
|
|
25
|
-
{props.children}
|
|
26
|
-
</PopoverContent>
|
|
27
|
-
</Popover>
|
|
28
|
-
);
|
|
29
|
-
};
|
|
1
|
+
import type { ClassNameValue } from 'tailwind-merge';
|
|
2
|
+
|
|
3
|
+
import { Popover, PopoverContent, PopoverTrigger } from '../../components/ui/popover';
|
|
4
|
+
import { cn } from '../lib/utils';
|
|
5
|
+
|
|
6
|
+
interface SimplePopoverProps {
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
trigger: React.ReactNode;
|
|
9
|
+
side?: 'top' | 'right' | 'bottom' | 'left';
|
|
10
|
+
sideOffset?: number;
|
|
11
|
+
open?: boolean;
|
|
12
|
+
onOpenChange?: (open: boolean) => void;
|
|
13
|
+
className?: ClassNameValue;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const SimplePopover = (props: SimplePopoverProps) => {
|
|
17
|
+
return (
|
|
18
|
+
<Popover open={props.open} onOpenChange={props.onOpenChange}>
|
|
19
|
+
<PopoverTrigger asChild>{props.trigger}</PopoverTrigger>
|
|
20
|
+
<PopoverContent
|
|
21
|
+
side={props.side}
|
|
22
|
+
sideOffset={props.sideOffset}
|
|
23
|
+
className={cn(props.className)}
|
|
24
|
+
>
|
|
25
|
+
{props.children}
|
|
26
|
+
</PopoverContent>
|
|
27
|
+
</Popover>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import type React from 'react';
|
|
2
|
-
|
|
3
|
-
export type SwitchCases<T extends string> = {
|
|
4
|
-
[key in T]?: React.ReactNode; // Mapping of cases with the type T
|
|
5
|
-
};
|
|
6
|
-
|
|
7
|
-
export type SwitchProps<T extends string> = {
|
|
8
|
-
value: T; // The value to match against the cases
|
|
9
|
-
cases: SwitchCases<T>; // Cases object with keys as possible values of type T
|
|
10
|
-
default?: React.ReactNode; // Default component if no case matches
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
function SwitchComponent<T extends string>(props: SwitchProps<T>) {
|
|
14
|
-
const { value, cases, default: defaultCase } = props;
|
|
15
|
-
|
|
16
|
-
// Render the component matching the value, or the default if none matches
|
|
17
|
-
return <>{cases[value] ?? defaultCase ?? <div></div>}</>;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export { SwitchComponent };
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
|
|
3
|
+
export type SwitchCases<T extends string> = {
|
|
4
|
+
[key in T]?: React.ReactNode; // Mapping of cases with the type T
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type SwitchProps<T extends string> = {
|
|
8
|
+
value: T; // The value to match against the cases
|
|
9
|
+
cases: SwitchCases<T>; // Cases object with keys as possible values of type T
|
|
10
|
+
default?: React.ReactNode; // Default component if no case matches
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function SwitchComponent<T extends string>(props: SwitchProps<T>) {
|
|
14
|
+
const { value, cases, default: defaultCase } = props;
|
|
15
|
+
|
|
16
|
+
// Render the component matching the value, or the default if none matches
|
|
17
|
+
return <>{cases[value] ?? defaultCase ?? <div></div>}</>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { SwitchComponent };
|
|
@@ -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
|
+
}
|