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.
Files changed (67) hide show
  1. package/.editorconfig +20 -20
  2. package/.github/workflows/publish.yml +48 -0
  3. package/LICENSE +21 -21
  4. package/configs/eslint-config/plugins.d.ts +1 -1
  5. package/configs/eslint-config/src/rules/no-argument-spread.ts +96 -96
  6. package/configs/eslint-config/src/rules/no-internal-package-import.ts +40 -40
  7. package/configs/eslint-config/src/rules/no-interpolation-in-regular-string.ts +32 -32
  8. package/configs/eslint-config/src/rules/no-skipped-tests.ts +61 -61
  9. package/configs/eslint-config/src/rules/no-top-level-relative-imports-in-backend-module.ts +27 -27
  10. package/configs/eslint-config/src/rules/no-type-unsafe-event-emitter.ts +33 -33
  11. package/configs/eslint-config/src/rules/no-uncaught-json-parse.test.ts +21 -21
  12. package/configs/eslint-config/src/rules/no-untyped-config-class-field.ts +26 -26
  13. package/configs/eslint-config/src/rules/no-unused-param-catch-clause.ts +33 -33
  14. package/configs/eslint-config/src/rules/no-useless-catch-throw.test.ts +34 -34
  15. package/configs/eslint-config/src/rules/no-useless-catch-throw.ts +47 -47
  16. package/configs/eslint-config/src/utils/json.ts +21 -21
  17. package/package.json +34 -35
  18. package/packages/@repo/constants/src/api.ts +1 -1
  19. package/packages/@repo/constants/src/time.ts +23 -23
  20. package/packages/@repo/db/src/schema/index.ts +1 -1
  21. package/packages/@repo/lib/src/error-handlers-module/index.ts +11 -11
  22. package/packages/cli/dist/index.mjs +2 -2
  23. package/packages/cli/tsdown.config.ts +1 -0
  24. package/packages/ui/src/components/custom/grid-component.tsx +23 -23
  25. package/packages/ui/src/components/custom/hover-tool.tsx +38 -38
  26. package/packages/ui/src/components/custom/image-picker.tsx +109 -109
  27. package/packages/ui/src/components/custom/no-content.tsx +37 -37
  28. package/packages/ui/src/components/custom/page-container.tsx +24 -24
  29. package/packages/ui/src/components/custom/simple-popover.tsx +29 -29
  30. package/packages/ui/src/components/custom/switch-component.tsx +20 -20
  31. package/packages/ui/src/components/custom/theme-provider.tsx +74 -74
  32. package/packages/ui/src/components/hooks/event/use-click.tsx +39 -39
  33. package/packages/ui/src/components/hooks/time/useDebounce.tsx +21 -21
  34. package/packages/ui/src/components/hooks/time/useInterval.tsx +35 -35
  35. package/packages/ui/src/components/hooks/time/useTimeout.tsx +19 -19
  36. package/packages/ui/src/components/hooks/time/useTimer.tsx +51 -51
  37. package/packages/ui/src/components/hooks/use-media-query.tsx +19 -19
  38. package/packages/ui/src/components/hooks/use-persistent-storage.tsx +52 -52
  39. package/packages/ui/src/components/hooks/use-window-dimension.tsx +30 -30
  40. package/packages/ui/src/components/sonner.tsx +1 -1
  41. package/packages/ui/src/components/ui/button.tsx +96 -96
  42. package/packages/ui/src/components/ui/dropdown-menu.tsx +226 -226
  43. package/packages/ui/src/components/ui/label.tsx +24 -24
  44. package/packages/ui/src/components/ui/popover.tsx +42 -42
  45. package/packages/ui/src/components/ui/select.tsx +170 -170
  46. package/packages/ui/src/components/ui/separator.tsx +28 -28
  47. package/packages/ui/src/components/ui/sheet.tsx +130 -130
  48. package/packages/ui/src/components/ui/skeleton.tsx +13 -13
  49. package/packages/ui/src/components/ui/spinner.tsx +16 -16
  50. package/packages/ui/src/components/ui/switch.tsx +28 -28
  51. package/packages/ui/src/components/ui/tabs.tsx +54 -54
  52. package/packages/ui/src/components/ui/tooltip.tsx +30 -30
  53. package/packages/ui/src/components/util/n-formattor.ts +22 -22
  54. package/packages/ui/src/components/util/storage.ts +37 -37
  55. package/packages/ui/src/globals.css +87 -87
  56. package/configs/vitest-config/dist/base.mjs +0 -1
  57. package/configs/vitest-config/dist/frontend.mjs +0 -1
  58. package/configs/vitest-config/dist/node.mjs +0 -1
  59. package/packages/@repo/redis/dist/index.d.mts +0 -3
  60. package/packages/@repo/redis/dist/index.mjs +0 -5
  61. package/packages/@repo/redis/dist/lib/redis-client.d.mts +0 -7
  62. package/packages/@repo/redis/dist/lib/redis-client.mjs +0 -25
  63. package/packages/@repo/redis/dist/lib/redis-client.mjs.map +0 -1
  64. package/packages/@repo/redis/dist/lib/redis-module.d.mts +0 -5
  65. package/packages/@repo/redis/dist/lib/redis-module.mjs +0 -6
  66. package/packages/@repo/redis/dist/lib/redis-module.mjs.map +0 -1
  67. /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
+ }