@sonamu-kit/react-components 0.1.0 → 0.1.2
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/components/index.d.ts +65 -0
- package/dist/components/ui/accordion.d.ts +7 -0
- package/dist/components/ui/alert-dialog.d.ts +20 -0
- package/dist/components/ui/alert.d.ts +8 -0
- package/dist/components/ui/aspect-ratio.d.ts +3 -0
- package/dist/components/ui/async-select.d.ts +36 -0
- package/dist/components/ui/avatar.d.ts +6 -0
- package/dist/components/ui/badge.d.ts +9 -0
- package/dist/components/ui/breadcrumb.d.ts +19 -0
- package/dist/components/ui/button.d.ts +13 -0
- package/dist/components/ui/calendar.d.ts +5 -0
- package/dist/components/ui/card.d.ts +9 -0
- package/dist/components/ui/carousel.d.ts +18 -0
- package/dist/components/ui/checkbox.d.ts +8 -0
- package/dist/components/ui/collapsible.d.ts +5 -0
- package/dist/components/ui/combobox.d.ts +20 -0
- package/dist/components/ui/command.d.ts +80 -0
- package/dist/components/ui/common-modal.d.ts +28 -0
- package/dist/components/ui/context-menu.d.ts +27 -0
- package/dist/components/ui/date-input.d.ts +7 -0
- package/dist/components/ui/date-picker.d.ts +26 -0
- package/dist/components/ui/date-selector-multiple.d.ts +38 -0
- package/dist/components/ui/dialog.d.ts +19 -0
- package/dist/components/ui/drawer.d.ts +22 -0
- package/dist/components/ui/dropdown-menu.d.ts +27 -0
- package/dist/components/ui/form.d.ts +23 -0
- package/dist/components/ui/hover-card.d.ts +6 -0
- package/dist/components/ui/image-uploader.d.ts +14 -0
- package/dist/components/ui/input-otp.d.ts +34 -0
- package/dist/components/ui/input.d.ts +7 -0
- package/dist/components/ui/label.d.ts +5 -0
- package/dist/components/ui/menubar.d.ts +28 -0
- package/dist/components/ui/month-picker-multiple.d.ts +41 -0
- package/dist/components/ui/multi-image-uploader.d.ts +15 -0
- package/dist/components/ui/multi-select.d.ts +229 -0
- package/dist/components/ui/navigation-menu.d.ts +12 -0
- package/dist/components/ui/pagination.d.ts +10 -0
- package/dist/components/ui/popover.d.ts +7 -0
- package/dist/components/ui/progress.d.ts +4 -0
- package/dist/components/ui/radio-group.d.ts +5 -0
- package/dist/components/ui/resizable.d.ts +23 -0
- package/dist/components/ui/scroll-area.d.ts +5 -0
- package/dist/components/ui/select.d.ts +20 -0
- package/dist/components/ui/separator.d.ts +4 -0
- package/dist/components/ui/sheet.d.ts +25 -0
- package/dist/components/ui/sidebar.d.ts +69 -0
- package/dist/components/ui/skeleton.d.ts +2 -0
- package/dist/components/ui/slider.d.ts +8 -0
- package/dist/components/ui/sonner.d.ts +4 -0
- package/dist/components/ui/switch.d.ts +8 -0
- package/dist/components/ui/table.d.ts +24 -0
- package/dist/components/ui/tabs.d.ts +7 -0
- package/dist/components/ui/textarea.d.ts +7 -0
- package/dist/components/ui/toast.d.ts +15 -0
- package/dist/components/ui/toaster.d.ts +1 -0
- package/dist/components/ui/toggle-group.d.ts +12 -0
- package/dist/components/ui/toggle.d.ts +12 -0
- package/dist/components/ui/tooltip.d.ts +7 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/use-toast.d.ts +44 -0
- package/dist/index.d.ts +3 -0
- package/dist/lib/caster.d.ts +3 -0
- package/dist/lib/helpers.d.ts +72 -0
- package/dist/lib/index.d.ts +6 -0
- package/{src/lib/lazy-upload.ts → dist/lib/lazy-upload.d.ts} +1 -12
- package/dist/lib/use-mobile.d.ts +1 -0
- package/dist/lib/utils.d.ts +2 -0
- package/dist/react-components.es.js +28375 -0
- package/package.json +105 -76
- package/COMPONENTS_LIST.md +0 -106
- package/COMPONENTS_STATUS.md +0 -114
- package/HELPERS_GUIDE.md +0 -489
- package/MIGRATION_PLAN.md +0 -404
- package/SETUP_GUIDE.md +0 -125
- package/components.json +0 -21
- package/postcss.config.js +0 -6
- package/src/components/index.ts +0 -315
- package/src/components/ui/accordion.tsx +0 -54
- package/src/components/ui/alert-dialog.tsx +0 -115
- package/src/components/ui/alert.tsx +0 -49
- package/src/components/ui/aspect-ratio.tsx +0 -5
- package/src/components/ui/async-select.tsx +0 -186
- package/src/components/ui/avatar.tsx +0 -45
- package/src/components/ui/badge.tsx +0 -38
- package/src/components/ui/breadcrumb.tsx +0 -102
- package/src/components/ui/button.tsx +0 -54
- package/src/components/ui/calendar.tsx +0 -193
- package/src/components/ui/card.tsx +0 -65
- package/src/components/ui/carousel.tsx +0 -243
- package/src/components/ui/checkbox.tsx +0 -67
- package/src/components/ui/collapsible.tsx +0 -9
- package/src/components/ui/combobox.tsx +0 -135
- package/src/components/ui/command.tsx +0 -143
- package/src/components/ui/common-modal.tsx +0 -95
- package/src/components/ui/context-menu.tsx +0 -189
- package/src/components/ui/date-picker.tsx +0 -112
- package/src/components/ui/date-selector-multiple.tsx +0 -197
- package/src/components/ui/dialog.tsx +0 -104
- package/src/components/ui/drawer.tsx +0 -100
- package/src/components/ui/dropdown-menu.tsx +0 -189
- package/src/components/ui/form.tsx +0 -171
- package/src/components/ui/hover-card.tsx +0 -27
- package/src/components/ui/image-uploader.tsx +0 -251
- package/src/components/ui/input-otp.tsx +0 -69
- package/src/components/ui/input.tsx +0 -38
- package/src/components/ui/label.tsx +0 -19
- package/src/components/ui/menubar.tsx +0 -231
- package/src/components/ui/month-picker-multiple.tsx +0 -351
- package/src/components/ui/multi-image-uploader.tsx +0 -283
- package/src/components/ui/multi-select.tsx +0 -1143
- package/src/components/ui/navigation-menu.tsx +0 -120
- package/src/components/ui/pagination.tsx +0 -72
- package/src/components/ui/popover.tsx +0 -42
- package/src/components/ui/progress.tsx +0 -25
- package/src/components/ui/radio-group.tsx +0 -38
- package/src/components/ui/resizable.tsx +0 -42
- package/src/components/ui/scroll-area.tsx +0 -46
- package/src/components/ui/select.tsx +0 -235
- package/src/components/ui/separator.tsx +0 -24
- package/src/components/ui/sheet.tsx +0 -119
- package/src/components/ui/sidebar.tsx +0 -683
- package/src/components/ui/skeleton.tsx +0 -7
- package/src/components/ui/slider.tsx +0 -57
- package/src/components/ui/sonner.tsx +0 -39
- package/src/components/ui/switch.tsx +0 -63
- package/src/components/ui/table.tsx +0 -94
- package/src/components/ui/tabs.tsx +0 -53
- package/src/components/ui/textarea.tsx +0 -34
- package/src/components/ui/toast.tsx +0 -122
- package/src/components/ui/toaster.tsx +0 -29
- package/src/components/ui/toggle-group.tsx +0 -55
- package/src/components/ui/toggle.tsx +0 -41
- package/src/components/ui/tooltip.tsx +0 -28
- package/src/hooks/index.ts +0 -2
- package/src/hooks/use-toast.ts +0 -189
- package/src/icons.d.ts +0 -1
- package/src/index.ts +0 -4
- package/src/lib/caster.ts +0 -66
- package/src/lib/helpers.ts +0 -394
- package/src/lib/index.ts +0 -31
- package/src/lib/use-mobile.ts +0 -19
- package/src/lib/utils.ts +0 -6
- package/src/styles/globals.css +0 -658
- package/tailwind.config.ts +0 -8
- package/tsconfig.json +0 -31
- package/tsconfig.node.json +0 -11
package/src/hooks/use-toast.ts
DELETED
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
// Inspired by react-hot-toast library
|
|
4
|
-
import * as React from "react";
|
|
5
|
-
|
|
6
|
-
import type { ToastActionElement, ToastProps } from "../components/ui/toast";
|
|
7
|
-
|
|
8
|
-
const TOAST_LIMIT = 1;
|
|
9
|
-
const TOAST_REMOVE_DELAY = 1000000;
|
|
10
|
-
|
|
11
|
-
type ToasterToast = ToastProps & {
|
|
12
|
-
id: string;
|
|
13
|
-
title?: React.ReactNode;
|
|
14
|
-
description?: React.ReactNode;
|
|
15
|
-
action?: ToastActionElement;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const actionTypes = {
|
|
19
|
-
ADD_TOAST: "ADD_TOAST",
|
|
20
|
-
UPDATE_TOAST: "UPDATE_TOAST",
|
|
21
|
-
DISMISS_TOAST: "DISMISS_TOAST",
|
|
22
|
-
REMOVE_TOAST: "REMOVE_TOAST",
|
|
23
|
-
} as const;
|
|
24
|
-
|
|
25
|
-
let count = 0;
|
|
26
|
-
|
|
27
|
-
function genId() {
|
|
28
|
-
count = (count + 1) % Number.MAX_SAFE_INTEGER;
|
|
29
|
-
return count.toString();
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
type ActionType = typeof actionTypes;
|
|
33
|
-
|
|
34
|
-
type Action =
|
|
35
|
-
| {
|
|
36
|
-
type: ActionType["ADD_TOAST"];
|
|
37
|
-
toast: ToasterToast;
|
|
38
|
-
}
|
|
39
|
-
| {
|
|
40
|
-
type: ActionType["UPDATE_TOAST"];
|
|
41
|
-
toast: Partial<ToasterToast>;
|
|
42
|
-
}
|
|
43
|
-
| {
|
|
44
|
-
type: ActionType["DISMISS_TOAST"];
|
|
45
|
-
toastId?: ToasterToast["id"];
|
|
46
|
-
}
|
|
47
|
-
| {
|
|
48
|
-
type: ActionType["REMOVE_TOAST"];
|
|
49
|
-
toastId?: ToasterToast["id"];
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
interface State {
|
|
53
|
-
toasts: ToasterToast[];
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
|
|
57
|
-
|
|
58
|
-
const addToRemoveQueue = (toastId: string) => {
|
|
59
|
-
if (toastTimeouts.has(toastId)) {
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const timeout = setTimeout(() => {
|
|
64
|
-
toastTimeouts.delete(toastId);
|
|
65
|
-
dispatch({
|
|
66
|
-
type: "REMOVE_TOAST",
|
|
67
|
-
toastId: toastId,
|
|
68
|
-
});
|
|
69
|
-
}, TOAST_REMOVE_DELAY);
|
|
70
|
-
|
|
71
|
-
toastTimeouts.set(toastId, timeout);
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
export const reducer = (state: State, action: Action): State => {
|
|
75
|
-
switch (action.type) {
|
|
76
|
-
case "ADD_TOAST":
|
|
77
|
-
return {
|
|
78
|
-
...state,
|
|
79
|
-
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
case "UPDATE_TOAST":
|
|
83
|
-
return {
|
|
84
|
-
...state,
|
|
85
|
-
toasts: state.toasts.map((t) => (t.id === action.toast.id ? { ...t, ...action.toast } : t)),
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
case "DISMISS_TOAST": {
|
|
89
|
-
const { toastId } = action;
|
|
90
|
-
|
|
91
|
-
// ! Side effects ! - This could be extracted into a dismissToast() action,
|
|
92
|
-
// but I'll keep it here for simplicity
|
|
93
|
-
if (toastId) {
|
|
94
|
-
addToRemoveQueue(toastId);
|
|
95
|
-
} else {
|
|
96
|
-
state.toasts.forEach((toast) => {
|
|
97
|
-
addToRemoveQueue(toast.id);
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return {
|
|
102
|
-
...state,
|
|
103
|
-
toasts: state.toasts.map((t) =>
|
|
104
|
-
t.id === toastId || toastId === undefined
|
|
105
|
-
? {
|
|
106
|
-
...t,
|
|
107
|
-
open: false,
|
|
108
|
-
}
|
|
109
|
-
: t,
|
|
110
|
-
),
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
case "REMOVE_TOAST":
|
|
114
|
-
if (action.toastId === undefined) {
|
|
115
|
-
return {
|
|
116
|
-
...state,
|
|
117
|
-
toasts: [],
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
return {
|
|
121
|
-
...state,
|
|
122
|
-
toasts: state.toasts.filter((t) => t.id !== action.toastId),
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
const listeners: Array<(state: State) => void> = [];
|
|
128
|
-
|
|
129
|
-
let memoryState: State = { toasts: [] };
|
|
130
|
-
|
|
131
|
-
function dispatch(action: Action) {
|
|
132
|
-
memoryState = reducer(memoryState, action);
|
|
133
|
-
listeners.forEach((listener) => {
|
|
134
|
-
listener(memoryState);
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
type Toast = Omit<ToasterToast, "id">;
|
|
139
|
-
|
|
140
|
-
function toast({ ...props }: Toast) {
|
|
141
|
-
const id = genId();
|
|
142
|
-
|
|
143
|
-
const update = (props: ToasterToast) =>
|
|
144
|
-
dispatch({
|
|
145
|
-
type: "UPDATE_TOAST",
|
|
146
|
-
toast: { ...props, id },
|
|
147
|
-
});
|
|
148
|
-
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
|
|
149
|
-
|
|
150
|
-
dispatch({
|
|
151
|
-
type: "ADD_TOAST",
|
|
152
|
-
toast: {
|
|
153
|
-
...props,
|
|
154
|
-
id,
|
|
155
|
-
open: true,
|
|
156
|
-
onOpenChange: (open) => {
|
|
157
|
-
if (!open) dismiss();
|
|
158
|
-
},
|
|
159
|
-
},
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
return {
|
|
163
|
-
id: id,
|
|
164
|
-
dismiss,
|
|
165
|
-
update,
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function useToast() {
|
|
170
|
-
const [state, setState] = React.useState<State>(memoryState);
|
|
171
|
-
|
|
172
|
-
React.useEffect(() => {
|
|
173
|
-
listeners.push(setState);
|
|
174
|
-
return () => {
|
|
175
|
-
const index = listeners.indexOf(setState);
|
|
176
|
-
if (index > -1) {
|
|
177
|
-
listeners.splice(index, 1);
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
}, [state]);
|
|
181
|
-
|
|
182
|
-
return {
|
|
183
|
-
...state,
|
|
184
|
-
toast,
|
|
185
|
-
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
export { useToast, toast };
|
package/src/icons.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
/// <reference types="unplugin-icons/types/react" />
|
package/src/index.ts
DELETED
package/src/lib/caster.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
/** biome-ignore-all lint/suspicious/noExplicitAny: caster 함수에는 any가 필요함 */
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
|
|
4
|
-
// optional, nullable 무관하게 ZodNumber 체크
|
|
5
|
-
function isZodNumberAnyway(zodType: z.ZodType<any>) {
|
|
6
|
-
if (zodType instanceof z.ZodNumber) {
|
|
7
|
-
return true;
|
|
8
|
-
} else if (zodType instanceof z.ZodNullable && zodType._def.innerType instanceof z.ZodNumber) {
|
|
9
|
-
return true;
|
|
10
|
-
} else if (zodType instanceof z.ZodOptional && zodType._def.innerType instanceof z.ZodNumber) {
|
|
11
|
-
return true;
|
|
12
|
-
} else if (
|
|
13
|
-
zodType instanceof z.ZodOptional &&
|
|
14
|
-
zodType._def.innerType instanceof z.ZodOptional &&
|
|
15
|
-
(zodType._def.innerType as any)._def.innerType instanceof z.ZodNumber
|
|
16
|
-
) {
|
|
17
|
-
return true;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return false;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// ZodType을 이용해 raw를 Type Coercing
|
|
24
|
-
export function caster(zodType: z.ZodType<any>, raw: any): any {
|
|
25
|
-
if (isZodNumberAnyway(zodType) && typeof raw === "string") {
|
|
26
|
-
// number
|
|
27
|
-
return Number(raw);
|
|
28
|
-
} else if (zodType instanceof z.ZodUnion) {
|
|
29
|
-
const options = zodType.options as z.ZodType<any>[];
|
|
30
|
-
if (options.some((opt) => isZodNumberAnyway(opt))) {
|
|
31
|
-
// zArrayable Number 케이스 처리
|
|
32
|
-
if (Array.isArray(raw)) {
|
|
33
|
-
const numType = options.find((opt) => opt instanceof z.ZodNumber);
|
|
34
|
-
if (!numType) {
|
|
35
|
-
throw new Error("Expected to find a number type in union");
|
|
36
|
-
}
|
|
37
|
-
return raw.map((elem: any) => caster(numType, elem));
|
|
38
|
-
} else {
|
|
39
|
-
return Number(raw);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
} else if (zodType instanceof z.ZodBoolean && (raw === "true" || raw === "false")) {
|
|
43
|
-
// boolean
|
|
44
|
-
return raw === "true";
|
|
45
|
-
} else if (zodType instanceof z.ZodArray) {
|
|
46
|
-
// array
|
|
47
|
-
return raw.map((elem: any) => caster(zodType.element as z.ZodType<any>, elem));
|
|
48
|
-
} else if (zodType instanceof z.ZodObject && typeof raw === "object") {
|
|
49
|
-
// object
|
|
50
|
-
return Object.keys(raw).reduce((r, rawKey) => {
|
|
51
|
-
r[rawKey] = caster(zodType.shape[rawKey], raw[rawKey]);
|
|
52
|
-
return r;
|
|
53
|
-
}, {} as any);
|
|
54
|
-
} else if (zodType instanceof z.ZodOptional) {
|
|
55
|
-
return caster(zodType._def.innerType as z.ZodType<any>, raw);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// 나머지는 처리 안함
|
|
59
|
-
return raw;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function fastifyCaster(schema: z.ZodObject<any>) {
|
|
63
|
-
return z.preprocess((raw: any) => {
|
|
64
|
-
return caster(schema, raw);
|
|
65
|
-
}, schema);
|
|
66
|
-
}
|
package/src/lib/helpers.ts
DELETED
|
@@ -1,394 +0,0 @@
|
|
|
1
|
-
/** biome-ignore-all lint/suspicious/noExplicitAny: 파싱 결과이므로 any 허용 */
|
|
2
|
-
|
|
3
|
-
import { useNavigate, useRouter, useSearch } from "@tanstack/react-router";
|
|
4
|
-
import equal from "fast-deep-equal";
|
|
5
|
-
import qs from "qs";
|
|
6
|
-
import { get, isObject, set, unique } from "radashi";
|
|
7
|
-
import React, { type ReactElement, useEffect, useState } from "react";
|
|
8
|
-
import { z } from "zod";
|
|
9
|
-
import { caster } from "./caster";
|
|
10
|
-
|
|
11
|
-
// radashi에 intersection이 없으므로 직접 구현
|
|
12
|
-
function intersection<T>(arr1: T[], arr2: T[]): T[] {
|
|
13
|
-
return arr1.filter((item) => arr2.includes(item));
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// shadcn/ui용 타입 정의 (semantic-ui-react 대체)
|
|
17
|
-
export type PaginationProps = {
|
|
18
|
-
activePage?: number;
|
|
19
|
-
totalPages?: number;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export type TableColumnWidth = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16;
|
|
23
|
-
|
|
24
|
-
export function hidden(condition: boolean | undefined): string {
|
|
25
|
-
return condition === true ? "hidden" : "";
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function searchParamsToParams<T extends z.ZodType<any>>(
|
|
29
|
-
searchParams: URLSearchParams,
|
|
30
|
-
paramsSchema: T,
|
|
31
|
-
): z.infer<T> {
|
|
32
|
-
const obj = qs.parse(searchParams.toString());
|
|
33
|
-
return caster(paramsSchema, obj);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function paramsToSearchParams<T>(params: T): {
|
|
37
|
-
[key in string]: string | string[];
|
|
38
|
-
} {
|
|
39
|
-
return Object.fromEntries(
|
|
40
|
-
// biome-ignore lint/complexity/useFlatMap: 여기는 flatMap 사용하면 깨짐
|
|
41
|
-
Object.entries(params as any)
|
|
42
|
-
.filter(([, value]) => {
|
|
43
|
-
return value !== undefined;
|
|
44
|
-
})
|
|
45
|
-
.map(([key, value]) => {
|
|
46
|
-
if (Array.isArray(value)) {
|
|
47
|
-
return [[`${key}[]`, value]];
|
|
48
|
-
} else if (isObject(value)) {
|
|
49
|
-
return Object.keys(value).map((subKey) => {
|
|
50
|
-
return [`${key}[${subKey}]`, String(value[subKey as keyof typeof value])];
|
|
51
|
-
});
|
|
52
|
-
} else {
|
|
53
|
-
return [[key, String(value)]];
|
|
54
|
-
}
|
|
55
|
-
})
|
|
56
|
-
.flat(),
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export type ErrorObj = {
|
|
61
|
-
content: string;
|
|
62
|
-
pointing?: "above" | "below" | "left" | "right";
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
export function useTypeForm<T extends z.ZodObject<any> | z.ZodArray<any>, U extends z.infer<T>>(
|
|
66
|
-
zType: T,
|
|
67
|
-
defaultValue: U,
|
|
68
|
-
) {
|
|
69
|
-
const [form, setForm] = useState<z.infer<T>>(defaultValue);
|
|
70
|
-
const [errorObjs, setErrorObjs] = useState<Map<string, ErrorObj>>(new Map());
|
|
71
|
-
|
|
72
|
-
function getEmptyStringTo(zType: T, objPath: string): "normal" | "nullable" | "optional" {
|
|
73
|
-
const zTypeObjPath = objPath
|
|
74
|
-
.replace(/\./g, ".shape.")
|
|
75
|
-
.replace(/\[[^\]]+\]/g, ".element")
|
|
76
|
-
.replace(/^\.element/, "element");
|
|
77
|
-
|
|
78
|
-
let targetZType: unknown;
|
|
79
|
-
if (zType instanceof z.ZodObject) {
|
|
80
|
-
targetZType = get(zType.shape, zTypeObjPath);
|
|
81
|
-
} else if (zType instanceof z.ZodArray) {
|
|
82
|
-
targetZType = get(zType, zTypeObjPath);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (targetZType === undefined) {
|
|
86
|
-
return "normal";
|
|
87
|
-
} else if (targetZType instanceof z.ZodOptional) {
|
|
88
|
-
return "optional";
|
|
89
|
-
} else if (targetZType instanceof z.ZodNullable) {
|
|
90
|
-
return "nullable";
|
|
91
|
-
}
|
|
92
|
-
return "normal";
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return {
|
|
96
|
-
form,
|
|
97
|
-
setForm,
|
|
98
|
-
register: (objPath: string, _emptyStringTo?: "normal" | "nullable" | "optional"): any => {
|
|
99
|
-
const emptyStringTo = _emptyStringTo ?? getEmptyStringTo(zType, objPath);
|
|
100
|
-
const srcValue = get(form, objPath) as unknown;
|
|
101
|
-
|
|
102
|
-
const error = errorObjs.get(objPath);
|
|
103
|
-
|
|
104
|
-
// 공통 업데이트 로직
|
|
105
|
-
const updateValue = (newValue: any) => {
|
|
106
|
-
if (error !== undefined) {
|
|
107
|
-
setErrorObjs((p) => {
|
|
108
|
-
const newP = new Map(p);
|
|
109
|
-
newP.delete(objPath);
|
|
110
|
-
return newP;
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
let processedValue = newValue;
|
|
115
|
-
if (emptyStringTo === "nullable") {
|
|
116
|
-
processedValue = newValue === "" ? null : newValue;
|
|
117
|
-
} else if (emptyStringTo === "optional") {
|
|
118
|
-
processedValue = newValue === "" ? undefined : newValue;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
setForm(set(form, objPath, processedValue));
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
const result: Record<string, any> = {
|
|
125
|
-
value: srcValue === undefined || srcValue === null ? "" : srcValue,
|
|
126
|
-
onValueChange: (value: any) => updateValue(value),
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
// error가 있으면 추가
|
|
130
|
-
if (error) {
|
|
131
|
-
result.error = error;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return result;
|
|
135
|
-
},
|
|
136
|
-
addError: (objPath: string, errorMessage: string | ErrorObj): void => {
|
|
137
|
-
setErrorObjs((p) => {
|
|
138
|
-
const newP = new Map(p);
|
|
139
|
-
newP.set(
|
|
140
|
-
objPath,
|
|
141
|
-
typeof errorMessage === "string" ? { content: errorMessage } : errorMessage,
|
|
142
|
-
);
|
|
143
|
-
return newP;
|
|
144
|
-
});
|
|
145
|
-
},
|
|
146
|
-
removeError: (objPath: string): void => {
|
|
147
|
-
setErrorObjs((p) => {
|
|
148
|
-
const newP = new Map(p);
|
|
149
|
-
newP.delete(objPath);
|
|
150
|
-
return newP;
|
|
151
|
-
});
|
|
152
|
-
},
|
|
153
|
-
clearError: (): void => {
|
|
154
|
-
setErrorObjs(new Map());
|
|
155
|
-
},
|
|
156
|
-
reset: (): void => {
|
|
157
|
-
setForm(defaultValue);
|
|
158
|
-
},
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
type ZodKeys<T extends z.ZodType<any>> = keyof z.infer<T>;
|
|
163
|
-
|
|
164
|
-
export function useListParams<U extends z.ZodType<any>, T extends Partial<z.infer<U>>>(
|
|
165
|
-
zType: U,
|
|
166
|
-
defaultValue: T,
|
|
167
|
-
options?: {
|
|
168
|
-
disableSearchParams?: boolean;
|
|
169
|
-
},
|
|
170
|
-
) {
|
|
171
|
-
const search = useSearch({ strict: false }) as Record<string, unknown>;
|
|
172
|
-
const navigate = useNavigate();
|
|
173
|
-
|
|
174
|
-
// URL에서 파라미터 파싱
|
|
175
|
-
const listParams = (() => {
|
|
176
|
-
if (options?.disableSearchParams) {
|
|
177
|
-
return defaultValue;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const parsed = zType.safeParse(search);
|
|
181
|
-
if (parsed.success) {
|
|
182
|
-
return { ...defaultValue, ...parsed.data };
|
|
183
|
-
}
|
|
184
|
-
return defaultValue;
|
|
185
|
-
})() as T;
|
|
186
|
-
|
|
187
|
-
const setListParams = (newParams: T) => {
|
|
188
|
-
if (equal(listParams, newParams)) {
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
navigate({
|
|
193
|
-
search: newParams as any,
|
|
194
|
-
});
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
// 함수 오버로드
|
|
198
|
-
function register(name: "page"): { value: number; onValueChange: (page: number) => void };
|
|
199
|
-
function register(name: Exclude<ZodKeys<U>, "page">): {
|
|
200
|
-
value: string;
|
|
201
|
-
onValueChange: (value: any) => void;
|
|
202
|
-
};
|
|
203
|
-
function register(name: ZodKeys<U>) {
|
|
204
|
-
if (name === "page") {
|
|
205
|
-
return {
|
|
206
|
-
value: (listParams as any).page ?? 1,
|
|
207
|
-
onValueChange: (page: number) => {
|
|
208
|
-
setListParams({ ...listParams, page } as T);
|
|
209
|
-
},
|
|
210
|
-
};
|
|
211
|
-
} else {
|
|
212
|
-
const currentValue = (listParams as any)[name];
|
|
213
|
-
return {
|
|
214
|
-
value: currentValue ?? "",
|
|
215
|
-
onValueChange: (value: any) => {
|
|
216
|
-
setListParams({
|
|
217
|
-
...listParams,
|
|
218
|
-
page: 1,
|
|
219
|
-
[name]: value === "" ? undefined : value,
|
|
220
|
-
} as T);
|
|
221
|
-
},
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
return {
|
|
227
|
-
listParams,
|
|
228
|
-
setListParams,
|
|
229
|
-
register,
|
|
230
|
-
};
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
export function useGoBack() {
|
|
234
|
-
const router = useRouter();
|
|
235
|
-
|
|
236
|
-
const goBack = () => {
|
|
237
|
-
router.history.back();
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
return { goBack };
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
export function useSelection<T>(allKeys: T[], defaultSelectedKeys: T[] = []) {
|
|
244
|
-
const [selection, setSelection] = useState<Map<T, boolean>>(
|
|
245
|
-
new Map(allKeys.map((key) => [key, defaultSelectedKeys.includes(key)])),
|
|
246
|
-
);
|
|
247
|
-
const [lastIndex, setLastIndex] = useState<number>(0);
|
|
248
|
-
|
|
249
|
-
// 전체 키가 바뀔 때마다 validation하여 갱신된 전체 키에 포함된 키만 유지
|
|
250
|
-
useEffect(() => {
|
|
251
|
-
const selectionKeys = Array.from(selection.keys());
|
|
252
|
-
if (intersection(allKeys, selectionKeys).length === selectionKeys.length) {
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
setSelection(new Map(Array.from(selection).filter(([key, _value]) => allKeys.includes(key))));
|
|
257
|
-
}, [allKeys, selection]);
|
|
258
|
-
|
|
259
|
-
const selectedKeys = Array.from(selection)
|
|
260
|
-
.filter(([key, value]) => allKeys.includes(key) && value === true)
|
|
261
|
-
.map(([key]) => key);
|
|
262
|
-
|
|
263
|
-
return {
|
|
264
|
-
getSelected: (key: T) => selection.get(key) ?? false,
|
|
265
|
-
toggle: (key: T) => {
|
|
266
|
-
setSelection((selection) => {
|
|
267
|
-
return new Map([...selection, [key, !(selection.get(key) ?? false)]]);
|
|
268
|
-
});
|
|
269
|
-
},
|
|
270
|
-
selectedKeys,
|
|
271
|
-
deselectAll: () => setSelection(new Map(allKeys.map((key) => [key, false]))),
|
|
272
|
-
selectAll: () => setSelection(new Map(allKeys.map((key) => [key, true]))),
|
|
273
|
-
isAllSelected: selectedKeys.length === allKeys.length,
|
|
274
|
-
handleCheckboxClick: (e: React.MouseEvent<HTMLInputElement, MouseEvent>, index: number) => {
|
|
275
|
-
const input = e.currentTarget.getElementsByTagName("input");
|
|
276
|
-
if (e.shiftKey && input[0]?.checked === false) {
|
|
277
|
-
const [begin, end] = (() => {
|
|
278
|
-
if (lastIndex < index) {
|
|
279
|
-
return [lastIndex, index];
|
|
280
|
-
} else {
|
|
281
|
-
return [index + 1, lastIndex];
|
|
282
|
-
}
|
|
283
|
-
})();
|
|
284
|
-
setSelection(
|
|
285
|
-
new Map(unique([...selectedKeys, ...allKeys.slice(begin, end)]).map((k: T) => [k, true])),
|
|
286
|
-
);
|
|
287
|
-
} else {
|
|
288
|
-
setLastIndex(index);
|
|
289
|
-
}
|
|
290
|
-
},
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
export function sqlDateToDateString(sqlDateString: string | null) {
|
|
295
|
-
if (sqlDateString === null) {
|
|
296
|
-
return null;
|
|
297
|
-
} else {
|
|
298
|
-
return sqlDateString.slice(0, 10);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
export function numF(num: number | null | undefined): string | number | undefined | null {
|
|
303
|
-
return num && new Intl.NumberFormat().format(num);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
export function dateF(dateValue: string | Date | null | undefined): string | null {
|
|
307
|
-
if (dateValue === null || dateValue === undefined) {
|
|
308
|
-
return null;
|
|
309
|
-
} else if (dateValue instanceof Date) {
|
|
310
|
-
return dateValue.toISOString().slice(0, 10);
|
|
311
|
-
} else {
|
|
312
|
-
return dateValue.slice(0, 10);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
export function datetimeF(dateValue: string | Date | null | undefined): string | null {
|
|
316
|
-
if (dateValue === null || dateValue === undefined) {
|
|
317
|
-
return null;
|
|
318
|
-
} else if (dateValue instanceof Date) {
|
|
319
|
-
return dateValue.toISOString().slice(0, 19).replace("T", " ");
|
|
320
|
-
} else {
|
|
321
|
-
return dateValue.slice(0, 19);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
export function arrayableToArray<T extends number | string | boolean>(
|
|
326
|
-
val: T | T[] | undefined,
|
|
327
|
-
): T[] {
|
|
328
|
-
return val ? (Array.isArray(val) ? val : [val]) : [];
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
export type ControlledModalProps = {
|
|
332
|
-
open: boolean;
|
|
333
|
-
close: () => void;
|
|
334
|
-
};
|
|
335
|
-
|
|
336
|
-
export function useModal<T extends object>(
|
|
337
|
-
ModalComponent: (props: T & ControlledModalProps) => React.ReactElement,
|
|
338
|
-
defaultProps: T,
|
|
339
|
-
) {
|
|
340
|
-
const [modalProps, setModalProps] = useState<T & { open: boolean }>({
|
|
341
|
-
...defaultProps,
|
|
342
|
-
open: false,
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
const close = () => {
|
|
346
|
-
setModalProps({
|
|
347
|
-
...modalProps,
|
|
348
|
-
open: false,
|
|
349
|
-
});
|
|
350
|
-
};
|
|
351
|
-
|
|
352
|
-
return {
|
|
353
|
-
open: (newProps: T) => {
|
|
354
|
-
setModalProps({
|
|
355
|
-
...newProps,
|
|
356
|
-
open: true,
|
|
357
|
-
close,
|
|
358
|
-
});
|
|
359
|
-
},
|
|
360
|
-
modal: React.createElement(ModalComponent, {
|
|
361
|
-
...modalProps,
|
|
362
|
-
close,
|
|
363
|
-
}),
|
|
364
|
-
};
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
export function caller<T extends Function>() {
|
|
368
|
-
let savedFunc: T | null = null;
|
|
369
|
-
return {
|
|
370
|
-
set: (func: T) => {
|
|
371
|
-
savedFunc = func;
|
|
372
|
-
},
|
|
373
|
-
call: ((...args: unknown[]) => {
|
|
374
|
-
if (savedFunc) {
|
|
375
|
-
savedFunc.call(args);
|
|
376
|
-
}
|
|
377
|
-
}) as unknown as T,
|
|
378
|
-
};
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
export type SonamuCol<T> = {
|
|
382
|
-
label: string;
|
|
383
|
-
th?: ReactElement;
|
|
384
|
-
tc: (row: T, index: number) => ReactElement;
|
|
385
|
-
className?: string;
|
|
386
|
-
collapsing?: boolean;
|
|
387
|
-
width?: TableColumnWidth;
|
|
388
|
-
hidden?: boolean;
|
|
389
|
-
parentLabel?: string;
|
|
390
|
-
};
|
|
391
|
-
|
|
392
|
-
export type DistributiveOmit<T, K extends keyof any> = T extends any ? Omit<T, K> : never;
|
|
393
|
-
|
|
394
|
-
export type Override<T, U> = Omit<T, keyof U> & U;
|
package/src/lib/index.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
export { Pagination } from "../components/ui/pagination";
|
|
2
|
-
export { caster, fastifyCaster } from "./caster";
|
|
3
|
-
export {
|
|
4
|
-
arrayableToArray,
|
|
5
|
-
// Types
|
|
6
|
-
type ControlledModalProps,
|
|
7
|
-
caller,
|
|
8
|
-
type DistributiveOmit,
|
|
9
|
-
dateF,
|
|
10
|
-
datetimeF,
|
|
11
|
-
type ErrorObj,
|
|
12
|
-
// Utilities
|
|
13
|
-
hidden,
|
|
14
|
-
numF,
|
|
15
|
-
type Override,
|
|
16
|
-
type PaginationProps,
|
|
17
|
-
paramsToSearchParams,
|
|
18
|
-
type SonamuCol,
|
|
19
|
-
searchParamsToParams,
|
|
20
|
-
sqlDateToDateString,
|
|
21
|
-
type TableColumnWidth,
|
|
22
|
-
// Navigation & Selection
|
|
23
|
-
useGoBack,
|
|
24
|
-
useListParams,
|
|
25
|
-
useModal,
|
|
26
|
-
useSelection,
|
|
27
|
-
useTypeForm,
|
|
28
|
-
} from "./helpers";
|
|
29
|
-
export { lazyUpload } from "./lazy-upload";
|
|
30
|
-
export { useIsMobile } from "./use-mobile";
|
|
31
|
-
export { cn } from "./utils";
|
package/src/lib/use-mobile.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
|
|
3
|
-
const MOBILE_BREAKPOINT = 768;
|
|
4
|
-
|
|
5
|
-
export function useIsMobile() {
|
|
6
|
-
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined);
|
|
7
|
-
|
|
8
|
-
React.useEffect(() => {
|
|
9
|
-
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
|
|
10
|
-
const onChange = () => {
|
|
11
|
-
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
|
12
|
-
};
|
|
13
|
-
mql.addEventListener("change", onChange);
|
|
14
|
-
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
|
15
|
-
return () => mql.removeEventListener("change", onChange);
|
|
16
|
-
}, []);
|
|
17
|
-
|
|
18
|
-
return !!isMobile;
|
|
19
|
-
}
|