Skip to content

[URH-76] 프로젝트 폴더 구조 수정 #65

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ const config: JestConfigWithTsJest = {
},
moduleNameMapper: {
'\\.(css|less|sass|scss)$': 'identity-obj-proxy',
'^@/(.*)$': '<rootDir>/src/$1',
},
setupFiles: ['jest-canvas-mock', './src/mocks/mockWorker.ts'],
setupFilesAfterEnv: ['./jest.setup.ts'],
testPathIgnorePatterns: ['/dist/'],
coveragePathIgnorePatterns: ['./src/mocks/'],
coverageThreshold: {
global: {
Expand Down
2 changes: 2 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import React from 'react';

function App() {
return (
<div>
Expand Down
55 changes: 29 additions & 26 deletions src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
export { default as useAsyncTasks } from './useAsyncTasks';
export { default as useClipboard } from './useClipboard';
export { default as useConfirm } from './useConfirm';
export { default as useDebounce } from './useDebounce';
export { default as useDelayFlag } from './useDelayFlag';
export { default as useDetectDevice } from './useDetectDevice';
export { default as useDetectInactivity } from './useDetectInactivity';
export { default as useEventListener } from './useEventListener';
export { default as useGeolocation } from './useGeolocation';
export { default as useHover } from './useHover';
export { default as useIntersectionObserver } from './useIntersectionObserver';
export { default as useInterval } from './useInterval';
export { default as useLocalStorage } from './useLocalStorage';
export { default as useLongPress } from './useLongPress';
export { default as useMousePosition } from './useMousePosition';
export { default as useOnlineStatus } from './useOnlineStatus';
export { default as useOutsideInteraction } from './useOutsideInteraction';
export { default as usePrefersColorScheme } from './usePrefersColorScheme';
export { default as useScrollLock } from './useScrollLock';
export { default as useScrollY } from './useScrollY';
export { default as useSound } from './useSound';
export { default as useTimer } from './useTimer';
export { default as useToggle } from './useToggle';
export { default as useUnmountEffect } from './useUnmountEffect';
export { default as useWindowSize } from './useWindowSize';
export { default as useWorker } from './useWorker';
export { useAsyncTasks } from './useAsyncTasks';
export { useClipboard } from './useClipboard';
export { useConfirm } from './useConfirm';
export { useDebounce } from './useDebounce';
export { useDeepCompareEffect } from './useDeepCompareEffect';
export { useDelayFlag } from './useDelayFlag';
export { useDetectDevice } from './useDetectDevice';
export { useDetectInactivity } from './useDetectInactivity';
export { useEventListener } from './useEventListener';
export { useGeolocation } from './useGeolocation';
export { useHover } from './useHover';
export { useIntersectionObserver } from './useIntersectionObserver';
export { useInterval } from './useInterval';
export { useKeyCombination } from './useKeyCombination';
export { useLocalStorage } from './useLocalStorage';
export { useLongPress } from './useLongPress';
export { useMousePosition } from './useMousePosition';
export { useOnlineStatus } from './useOnlineStatus';
export { useOutsideInteraction } from './useOutsideInteraction';
export { usePrefersColorScheme } from './usePrefersColorScheme';
export { useScrollLock } from './useScrollLock';
export { useScrollY } from './useScrollY';
export { useSound } from './useSound';
export { useTimer } from './useTimer';
export { useToggle } from './useToggle';
export { useTranslation } from './useTranslation';
export { useUnmountEffect } from './useUnmountEffect';
export { useWindowSize } from './useWindowSize';
export { useWorker } from './useWorker';
1 change: 1 addition & 0 deletions src/hooks/useAsyncTasks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as useAsyncTasks } from './useAsyncTasks';
23 changes: 23 additions & 0 deletions src/hooks/useAsyncTasks/reducer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { TaskState, TaskAction, ACTION_TYPES } from './type';

export function reducer<R>(
state: TaskState<R>,
action: TaskAction<R>
): TaskState<R> {
switch (action.type) {
case ACTION_TYPES.LOADING:
return { ...state, isLoading: true, error: null };
case ACTION_TYPES.SUCCESS:
return {
isLoading: false,
data: action.payload,
error: null,
};
case ACTION_TYPES.ERROR:
return { ...state, isLoading: false, error: action.payload };
case ACTION_TYPES.RESET:
return { isLoading: false, data: null, error: null };
default:
throw new Error('Unhandled task action type');
}
}
45 changes: 45 additions & 0 deletions src/hooks/useAsyncTasks/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { AsyncWaveOptions, PromiseCircularityError } from 'async-wave';

export type Task<R> = R | ((input: R) => R | Promise<R> | void | Promise<void>);

export type HookOptionProps = {
initialLazyDelay: number;
successLazyDelay: number;
};

export type HookOptions = {
options: Partial<HookOptionProps>;
};

export type SyncOnBefore = { onBefore: () => void };

export type Options<R> = Partial<
Omit<AsyncWaveOptions<R>, 'onBefore'> & SyncOnBefore & Partial<HookOptions>
>;

export type StateInfo<R> = {
isLoading: boolean;
data: R | null;
error: PromiseCircularityError | null;
isError: boolean;
reset: () => void;
};

export const ACTION_TYPES = {
LOADING: 'loading',
SUCCESS: 'success',
ERROR: 'error',
RESET: 'reset',
} as const;

export interface TaskState<R> {
isLoading: boolean;
data: R | null;
error: PromiseCircularityError | null;
}

export type TaskAction<R> =
| { type: typeof ACTION_TYPES.LOADING }
| { type: typeof ACTION_TYPES.SUCCESS; payload: R }
| { type: typeof ACTION_TYPES.ERROR; payload: PromiseCircularityError }
| { type: typeof ACTION_TYPES.RESET };
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,9 @@ import {
type AsyncWaveOptions,
PromiseCircularityError,
} from 'async-wave';

import { delayExecution } from '../utils';

type Task<R> = R | ((input: R) => R | Promise<R> | void | Promise<void>);

type HookOptionProps = {
initialLazyDelay: number;
successLazyDelay: number;
};

type HookOptions = {
options: Partial<HookOptionProps>;
};

type SyncOnBefore = { onBefore: () => void };

type Options<R> = Partial<
Omit<AsyncWaveOptions<R>, 'onBefore'> & SyncOnBefore & Partial<HookOptions>
>;

type StateInfo<R> = {
isLoading: boolean;
data: R | null;
error: PromiseCircularityError | null;
isError: boolean;
reset: () => void;
};
import { delayExecution } from '@/utils';
import { Task, Options, StateInfo, TaskAction, ACTION_TYPES } from './type';
import { reducer } from './reducer';

/**
* useAsyncTasks
Expand Down Expand Up @@ -69,7 +45,7 @@ const useAsyncTasks = <R>(tasks: Task<R>[], options: Options<R>) => {
isError: Boolean(state.error),
reset() {
dispatch({
type: ActionType.RESET,
type: ACTION_TYPES.RESET,
});
},
}),
Expand All @@ -95,7 +71,7 @@ const generateTaskHandlers = <R>(
async onBefore() {
if (!isMountedRef.current) return;

dispatch({ type: ActionType.LOADING });
dispatch({ type: ACTION_TYPES.LOADING });
options?.initialLazyDelay &&
(await delayExecution(options.initialLazyDelay).start());
onBefore?.();
Expand All @@ -105,14 +81,14 @@ const generateTaskHandlers = <R>(

options?.successLazyDelay &&
(await delayExecution(options.successLazyDelay).start());
dispatch({ type: ActionType.SUCCESS, payload });
dispatch({ type: ACTION_TYPES.SUCCESS, payload });
onSuccess?.(payload);
},
onError(error: PromiseCircularityError) {
if (!isMountedRef.current) return;

dispatch({
type: ActionType.ERROR,
type: ACTION_TYPES.ERROR,
payload: error,
});
onError?.(error);
Expand All @@ -127,48 +103,4 @@ const generateTaskHandlers = <R>(
return handler;
};

enum ActionType {
LOADING = 'loading',
SUCCESS = 'success',
ERROR = 'error',
RESET = 'reset',
}

type TaskState<R> = {
isLoading: boolean;
data: R | null;
error: PromiseCircularityError | null;
};

type TaskAction<R> =
| { type: ActionType.LOADING }
| { type: ActionType.SUCCESS; payload: R }
| { type: ActionType.ERROR; payload: PromiseCircularityError }
| { type: ActionType.RESET };

/**
* reducer 함수
* @param {TaskState<R>} state - 현재 상태
* @param {TaskAction<R>} action - 액션 객체
* @returns {TaskState<R>} 새로운 상태
*/
function reducer<R>(state: TaskState<R>, action: TaskAction<R>): TaskState<R> {
switch (action.type) {
case ActionType.LOADING:
return { ...state, isLoading: true, error: null };
case ActionType.SUCCESS:
return {
isLoading: false,
data: action.payload,
error: null,
};
case ActionType.ERROR:
return { ...state, isLoading: false, error: action.payload };
case ActionType.RESET:
return { isLoading: false, data: null, error: null };
default:
throw new Error('Unhandled task action type');
}
}

export default useAsyncTasks;
1 change: 1 addition & 0 deletions src/hooks/useClipboard/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as useClipboard } from './useClipboard';
File renamed without changes.
9 changes: 9 additions & 0 deletions src/hooks/useClipboard/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export interface UseClipboardProps {
resetTime?: number;
}

export interface UseClipboardReturns {
copied: boolean;
copyText: (text: string) => void;
copyImg: (path: string) => void;
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,6 @@
import { useState, useEffect } from 'react';
import { imgToBlob } from '../utils';

interface UseClipboardProps {
resetTime?: number;
}

interface UseClipboardReturns {
copied: boolean;
copyText: (text: string) => void;
copyImg: (path: string) => void;
}
import { imgToBlob } from '@/utils';
import { UseClipboardProps, UseClipboardReturns } from './type';

/**
* 클립보드에 텍스트나 이미지를 복사하는 커스텀 훅
Expand Down
1 change: 1 addition & 0 deletions src/hooks/useConfirm/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as useConfirm } from './useConfirm';
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useContext } from 'react';
import { ConfirmContext as context } from '../context/ConfirmContext';
import { ConfirmContext as context } from '@/context/ConfirmContext';

/**
* 컨펌 다이얼로그를 구현할 때 사용하는 훅.
Expand Down
1 change: 1 addition & 0 deletions src/hooks/useDebounce/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as useDebounce } from './useDebounce';
File renamed without changes.
1 change: 1 addition & 0 deletions src/hooks/useDebounce/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type GenericFn<T extends unknown[]> = (...args: T) => void;
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useCallback, useRef } from 'react';
import { GenericFn } from '../types';
import { GenericFn } from './type';

/**
* 주어진 콜백 함수를 디바운스하여, 일정 시간(delay)동안 값이 변경되지 않을 때까지 호출을 지연시키는 훅
Expand Down
1 change: 1 addition & 0 deletions src/hooks/useDeepCompareEffect/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as useDeepCompareEffect } from './useDeepCompareEffect';
1 change: 1 addition & 0 deletions src/hooks/useDelayFlag/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as useDelayFlag } from './useDelayFlag';
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { useState, useEffect, useRef } from 'react';
import { PositiveInteger } from '../types/number';

import { PositiveInteger } from './type';
/**
* 인자로 받은 플래그를 주어진 시간만큼 지연시키는 커스텀 훅.
*
Expand Down
1 change: 1 addition & 0 deletions src/hooks/useDetectDevice/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as useDetectDevice } from './useDetectDevice';
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { renderHook } from '@testing-library/react';
import useDetectDevice from './useDetectDevice';
import * as utils from '../utils';
import * as utils from '@/utils';

const mockUserAgent = (userAgent: string) => {
Object.defineProperty(navigator, 'userAgent', {
Expand Down
26 changes: 26 additions & 0 deletions src/hooks/useDetectDevice/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export interface UseDeviceDetectReturns {
isMobile: boolean;
isDesktop: boolean;
os: string;
browser: string;
}

export const DEVICE_PATTERNS = {
mobile: /Mobi/i,
};

export const OS_PATTERNS = {
windows: /Windows/i,
macOS: /Macintosh|Mac/i,
linux: /Linux/i,
android: /Android/i,
iOS: /iPhone|iPad|iPod/i,
};

export const BROWSER_PATTERNS = {
whale: /Whale/i,
edge: /Edg/i,
chrome: /Chrome/i,
safari: /Safari/i,
firefox: /Firefox/i,
};
Loading
Loading