@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
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import useEmblaCarousel, { type UseEmblaCarouselType } from "embla-carousel-react";
|
|
4
|
-
import * as React from "react";
|
|
5
|
-
import ArrowLeftIcon from "~icons/lucide/arrow-left";
|
|
6
|
-
import ArrowRightIcon from "~icons/lucide/arrow-right";
|
|
7
|
-
|
|
8
|
-
import { cn } from "../../lib/utils";
|
|
9
|
-
import { Button } from "./button";
|
|
10
|
-
|
|
11
|
-
type CarouselApi = UseEmblaCarouselType[1];
|
|
12
|
-
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
|
|
13
|
-
type CarouselOptions = UseCarouselParameters[0];
|
|
14
|
-
type CarouselPlugin = UseCarouselParameters[1];
|
|
15
|
-
|
|
16
|
-
type CarouselProps = {
|
|
17
|
-
opts?: CarouselOptions;
|
|
18
|
-
plugins?: CarouselPlugin;
|
|
19
|
-
orientation?: "horizontal" | "vertical";
|
|
20
|
-
setApi?: (api: CarouselApi) => void;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
type CarouselContextProps = {
|
|
24
|
-
carouselRef: ReturnType<typeof useEmblaCarousel>[0];
|
|
25
|
-
api: ReturnType<typeof useEmblaCarousel>[1];
|
|
26
|
-
scrollPrev: () => void;
|
|
27
|
-
scrollNext: () => void;
|
|
28
|
-
canScrollPrev: boolean;
|
|
29
|
-
canScrollNext: boolean;
|
|
30
|
-
} & CarouselProps;
|
|
31
|
-
|
|
32
|
-
const CarouselContext = React.createContext<CarouselContextProps | null>(null);
|
|
33
|
-
|
|
34
|
-
function useCarousel() {
|
|
35
|
-
const context = React.useContext(CarouselContext);
|
|
36
|
-
|
|
37
|
-
if (!context) {
|
|
38
|
-
throw new Error("useCarousel must be used within a <Carousel />");
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return context;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const Carousel = React.forwardRef<
|
|
45
|
-
HTMLDivElement,
|
|
46
|
-
React.HTMLAttributes<HTMLDivElement> & CarouselProps
|
|
47
|
-
>(({ orientation = "horizontal", opts, setApi, plugins, className, children, ...props }, ref) => {
|
|
48
|
-
const [carouselRef, api] = useEmblaCarousel(
|
|
49
|
-
{
|
|
50
|
-
...opts,
|
|
51
|
-
axis: orientation === "horizontal" ? "x" : "y",
|
|
52
|
-
},
|
|
53
|
-
plugins,
|
|
54
|
-
);
|
|
55
|
-
const [canScrollPrev, setCanScrollPrev] = React.useState(false);
|
|
56
|
-
const [canScrollNext, setCanScrollNext] = React.useState(false);
|
|
57
|
-
|
|
58
|
-
const onSelect = React.useCallback((api: CarouselApi) => {
|
|
59
|
-
if (!api) {
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
setCanScrollPrev(api.canScrollPrev());
|
|
64
|
-
setCanScrollNext(api.canScrollNext());
|
|
65
|
-
}, []);
|
|
66
|
-
|
|
67
|
-
const scrollPrev = React.useCallback(() => {
|
|
68
|
-
api?.scrollPrev();
|
|
69
|
-
}, [api]);
|
|
70
|
-
|
|
71
|
-
const scrollNext = React.useCallback(() => {
|
|
72
|
-
api?.scrollNext();
|
|
73
|
-
}, [api]);
|
|
74
|
-
|
|
75
|
-
const handleKeyDown = React.useCallback(
|
|
76
|
-
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
|
77
|
-
if (event.key === "ArrowLeft") {
|
|
78
|
-
event.preventDefault();
|
|
79
|
-
scrollPrev();
|
|
80
|
-
} else if (event.key === "ArrowRight") {
|
|
81
|
-
event.preventDefault();
|
|
82
|
-
scrollNext();
|
|
83
|
-
}
|
|
84
|
-
},
|
|
85
|
-
[scrollPrev, scrollNext],
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
React.useEffect(() => {
|
|
89
|
-
if (!api || !setApi) {
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
setApi(api);
|
|
94
|
-
}, [api, setApi]);
|
|
95
|
-
|
|
96
|
-
React.useEffect(() => {
|
|
97
|
-
if (!api) {
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
onSelect(api);
|
|
102
|
-
api.on("reInit", onSelect);
|
|
103
|
-
api.on("select", onSelect);
|
|
104
|
-
|
|
105
|
-
return () => {
|
|
106
|
-
api?.off("select", onSelect);
|
|
107
|
-
};
|
|
108
|
-
}, [api, onSelect]);
|
|
109
|
-
|
|
110
|
-
return (
|
|
111
|
-
<CarouselContext.Provider
|
|
112
|
-
value={{
|
|
113
|
-
carouselRef,
|
|
114
|
-
api: api,
|
|
115
|
-
opts,
|
|
116
|
-
orientation: orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
|
|
117
|
-
scrollPrev,
|
|
118
|
-
scrollNext,
|
|
119
|
-
canScrollPrev,
|
|
120
|
-
canScrollNext,
|
|
121
|
-
}}
|
|
122
|
-
>
|
|
123
|
-
<div
|
|
124
|
-
ref={ref}
|
|
125
|
-
onKeyDownCapture={handleKeyDown}
|
|
126
|
-
className={cn("relative", className)}
|
|
127
|
-
role="region"
|
|
128
|
-
aria-roledescription="carousel"
|
|
129
|
-
{...props}
|
|
130
|
-
>
|
|
131
|
-
{children}
|
|
132
|
-
</div>
|
|
133
|
-
</CarouselContext.Provider>
|
|
134
|
-
);
|
|
135
|
-
});
|
|
136
|
-
Carousel.displayName = "Carousel";
|
|
137
|
-
|
|
138
|
-
const CarouselContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
139
|
-
({ className, ...props }, ref) => {
|
|
140
|
-
const { carouselRef, orientation } = useCarousel();
|
|
141
|
-
|
|
142
|
-
return (
|
|
143
|
-
<div ref={carouselRef} className="overflow-hidden">
|
|
144
|
-
<div
|
|
145
|
-
ref={ref}
|
|
146
|
-
className={cn(
|
|
147
|
-
"flex",
|
|
148
|
-
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
|
|
149
|
-
className,
|
|
150
|
-
)}
|
|
151
|
-
{...props}
|
|
152
|
-
/>
|
|
153
|
-
</div>
|
|
154
|
-
);
|
|
155
|
-
},
|
|
156
|
-
);
|
|
157
|
-
CarouselContent.displayName = "CarouselContent";
|
|
158
|
-
|
|
159
|
-
const CarouselItem = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
160
|
-
({ className, ...props }, ref) => {
|
|
161
|
-
const { orientation } = useCarousel();
|
|
162
|
-
|
|
163
|
-
return (
|
|
164
|
-
<div
|
|
165
|
-
ref={ref}
|
|
166
|
-
role="group"
|
|
167
|
-
aria-roledescription="slide"
|
|
168
|
-
className={cn(
|
|
169
|
-
"min-w-0 shrink-0 grow-0 basis-full",
|
|
170
|
-
orientation === "horizontal" ? "pl-4" : "pt-4",
|
|
171
|
-
className,
|
|
172
|
-
)}
|
|
173
|
-
{...props}
|
|
174
|
-
/>
|
|
175
|
-
);
|
|
176
|
-
},
|
|
177
|
-
);
|
|
178
|
-
CarouselItem.displayName = "CarouselItem";
|
|
179
|
-
|
|
180
|
-
const CarouselPrevious = React.forwardRef<HTMLButtonElement, React.ComponentProps<typeof Button>>(
|
|
181
|
-
({ className, variant = "outline", size = "icon", ...props }, ref) => {
|
|
182
|
-
const { orientation, scrollPrev, canScrollPrev } = useCarousel();
|
|
183
|
-
|
|
184
|
-
return (
|
|
185
|
-
<Button
|
|
186
|
-
ref={ref}
|
|
187
|
-
variant={variant}
|
|
188
|
-
size={size}
|
|
189
|
-
className={cn(
|
|
190
|
-
"absolute h-8 w-8 rounded-full",
|
|
191
|
-
orientation === "horizontal"
|
|
192
|
-
? "-left-12 top-1/2 -translate-y-1/2"
|
|
193
|
-
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
|
|
194
|
-
className,
|
|
195
|
-
)}
|
|
196
|
-
disabled={!canScrollPrev}
|
|
197
|
-
onClick={scrollPrev}
|
|
198
|
-
{...props}
|
|
199
|
-
>
|
|
200
|
-
<ArrowLeftIcon className="h-4 w-4" />
|
|
201
|
-
<span className="sr-only">Previous slide</span>
|
|
202
|
-
</Button>
|
|
203
|
-
);
|
|
204
|
-
},
|
|
205
|
-
);
|
|
206
|
-
CarouselPrevious.displayName = "CarouselPrevious";
|
|
207
|
-
|
|
208
|
-
const CarouselNext = React.forwardRef<HTMLButtonElement, React.ComponentProps<typeof Button>>(
|
|
209
|
-
({ className, variant = "outline", size = "icon", ...props }, ref) => {
|
|
210
|
-
const { orientation, scrollNext, canScrollNext } = useCarousel();
|
|
211
|
-
|
|
212
|
-
return (
|
|
213
|
-
<Button
|
|
214
|
-
ref={ref}
|
|
215
|
-
variant={variant}
|
|
216
|
-
size={size}
|
|
217
|
-
className={cn(
|
|
218
|
-
"absolute h-8 w-8 rounded-full",
|
|
219
|
-
orientation === "horizontal"
|
|
220
|
-
? "-right-12 top-1/2 -translate-y-1/2"
|
|
221
|
-
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
|
|
222
|
-
className,
|
|
223
|
-
)}
|
|
224
|
-
disabled={!canScrollNext}
|
|
225
|
-
onClick={scrollNext}
|
|
226
|
-
{...props}
|
|
227
|
-
>
|
|
228
|
-
<ArrowRightIcon className="h-4 w-4" />
|
|
229
|
-
<span className="sr-only">Next slide</span>
|
|
230
|
-
</Button>
|
|
231
|
-
);
|
|
232
|
-
},
|
|
233
|
-
);
|
|
234
|
-
CarouselNext.displayName = "CarouselNext";
|
|
235
|
-
|
|
236
|
-
export {
|
|
237
|
-
type CarouselApi,
|
|
238
|
-
Carousel,
|
|
239
|
-
CarouselContent,
|
|
240
|
-
CarouselItem,
|
|
241
|
-
CarouselPrevious,
|
|
242
|
-
CarouselNext,
|
|
243
|
-
};
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
|
4
|
-
import * as React from "react";
|
|
5
|
-
import CheckIcon from "~icons/lucide/check";
|
|
6
|
-
import type { Override } from "../../lib/helpers";
|
|
7
|
-
import { cn } from "../../lib/utils";
|
|
8
|
-
|
|
9
|
-
type CheckboxProps = Override<
|
|
10
|
-
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>,
|
|
11
|
-
{
|
|
12
|
-
name?: string;
|
|
13
|
-
onValueChange?: (checked: boolean) => void;
|
|
14
|
-
onBlur?: React.FocusEventHandler<HTMLInputElement>;
|
|
15
|
-
}
|
|
16
|
-
>;
|
|
17
|
-
|
|
18
|
-
const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
|
|
19
|
-
({ className, name, checked, defaultChecked, onValueChange, onBlur, ...props }, ref) => {
|
|
20
|
-
const inputRef = React.useRef<HTMLInputElement>(null);
|
|
21
|
-
|
|
22
|
-
// biome-ignore lint/style/noNonNullAssertion: useImperativeHandle은 ref가 할당된 후 실행되므로 안전함
|
|
23
|
-
React.useImperativeHandle(ref, () => inputRef.current!);
|
|
24
|
-
|
|
25
|
-
const handleCheckedChange = (newChecked: boolean | "indeterminate") => {
|
|
26
|
-
const boolValue = newChecked === "indeterminate" ? false : newChecked;
|
|
27
|
-
onValueChange?.(boolValue);
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
return (
|
|
31
|
-
<>
|
|
32
|
-
<input
|
|
33
|
-
type="checkbox"
|
|
34
|
-
ref={inputRef}
|
|
35
|
-
name={name}
|
|
36
|
-
defaultChecked={defaultChecked === true}
|
|
37
|
-
onBlur={onBlur}
|
|
38
|
-
className="sr-only"
|
|
39
|
-
tabIndex={-1}
|
|
40
|
-
aria-hidden="true"
|
|
41
|
-
/>
|
|
42
|
-
<CheckboxPrimitive.Root
|
|
43
|
-
data-slot="checkbox"
|
|
44
|
-
checked={checked}
|
|
45
|
-
defaultChecked={defaultChecked}
|
|
46
|
-
onCheckedChange={handleCheckedChange}
|
|
47
|
-
className={cn(
|
|
48
|
-
"peer border border-muted-foreground/30 bg-input-background dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
|
49
|
-
className,
|
|
50
|
-
)}
|
|
51
|
-
{...props}
|
|
52
|
-
>
|
|
53
|
-
<CheckboxPrimitive.Indicator
|
|
54
|
-
data-slot="checkbox-indicator"
|
|
55
|
-
className="flex items-center justify-center text-current transition-none"
|
|
56
|
-
>
|
|
57
|
-
<CheckIcon className="size-3.5" />
|
|
58
|
-
</CheckboxPrimitive.Indicator>
|
|
59
|
-
</CheckboxPrimitive.Root>
|
|
60
|
-
</>
|
|
61
|
-
);
|
|
62
|
-
},
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
Checkbox.displayName = "Checkbox";
|
|
66
|
-
|
|
67
|
-
export { Checkbox };
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
|
|
2
|
-
|
|
3
|
-
const Collapsible = CollapsiblePrimitive.Root;
|
|
4
|
-
|
|
5
|
-
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
|
|
6
|
-
|
|
7
|
-
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;
|
|
8
|
-
|
|
9
|
-
export { Collapsible, CollapsibleTrigger, CollapsibleContent };
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import * as React from "react";
|
|
4
|
-
import CheckIcon from "~icons/lucide/check";
|
|
5
|
-
import ChevronsUpDownIcon from "~icons/lucide/chevrons-up-down";
|
|
6
|
-
import XCircleIcon from "~icons/lucide/x-circle";
|
|
7
|
-
|
|
8
|
-
import { cn } from "../../lib/utils";
|
|
9
|
-
import { Button } from "./button";
|
|
10
|
-
import {
|
|
11
|
-
Command,
|
|
12
|
-
CommandEmpty,
|
|
13
|
-
CommandGroup,
|
|
14
|
-
CommandInput,
|
|
15
|
-
CommandItem,
|
|
16
|
-
CommandList,
|
|
17
|
-
} from "./command";
|
|
18
|
-
import { Popover, PopoverContent, PopoverTrigger } from "./popover";
|
|
19
|
-
|
|
20
|
-
export interface ComboboxOption {
|
|
21
|
-
value: string;
|
|
22
|
-
label: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
interface ComboboxProps {
|
|
26
|
-
options: ComboboxOption[];
|
|
27
|
-
name?: string;
|
|
28
|
-
value?: string;
|
|
29
|
-
onValueChange?: (value: string | undefined) => void;
|
|
30
|
-
onBlur?: React.FocusEventHandler<HTMLInputElement>;
|
|
31
|
-
placeholder?: string;
|
|
32
|
-
searchPlaceholder?: string;
|
|
33
|
-
emptyText?: string;
|
|
34
|
-
disabled?: boolean;
|
|
35
|
-
className?: string;
|
|
36
|
-
clearable?: boolean;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const Combobox = React.forwardRef<HTMLInputElement, ComboboxProps>(
|
|
40
|
-
(
|
|
41
|
-
{
|
|
42
|
-
options,
|
|
43
|
-
name,
|
|
44
|
-
value,
|
|
45
|
-
onValueChange,
|
|
46
|
-
onBlur,
|
|
47
|
-
placeholder = "Select option...",
|
|
48
|
-
searchPlaceholder = "Search...",
|
|
49
|
-
emptyText = "No option found.",
|
|
50
|
-
disabled = false,
|
|
51
|
-
className,
|
|
52
|
-
clearable = false,
|
|
53
|
-
},
|
|
54
|
-
ref,
|
|
55
|
-
) => {
|
|
56
|
-
const [open, setOpen] = React.useState(false);
|
|
57
|
-
const inputRef = React.useRef<HTMLInputElement>(null);
|
|
58
|
-
|
|
59
|
-
// biome-ignore lint/style/noNonNullAssertion: useImperativeHandle은 ref가 할당된 후 실행되므로 안전함
|
|
60
|
-
React.useImperativeHandle(ref, () => inputRef.current!);
|
|
61
|
-
|
|
62
|
-
const handleSelect = (currentValue: string) => {
|
|
63
|
-
const newValue = currentValue === value ? "" : currentValue;
|
|
64
|
-
onValueChange?.(newValue || undefined);
|
|
65
|
-
setOpen(false);
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const handleClear = (e: React.MouseEvent) => {
|
|
69
|
-
e.preventDefault();
|
|
70
|
-
e.stopPropagation();
|
|
71
|
-
onValueChange?.(undefined);
|
|
72
|
-
setOpen(false);
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
const hasValue = Boolean(value);
|
|
76
|
-
|
|
77
|
-
return (
|
|
78
|
-
<>
|
|
79
|
-
<input type="hidden" ref={inputRef} name={name} value={value || ""} onBlur={onBlur} />
|
|
80
|
-
<Popover open={open} onOpenChange={setOpen}>
|
|
81
|
-
<PopoverTrigger asChild>
|
|
82
|
-
<Button
|
|
83
|
-
variant="outline"
|
|
84
|
-
role="combobox"
|
|
85
|
-
aria-expanded={open}
|
|
86
|
-
disabled={disabled}
|
|
87
|
-
className={cn("w-full justify-between", className)}
|
|
88
|
-
>
|
|
89
|
-
<span className="flex-1 truncate text-left">
|
|
90
|
-
{value ? options.find((option) => option.value === value)?.label : placeholder}
|
|
91
|
-
</span>
|
|
92
|
-
<div className="flex items-center gap-1 shrink-0 pl-2">
|
|
93
|
-
{clearable && hasValue && (
|
|
94
|
-
<span
|
|
95
|
-
className="flex items-center justify-center"
|
|
96
|
-
onMouseDown={(e) => e.stopPropagation()}
|
|
97
|
-
onClick={handleClear}
|
|
98
|
-
>
|
|
99
|
-
<XCircleIcon className="h-4 w-4 cursor-pointer opacity-50 hover:opacity-100 transition-opacity" />
|
|
100
|
-
</span>
|
|
101
|
-
)}
|
|
102
|
-
<ChevronsUpDownIcon className="h-4 w-4 opacity-50" />
|
|
103
|
-
</div>
|
|
104
|
-
</Button>
|
|
105
|
-
</PopoverTrigger>
|
|
106
|
-
<PopoverContent className="w-full p-0">
|
|
107
|
-
<Command>
|
|
108
|
-
<CommandInput placeholder={searchPlaceholder} className="h-9" />
|
|
109
|
-
<CommandList>
|
|
110
|
-
<CommandEmpty>{emptyText}</CommandEmpty>
|
|
111
|
-
<CommandGroup>
|
|
112
|
-
{options.map((option) => (
|
|
113
|
-
<CommandItem key={option.value} value={option.value} onSelect={handleSelect}>
|
|
114
|
-
{option.label}
|
|
115
|
-
<CheckIcon
|
|
116
|
-
className={cn(
|
|
117
|
-
"ml-auto",
|
|
118
|
-
value === option.value ? "opacity-100" : "opacity-0",
|
|
119
|
-
)}
|
|
120
|
-
/>
|
|
121
|
-
</CommandItem>
|
|
122
|
-
))}
|
|
123
|
-
</CommandGroup>
|
|
124
|
-
</CommandList>
|
|
125
|
-
</Command>
|
|
126
|
-
</PopoverContent>
|
|
127
|
-
</Popover>
|
|
128
|
-
</>
|
|
129
|
-
);
|
|
130
|
-
},
|
|
131
|
-
);
|
|
132
|
-
|
|
133
|
-
Combobox.displayName = "Combobox";
|
|
134
|
-
|
|
135
|
-
export { Combobox };
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import type { DialogProps } from "@radix-ui/react-dialog";
|
|
4
|
-
import { Command as CommandPrimitive } from "cmdk";
|
|
5
|
-
import * as React from "react";
|
|
6
|
-
import SearchIcon from "~icons/lucide/search";
|
|
7
|
-
|
|
8
|
-
import { cn } from "../../lib/utils";
|
|
9
|
-
import { Dialog, DialogContent } from "./dialog";
|
|
10
|
-
|
|
11
|
-
const Command = React.forwardRef<
|
|
12
|
-
React.ElementRef<typeof CommandPrimitive>,
|
|
13
|
-
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
|
|
14
|
-
>(({ className, ...props }, ref) => (
|
|
15
|
-
<CommandPrimitive
|
|
16
|
-
ref={ref}
|
|
17
|
-
className={cn(
|
|
18
|
-
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
|
|
19
|
-
className,
|
|
20
|
-
)}
|
|
21
|
-
{...props}
|
|
22
|
-
/>
|
|
23
|
-
));
|
|
24
|
-
Command.displayName = CommandPrimitive.displayName;
|
|
25
|
-
|
|
26
|
-
const CommandDialog = ({ children, ...props }: DialogProps) => {
|
|
27
|
-
return (
|
|
28
|
-
<Dialog {...props}>
|
|
29
|
-
<DialogContent className="overflow-hidden p-0 shadow-lg">
|
|
30
|
-
<Command className="**:[[cmdk-group-heading]]:px-2 **:[[cmdk-group-heading]]:font-medium **:[[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 **:[[cmdk-group]]:px-2 **:[[cmdk-input-wrapper]_svg]:size-5 **:[[cmdk-input]]:h-12 **:[[cmdk-item]]:px-2 **:[[cmdk-item]]:py-3 **:[[cmdk-item]_svg]:size-5">
|
|
31
|
-
{children}
|
|
32
|
-
</Command>
|
|
33
|
-
</DialogContent>
|
|
34
|
-
</Dialog>
|
|
35
|
-
);
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const CommandInput = React.forwardRef<
|
|
39
|
-
React.ElementRef<typeof CommandPrimitive.Input>,
|
|
40
|
-
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
|
|
41
|
-
>(({ className, ...props }, ref) => (
|
|
42
|
-
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
|
|
43
|
-
<SearchIcon className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
|
44
|
-
<CommandPrimitive.Input
|
|
45
|
-
ref={ref}
|
|
46
|
-
className={cn(
|
|
47
|
-
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
|
|
48
|
-
className,
|
|
49
|
-
)}
|
|
50
|
-
{...props}
|
|
51
|
-
/>
|
|
52
|
-
</div>
|
|
53
|
-
));
|
|
54
|
-
|
|
55
|
-
CommandInput.displayName = CommandPrimitive.Input.displayName;
|
|
56
|
-
|
|
57
|
-
const CommandList = React.forwardRef<
|
|
58
|
-
React.ElementRef<typeof CommandPrimitive.List>,
|
|
59
|
-
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
|
|
60
|
-
>(({ className, ...props }, ref) => (
|
|
61
|
-
<CommandPrimitive.List
|
|
62
|
-
ref={ref}
|
|
63
|
-
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
|
|
64
|
-
{...props}
|
|
65
|
-
/>
|
|
66
|
-
));
|
|
67
|
-
|
|
68
|
-
CommandList.displayName = CommandPrimitive.List.displayName;
|
|
69
|
-
|
|
70
|
-
const CommandEmpty = React.forwardRef<
|
|
71
|
-
React.ElementRef<typeof CommandPrimitive.Empty>,
|
|
72
|
-
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
|
|
73
|
-
>((props, ref) => (
|
|
74
|
-
<CommandPrimitive.Empty ref={ref} className="py-6 text-center text-sm" {...props} />
|
|
75
|
-
));
|
|
76
|
-
|
|
77
|
-
CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
|
|
78
|
-
|
|
79
|
-
const CommandGroup = React.forwardRef<
|
|
80
|
-
React.ElementRef<typeof CommandPrimitive.Group>,
|
|
81
|
-
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
|
|
82
|
-
>(({ className, ...props }, ref) => (
|
|
83
|
-
<CommandPrimitive.Group
|
|
84
|
-
ref={ref}
|
|
85
|
-
className={cn(
|
|
86
|
-
"overflow-hidden p-1 text-foreground **:[[cmdk-group-heading]]:px-2 **:[[cmdk-group-heading]]:py-1.5 **:[[cmdk-group-heading]]:text-xs **:[[cmdk-group-heading]]:font-medium **:[[cmdk-group-heading]]:text-muted-foreground",
|
|
87
|
-
className,
|
|
88
|
-
)}
|
|
89
|
-
{...props}
|
|
90
|
-
/>
|
|
91
|
-
));
|
|
92
|
-
|
|
93
|
-
CommandGroup.displayName = CommandPrimitive.Group.displayName;
|
|
94
|
-
|
|
95
|
-
const CommandSeparator = React.forwardRef<
|
|
96
|
-
React.ElementRef<typeof CommandPrimitive.Separator>,
|
|
97
|
-
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
|
|
98
|
-
>(({ className, ...props }, ref) => (
|
|
99
|
-
<CommandPrimitive.Separator
|
|
100
|
-
ref={ref}
|
|
101
|
-
className={cn("-mx-1 h-px bg-border", className)}
|
|
102
|
-
{...props}
|
|
103
|
-
/>
|
|
104
|
-
));
|
|
105
|
-
CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
|
|
106
|
-
|
|
107
|
-
const CommandItem = React.forwardRef<
|
|
108
|
-
React.ElementRef<typeof CommandPrimitive.Item>,
|
|
109
|
-
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
|
|
110
|
-
>(({ className, ...props }, ref) => (
|
|
111
|
-
<CommandPrimitive.Item
|
|
112
|
-
ref={ref}
|
|
113
|
-
className={cn(
|
|
114
|
-
"relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected='true']:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 **:[svg]:pointer-events-none **:[svg]:size-4 **:[svg]:shrink-0",
|
|
115
|
-
className,
|
|
116
|
-
)}
|
|
117
|
-
{...props}
|
|
118
|
-
/>
|
|
119
|
-
));
|
|
120
|
-
|
|
121
|
-
CommandItem.displayName = CommandPrimitive.Item.displayName;
|
|
122
|
-
|
|
123
|
-
const CommandShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
|
|
124
|
-
return (
|
|
125
|
-
<span
|
|
126
|
-
className={cn("ml-auto text-xs tracking-widest text-muted-foreground", className)}
|
|
127
|
-
{...props}
|
|
128
|
-
/>
|
|
129
|
-
);
|
|
130
|
-
};
|
|
131
|
-
CommandShortcut.displayName = "CommandShortcut";
|
|
132
|
-
|
|
133
|
-
export {
|
|
134
|
-
Command,
|
|
135
|
-
CommandDialog,
|
|
136
|
-
CommandInput,
|
|
137
|
-
CommandList,
|
|
138
|
-
CommandEmpty,
|
|
139
|
-
CommandGroup,
|
|
140
|
-
CommandItem,
|
|
141
|
-
CommandShortcut,
|
|
142
|
-
CommandSeparator,
|
|
143
|
-
};
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { atom, useAtom } from "jotai";
|
|
4
|
-
import type * as React from "react";
|
|
5
|
-
import { cn } from "../../lib/utils";
|
|
6
|
-
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "./dialog";
|
|
7
|
-
|
|
8
|
-
type ExtendedModalProps = {
|
|
9
|
-
title?: string;
|
|
10
|
-
description?: string;
|
|
11
|
-
className?: string;
|
|
12
|
-
onCompleted?: (data?: unknown) => void;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export const commonModalAtom = atom<
|
|
16
|
-
{
|
|
17
|
-
open: boolean;
|
|
18
|
-
reactNode: React.ReactNode | null;
|
|
19
|
-
} & ExtendedModalProps
|
|
20
|
-
>({
|
|
21
|
-
open: false,
|
|
22
|
-
reactNode: null,
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
type CommonModalProps = {
|
|
26
|
-
className?: string;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export function CommonModal({ className }: CommonModalProps) {
|
|
30
|
-
const [atomValue, setAtomValue] = useAtom(commonModalAtom);
|
|
31
|
-
const { open, reactNode, title, description, className: modalClassName } = atomValue;
|
|
32
|
-
|
|
33
|
-
const closeAndClear = () => {
|
|
34
|
-
setAtomValue({
|
|
35
|
-
open: false,
|
|
36
|
-
reactNode: null,
|
|
37
|
-
});
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
return (
|
|
41
|
-
<Dialog
|
|
42
|
-
open={open}
|
|
43
|
-
onOpenChange={(isOpen) => {
|
|
44
|
-
if (!isOpen) {
|
|
45
|
-
closeAndClear();
|
|
46
|
-
}
|
|
47
|
-
}}
|
|
48
|
-
>
|
|
49
|
-
<DialogContent className={cn("max-w-4xl", modalClassName, className)}>
|
|
50
|
-
{(title || description) && (
|
|
51
|
-
<DialogHeader>
|
|
52
|
-
{title && <DialogTitle>{title}</DialogTitle>}
|
|
53
|
-
{description && <DialogDescription>{description}</DialogDescription>}
|
|
54
|
-
</DialogHeader>
|
|
55
|
-
)}
|
|
56
|
-
{reactNode}
|
|
57
|
-
</DialogContent>
|
|
58
|
-
</Dialog>
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function useCommonModal() {
|
|
63
|
-
const [atomValue, setAtomValue] = useAtom(commonModalAtom);
|
|
64
|
-
const { open, reactNode, onCompleted } = atomValue;
|
|
65
|
-
|
|
66
|
-
const openModal = (reactNode: React.ReactNode, props?: ExtendedModalProps) => {
|
|
67
|
-
setAtomValue({
|
|
68
|
-
open: true,
|
|
69
|
-
reactNode,
|
|
70
|
-
...props,
|
|
71
|
-
});
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
const closeModal = () => {
|
|
75
|
-
setAtomValue({
|
|
76
|
-
open: false,
|
|
77
|
-
reactNode: null,
|
|
78
|
-
});
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
const doneModal = (data?: unknown) => {
|
|
82
|
-
closeModal();
|
|
83
|
-
if (onCompleted) {
|
|
84
|
-
onCompleted(data);
|
|
85
|
-
}
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
return {
|
|
89
|
-
open,
|
|
90
|
-
reactNode,
|
|
91
|
-
openModal,
|
|
92
|
-
closeModal,
|
|
93
|
-
doneModal,
|
|
94
|
-
};
|
|
95
|
-
}
|