@hua-labs/ui 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +363 -0
- package/dist/components/Accordion.d.ts +39 -0
- package/dist/components/Accordion.d.ts.map +1 -0
- package/dist/components/Accordion.js +84 -0
- package/dist/components/Alert.d.ts +17 -0
- package/dist/components/Alert.d.ts.map +1 -0
- package/dist/components/Alert.js +61 -0
- package/dist/components/Avatar.d.ts +13 -0
- package/dist/components/Avatar.d.ts.map +1 -0
- package/dist/components/Avatar.js +18 -0
- package/dist/components/Badge.d.ts +7 -0
- package/dist/components/Badge.d.ts.map +1 -0
- package/dist/components/Badge.js +15 -0
- package/dist/components/BottomSheet.d.ts +29 -0
- package/dist/components/BottomSheet.d.ts.map +1 -0
- package/dist/components/BottomSheet.js +96 -0
- package/dist/components/Breadcrumb.d.ts +27 -0
- package/dist/components/Breadcrumb.d.ts.map +1 -0
- package/dist/components/Breadcrumb.js +47 -0
- package/dist/components/Button.d.ts +9 -0
- package/dist/components/Button.d.ts.map +1 -0
- package/dist/components/Button.js +23 -0
- package/dist/components/Card.d.ts +21 -0
- package/dist/components/Card.d.ts.map +1 -0
- package/dist/components/Card.js +18 -0
- package/dist/components/ChatMessage.d.ts +35 -0
- package/dist/components/ChatMessage.d.ts.map +1 -0
- package/dist/components/ChatMessage.js +59 -0
- package/dist/components/Checkbox.d.ts +12 -0
- package/dist/components/Checkbox.d.ts.map +1 -0
- package/dist/components/Checkbox.js +30 -0
- package/dist/components/Command.d.ts +36 -0
- package/dist/components/Command.d.ts.map +1 -0
- package/dist/components/Command.js +119 -0
- package/dist/components/ConfirmModal.d.ts +26 -0
- package/dist/components/ConfirmModal.d.ts.map +1 -0
- package/dist/components/ConfirmModal.js +53 -0
- package/dist/components/Container.d.ts +10 -0
- package/dist/components/Container.d.ts.map +1 -0
- package/dist/components/Container.js +23 -0
- package/dist/components/ContextMenu.d.ts +26 -0
- package/dist/components/ContextMenu.d.ts.map +1 -0
- package/dist/components/ContextMenu.js +110 -0
- package/dist/components/Divider.d.ts +11 -0
- package/dist/components/Divider.d.ts.map +1 -0
- package/dist/components/Divider.js +39 -0
- package/dist/components/Drawer.d.ts +32 -0
- package/dist/components/Drawer.d.ts.map +1 -0
- package/dist/components/Drawer.js +79 -0
- package/dist/components/Dropdown.d.ts +28 -0
- package/dist/components/Dropdown.d.ts.map +1 -0
- package/dist/components/Dropdown.js +174 -0
- package/dist/components/EmotionAnalysis.d.ts +25 -0
- package/dist/components/EmotionAnalysis.d.ts.map +1 -0
- package/dist/components/EmotionAnalysis.js +40 -0
- package/dist/components/EmotionButton.d.ts +9 -0
- package/dist/components/EmotionButton.d.ts.map +1 -0
- package/dist/components/EmotionButton.js +16 -0
- package/dist/components/EmotionMeter.d.ts +10 -0
- package/dist/components/EmotionMeter.d.ts.map +1 -0
- package/dist/components/EmotionMeter.js +21 -0
- package/dist/components/EmotionSelector.d.ts +20 -0
- package/dist/components/EmotionSelector.d.ts.map +1 -0
- package/dist/components/EmotionSelector.js +46 -0
- package/dist/components/Grid.d.ts +11 -0
- package/dist/components/Grid.d.ts.map +1 -0
- package/dist/components/Grid.js +44 -0
- package/dist/components/Icon.d.ts +26 -0
- package/dist/components/Icon.d.ts.map +1 -0
- package/dist/components/Icon.js +48 -0
- package/dist/components/Input.d.ts +12 -0
- package/dist/components/Input.d.ts.map +1 -0
- package/dist/components/Input.js +25 -0
- package/dist/components/LanguageToggle.d.ts +17 -0
- package/dist/components/LanguageToggle.d.ts.map +1 -0
- package/dist/components/LanguageToggle.js +61 -0
- package/dist/components/LoadingSpinner.d.ts +10 -0
- package/dist/components/LoadingSpinner.d.ts.map +1 -0
- package/dist/components/LoadingSpinner.js +37 -0
- package/dist/components/Menu.d.ts +29 -0
- package/dist/components/Menu.d.ts.map +1 -0
- package/dist/components/Menu.js +122 -0
- package/dist/components/Modal.d.ts +15 -0
- package/dist/components/Modal.d.ts.map +1 -0
- package/dist/components/Modal.js +62 -0
- package/dist/components/PageTransition.d.ts +18 -0
- package/dist/components/PageTransition.d.ts.map +1 -0
- package/dist/components/PageTransition.js +39 -0
- package/dist/components/Pagination.d.ts +21 -0
- package/dist/components/Pagination.d.ts.map +1 -0
- package/dist/components/Pagination.js +87 -0
- package/dist/components/Popover.d.ts +16 -0
- package/dist/components/Popover.d.ts.map +1 -0
- package/dist/components/Popover.js +159 -0
- package/dist/components/Progress.d.ts +23 -0
- package/dist/components/Progress.d.ts.map +1 -0
- package/dist/components/Progress.js +51 -0
- package/dist/components/Radio.d.ts +12 -0
- package/dist/components/Radio.d.ts.map +1 -0
- package/dist/components/Radio.js +29 -0
- package/dist/components/ScrollArea.d.ts +16 -0
- package/dist/components/ScrollArea.d.ts.map +1 -0
- package/dist/components/ScrollArea.js +42 -0
- package/dist/components/ScrollIndicator.d.ts +17 -0
- package/dist/components/ScrollIndicator.d.ts.map +1 -0
- package/dist/components/ScrollIndicator.js +60 -0
- package/dist/components/ScrollProgress.d.ts +12 -0
- package/dist/components/ScrollProgress.d.ts.map +1 -0
- package/dist/components/ScrollProgress.js +39 -0
- package/dist/components/ScrollToTop.d.ts +15 -0
- package/dist/components/ScrollToTop.d.ts.map +1 -0
- package/dist/components/ScrollToTop.js +46 -0
- package/dist/components/Select.d.ts +17 -0
- package/dist/components/Select.d.ts.map +1 -0
- package/dist/components/Select.js +29 -0
- package/dist/components/Skeleton.d.ts +19 -0
- package/dist/components/Skeleton.d.ts.map +1 -0
- package/dist/components/Skeleton.js +71 -0
- package/dist/components/Stack.d.ts +11 -0
- package/dist/components/Stack.d.ts.map +1 -0
- package/dist/components/Stack.js +34 -0
- package/dist/components/Switch.d.ts +12 -0
- package/dist/components/Switch.d.ts.map +1 -0
- package/dist/components/Switch.js +29 -0
- package/dist/components/Tabs.d.ts +36 -0
- package/dist/components/Tabs.d.ts.map +1 -0
- package/dist/components/Tabs.js +117 -0
- package/dist/components/Textarea.d.ts +11 -0
- package/dist/components/Textarea.d.ts.map +1 -0
- package/dist/components/Textarea.js +31 -0
- package/dist/components/ThemeProvider.d.ts +19 -0
- package/dist/components/ThemeProvider.d.ts.map +1 -0
- package/dist/components/ThemeProvider.js +76 -0
- package/dist/components/ThemeToggle.d.ts +14 -0
- package/dist/components/ThemeToggle.d.ts.map +1 -0
- package/dist/components/ThemeToggle.js +49 -0
- package/dist/components/Toast.d.ts +32 -0
- package/dist/components/Toast.d.ts.map +1 -0
- package/dist/components/Toast.js +138 -0
- package/dist/components/Tooltip.d.ts +14 -0
- package/dist/components/Tooltip.d.ts.map +1 -0
- package/dist/components/Tooltip.js +102 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +49 -0
- package/dist/lib/icons.d.ts +43 -0
- package/dist/lib/icons.d.ts.map +1 -0
- package/dist/lib/icons.js +321 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +5 -0
- package/package.json +67 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../lib/utils";
|
|
5
|
+
const Popover = React.forwardRef(({ className, children, trigger, open: controlledOpen, onOpenChange, position = "bottom", align = "center", offset = 8, disabled = false, ...props }, ref) => {
|
|
6
|
+
const [internalOpen, setInternalOpen] = React.useState(false);
|
|
7
|
+
const [coords, setCoords] = React.useState({ x: 0, y: 0 });
|
|
8
|
+
const triggerRef = React.useRef(null);
|
|
9
|
+
const popoverRef = React.useRef(null);
|
|
10
|
+
const isControlled = controlledOpen !== undefined;
|
|
11
|
+
const isOpen = isControlled ? controlledOpen : internalOpen;
|
|
12
|
+
const handleOpenChange = (newOpen) => {
|
|
13
|
+
if (disabled)
|
|
14
|
+
return;
|
|
15
|
+
if (!isControlled) {
|
|
16
|
+
setInternalOpen(newOpen);
|
|
17
|
+
}
|
|
18
|
+
onOpenChange?.(newOpen);
|
|
19
|
+
};
|
|
20
|
+
const handleTriggerClick = () => {
|
|
21
|
+
handleOpenChange(!isOpen);
|
|
22
|
+
};
|
|
23
|
+
const updatePosition = React.useCallback(() => {
|
|
24
|
+
if (!triggerRef.current || !popoverRef.current)
|
|
25
|
+
return;
|
|
26
|
+
const triggerRect = triggerRef.current.getBoundingClientRect();
|
|
27
|
+
const popoverRect = popoverRef.current.getBoundingClientRect();
|
|
28
|
+
const viewportWidth = window.innerWidth;
|
|
29
|
+
const viewportHeight = window.innerHeight;
|
|
30
|
+
let x = 0;
|
|
31
|
+
let y = 0;
|
|
32
|
+
// 기본 위치 계산
|
|
33
|
+
switch (position) {
|
|
34
|
+
case "top":
|
|
35
|
+
x = triggerRect.left + triggerRect.width / 2;
|
|
36
|
+
y = triggerRect.top - offset;
|
|
37
|
+
break;
|
|
38
|
+
case "bottom":
|
|
39
|
+
x = triggerRect.left + triggerRect.width / 2;
|
|
40
|
+
y = triggerRect.bottom + offset;
|
|
41
|
+
break;
|
|
42
|
+
case "left":
|
|
43
|
+
x = triggerRect.left - offset;
|
|
44
|
+
y = triggerRect.top + triggerRect.height / 2;
|
|
45
|
+
break;
|
|
46
|
+
case "right":
|
|
47
|
+
x = triggerRect.right + offset;
|
|
48
|
+
y = triggerRect.top + triggerRect.height / 2;
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
// 정렬 조정
|
|
52
|
+
switch (align) {
|
|
53
|
+
case "start":
|
|
54
|
+
if (position === "top" || position === "bottom") {
|
|
55
|
+
x = triggerRect.left;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
y = triggerRect.top;
|
|
59
|
+
}
|
|
60
|
+
break;
|
|
61
|
+
case "end":
|
|
62
|
+
if (position === "top" || position === "bottom") {
|
|
63
|
+
x = triggerRect.right - popoverRect.width;
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
y = triggerRect.bottom - popoverRect.height;
|
|
67
|
+
}
|
|
68
|
+
break;
|
|
69
|
+
case "center":
|
|
70
|
+
default:
|
|
71
|
+
if (position === "top" || position === "bottom") {
|
|
72
|
+
x = triggerRect.left + triggerRect.width / 2 - popoverRect.width / 2;
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
y = triggerRect.top + triggerRect.height / 2 - popoverRect.height / 2;
|
|
76
|
+
}
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
// 뷰포트 경계 확인 및 조정
|
|
80
|
+
if (x < 0)
|
|
81
|
+
x = 8; // 8px 여백
|
|
82
|
+
if (x + popoverRect.width > viewportWidth) {
|
|
83
|
+
x = viewportWidth - popoverRect.width - 8; // 8px 여백
|
|
84
|
+
}
|
|
85
|
+
if (y < 0)
|
|
86
|
+
y = 8; // 8px 여백
|
|
87
|
+
if (y + popoverRect.height > viewportHeight) {
|
|
88
|
+
y = viewportHeight - popoverRect.height - 8; // 8px 여백
|
|
89
|
+
}
|
|
90
|
+
setCoords({ x, y });
|
|
91
|
+
}, [position, align, offset]);
|
|
92
|
+
React.useEffect(() => {
|
|
93
|
+
if (isOpen) {
|
|
94
|
+
updatePosition();
|
|
95
|
+
window.addEventListener('resize', updatePosition);
|
|
96
|
+
window.addEventListener('scroll', updatePosition);
|
|
97
|
+
return () => {
|
|
98
|
+
window.removeEventListener('resize', updatePosition);
|
|
99
|
+
window.removeEventListener('scroll', updatePosition);
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}, [isOpen, updatePosition]);
|
|
103
|
+
React.useEffect(() => {
|
|
104
|
+
const handleClickOutside = (event) => {
|
|
105
|
+
if (triggerRef.current &&
|
|
106
|
+
popoverRef.current &&
|
|
107
|
+
!triggerRef.current.contains(event.target) &&
|
|
108
|
+
!popoverRef.current.contains(event.target)) {
|
|
109
|
+
handleOpenChange(false);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
if (isOpen) {
|
|
113
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
114
|
+
return () => {
|
|
115
|
+
document.removeEventListener('mousedown', handleClickOutside);
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}, [isOpen]);
|
|
119
|
+
const getPositionClasses = () => {
|
|
120
|
+
switch (position) {
|
|
121
|
+
case "top":
|
|
122
|
+
return "bottom-full left-0 mb-2"; // 8px 간격
|
|
123
|
+
case "bottom":
|
|
124
|
+
return "top-full left-0 mt-2"; // 8px 간격
|
|
125
|
+
case "left":
|
|
126
|
+
return "right-full top-0 mr-2"; // 8px 간격
|
|
127
|
+
case "right":
|
|
128
|
+
return "left-full top-0 ml-2"; // 8px 간격
|
|
129
|
+
default:
|
|
130
|
+
return "top-full left-0 mt-2";
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
const getArrowClasses = () => {
|
|
134
|
+
switch (position) {
|
|
135
|
+
case "top":
|
|
136
|
+
return "top-full left-1/2 -translate-x-1/2 border-t-gray-200 dark:border-t-gray-700";
|
|
137
|
+
case "bottom":
|
|
138
|
+
return "bottom-full left-1/2 -translate-x-1/2 border-b-gray-200 dark:border-b-gray-700";
|
|
139
|
+
case "left":
|
|
140
|
+
return "left-full top-1/2 -translate-y-1/2 border-l-gray-200 dark:border-l-gray-700";
|
|
141
|
+
case "right":
|
|
142
|
+
return "right-full top-1/2 -translate-y-1/2 border-r-gray-200 dark:border-r-gray-700";
|
|
143
|
+
default:
|
|
144
|
+
return "bottom-full left-1/2 -translate-x-1/2 border-b-gray-200 dark:border-b-gray-700";
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
return (_jsxs("div", { ref: ref, className: cn("relative", className), ...props, children: [_jsx("div", { ref: triggerRef, onClick: handleTriggerClick, className: "inline-block cursor-pointer", children: trigger }), isOpen && (_jsxs("div", { ref: popoverRef, className: cn("absolute z-50 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg p-4", // 16px 패딩
|
|
148
|
+
getPositionClasses()), style: {
|
|
149
|
+
transform: `translate(${coords.x}px, ${coords.y}px)`,
|
|
150
|
+
minWidth: '200px'
|
|
151
|
+
}, children: [_jsx("div", { className: cn("absolute w-0 h-0 border-4 border-transparent", getArrowClasses()) }), _jsx("div", { className: "relative z-10", children: children })] }))] }));
|
|
152
|
+
});
|
|
153
|
+
Popover.displayName = "Popover";
|
|
154
|
+
// 편의 컴포넌트들
|
|
155
|
+
export const PopoverTrigger = React.forwardRef(({ className, children, ...props }, ref) => (_jsx("div", { ref: ref, className: cn("inline-block cursor-pointer", className), ...props, children: children })));
|
|
156
|
+
PopoverTrigger.displayName = "PopoverTrigger";
|
|
157
|
+
export const PopoverContent = React.forwardRef(({ className, children, ...props }, ref) => (_jsx("div", { ref: ref, className: cn("bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg p-4", className), ...props, children: children })));
|
|
158
|
+
PopoverContent.displayName = "PopoverContent";
|
|
159
|
+
export { Popover };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export interface ProgressProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
3
|
+
value?: number;
|
|
4
|
+
max?: number;
|
|
5
|
+
size?: "sm" | "md" | "lg";
|
|
6
|
+
variant?: "default" | "success" | "warning" | "error" | "info";
|
|
7
|
+
showValue?: boolean;
|
|
8
|
+
animated?: boolean;
|
|
9
|
+
striped?: boolean;
|
|
10
|
+
label?: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
}
|
|
13
|
+
declare const Progress: React.ForwardRefExoticComponent<ProgressProps & React.RefAttributes<HTMLDivElement>>;
|
|
14
|
+
export declare const ProgressSuccess: React.ForwardRefExoticComponent<Omit<ProgressProps, "variant"> & React.RefAttributes<HTMLDivElement>>;
|
|
15
|
+
export declare const ProgressWarning: React.ForwardRefExoticComponent<Omit<ProgressProps, "variant"> & React.RefAttributes<HTMLDivElement>>;
|
|
16
|
+
export declare const ProgressError: React.ForwardRefExoticComponent<Omit<ProgressProps, "variant"> & React.RefAttributes<HTMLDivElement>>;
|
|
17
|
+
export declare const ProgressInfo: React.ForwardRefExoticComponent<Omit<ProgressProps, "variant"> & React.RefAttributes<HTMLDivElement>>;
|
|
18
|
+
export declare const ProgressCard: React.ForwardRefExoticComponent<ProgressProps & {
|
|
19
|
+
title?: string;
|
|
20
|
+
} & React.RefAttributes<HTMLDivElement>>;
|
|
21
|
+
export declare const ProgressGroup: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
|
|
22
|
+
export { Progress };
|
|
23
|
+
//# sourceMappingURL=Progress.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Progress.d.ts","sourceRoot":"","sources":["../../src/components/Progress.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,MAAM,WAAW,aAAc,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IACzE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;IACzB,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,CAAA;IAC9D,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,QAAA,MAAM,QAAQ,sFA2Fb,CAAA;AAID,eAAO,MAAM,eAAe,uGAI3B,CAAA;AAGD,eAAO,MAAM,eAAe,uGAI3B,CAAA;AAGD,eAAO,MAAM,aAAa,uGAIzB,CAAA;AAGD,eAAO,MAAM,YAAY,uGAIxB,CAAA;AAID,eAAO,MAAM,YAAY;YAA8D,MAAM;wCAW5F,CAAA;AAGD,eAAO,MAAM,aAAa,6GAUzB,CAAA;AAGD,OAAO,EAAE,QAAQ,EAAE,CAAA"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../lib/utils";
|
|
5
|
+
const Progress = React.forwardRef(({ className, value = 0, max = 100, size = "md", variant = "default", showValue = false, animated = true, striped = false, label, description, ...props }, ref) => {
|
|
6
|
+
const percentage = Math.min(Math.max((value / max) * 100, 0), 100);
|
|
7
|
+
const sizeClasses = {
|
|
8
|
+
sm: "h-2", // 8px 높이
|
|
9
|
+
md: "h-3", // 12px 높이
|
|
10
|
+
lg: "h-4" // 16px 높이
|
|
11
|
+
};
|
|
12
|
+
const getVariantClasses = () => {
|
|
13
|
+
switch (variant) {
|
|
14
|
+
case "success":
|
|
15
|
+
return "bg-green-500 dark:bg-green-400";
|
|
16
|
+
case "warning":
|
|
17
|
+
return "bg-yellow-500 dark:bg-yellow-400";
|
|
18
|
+
case "error":
|
|
19
|
+
return "bg-red-500 dark:bg-red-400";
|
|
20
|
+
case "info":
|
|
21
|
+
return "bg-blue-500 dark:bg-blue-400";
|
|
22
|
+
default:
|
|
23
|
+
return "bg-gray-900 dark:bg-gray-100";
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const getStripedClasses = () => {
|
|
27
|
+
if (!striped)
|
|
28
|
+
return "";
|
|
29
|
+
return "bg-gradient-to-r from-transparent via-white/20 to-transparent bg-[length:20px_100%] animate-pulse";
|
|
30
|
+
};
|
|
31
|
+
return (_jsxs("div", { className: cn("w-full", className), ...props, children: [(label || showValue) && (_jsxs("div", { className: "flex items-center justify-between mb-2", children: [" ", label && (_jsx("span", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: label })), showValue && (_jsxs("span", { className: "text-sm text-gray-600 dark:text-gray-400", children: [Math.round(percentage), "%"] }))] })), _jsx("div", { ref: ref, className: cn("relative w-full overflow-hidden rounded-full bg-gray-200 dark:bg-gray-700", sizeClasses[size]), children: _jsx("div", { className: cn("h-full rounded-full transition-all duration-300 ease-out", getVariantClasses(), getStripedClasses(), animated && "animate-pulse"), style: {
|
|
32
|
+
width: `${percentage}%`,
|
|
33
|
+
transition: animated ? "width 0.3s ease-out" : "none"
|
|
34
|
+
} }) }), description && (_jsxs("p", { className: "mt-2 text-xs text-gray-500 dark:text-gray-400", children: [" ", description] }))] }));
|
|
35
|
+
});
|
|
36
|
+
Progress.displayName = "Progress";
|
|
37
|
+
// 편의 컴포넌트들
|
|
38
|
+
export const ProgressSuccess = React.forwardRef(({ className, ...props }, ref) => (_jsx(Progress, { ref: ref, variant: "success", className: className, ...props })));
|
|
39
|
+
ProgressSuccess.displayName = "ProgressSuccess";
|
|
40
|
+
export const ProgressWarning = React.forwardRef(({ className, ...props }, ref) => (_jsx(Progress, { ref: ref, variant: "warning", className: className, ...props })));
|
|
41
|
+
ProgressWarning.displayName = "ProgressWarning";
|
|
42
|
+
export const ProgressError = React.forwardRef(({ className, ...props }, ref) => (_jsx(Progress, { ref: ref, variant: "error", className: className, ...props })));
|
|
43
|
+
ProgressError.displayName = "ProgressError";
|
|
44
|
+
export const ProgressInfo = React.forwardRef(({ className, ...props }, ref) => (_jsx(Progress, { ref: ref, variant: "info", className: className, ...props })));
|
|
45
|
+
ProgressInfo.displayName = "ProgressInfo";
|
|
46
|
+
// 복합 Progress 컴포넌트들
|
|
47
|
+
export const ProgressCard = React.forwardRef(({ title, className, ...props }, ref) => (_jsxs("div", { className: cn("p-4 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700", className), children: [title && (_jsxs("h3", { className: "text-sm font-semibold text-gray-900 dark:text-white mb-3", children: [" ", title] })), _jsx(Progress, { ref: ref, ...props })] })));
|
|
48
|
+
ProgressCard.displayName = "ProgressCard";
|
|
49
|
+
export const ProgressGroup = React.forwardRef(({ className, children, ...props }, ref) => (_jsx("div", { ref: ref, className: cn("space-y-4", className), ...props, children: children })));
|
|
50
|
+
ProgressGroup.displayName = "ProgressGroup";
|
|
51
|
+
export { Progress };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export interface RadioProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'> {
|
|
3
|
+
variant?: "default" | "outline" | "filled";
|
|
4
|
+
size?: "sm" | "md" | "lg";
|
|
5
|
+
error?: boolean;
|
|
6
|
+
success?: boolean;
|
|
7
|
+
label?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
}
|
|
10
|
+
declare const Radio: React.ForwardRefExoticComponent<RadioProps & React.RefAttributes<HTMLInputElement>>;
|
|
11
|
+
export { Radio };
|
|
12
|
+
//# sourceMappingURL=Radio.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Radio.d.ts","sourceRoot":"","sources":["../../src/components/Radio.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,MAAM,WAAW,UAAW,SAAQ,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC3F,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAA;IAC1C,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;IACzB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,QAAA,MAAM,KAAK,qFAmFV,CAAA;AAGD,OAAO,EAAE,KAAK,EAAE,CAAA"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../lib/utils";
|
|
5
|
+
const Radio = React.forwardRef(({ className, variant = "default", size = "md", error = false, success = false, label, description, ...props }, ref) => {
|
|
6
|
+
const sizeClasses = {
|
|
7
|
+
sm: "w-4 h-4",
|
|
8
|
+
md: "w-5 h-5",
|
|
9
|
+
lg: "w-6 h-6"
|
|
10
|
+
};
|
|
11
|
+
const dotSizes = {
|
|
12
|
+
sm: "w-1.5 h-1.5",
|
|
13
|
+
md: "w-2 h-2",
|
|
14
|
+
lg: "w-2.5 h-2.5"
|
|
15
|
+
};
|
|
16
|
+
const variantClasses = {
|
|
17
|
+
default: "border-gray-300 bg-white text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-800 dark:focus:ring-blue-400",
|
|
18
|
+
outline: "border-2 border-gray-200 bg-transparent text-blue-600 focus:ring-blue-500 dark:border-gray-700 dark:focus:ring-blue-400",
|
|
19
|
+
filled: "border-transparent bg-gray-50 text-blue-600 focus:bg-white focus:ring-blue-500 dark:bg-gray-700 dark:focus:bg-gray-800 dark:focus:ring-blue-400"
|
|
20
|
+
};
|
|
21
|
+
const stateClasses = error
|
|
22
|
+
? "border-red-500 focus:ring-red-500 dark:border-red-400 dark:focus:ring-red-400"
|
|
23
|
+
: success
|
|
24
|
+
? "border-green-500 focus:ring-green-500 dark:border-green-400 dark:focus:ring-green-400"
|
|
25
|
+
: "";
|
|
26
|
+
return (_jsxs("div", { className: "flex items-start space-x-3", children: [_jsxs("div", { className: "relative", children: [_jsx("input", { type: "radio", className: cn("peer sr-only", className), ref: ref, ...props }), _jsx("div", { className: cn("flex items-center justify-center rounded-full border transition-all duration-200 cursor-pointer", "peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-offset-2", "peer-disabled:cursor-not-allowed peer-disabled:opacity-50", sizeClasses[size], variantClasses[variant], stateClasses, "peer-checked:border-blue-600 dark:peer-checked:border-blue-500"), children: _jsx("div", { className: cn("rounded-full bg-blue-600 dark:bg-blue-500 opacity-0 peer-checked:opacity-100 transition-opacity duration-200", dotSizes[size]) }) })] }), (label || description) && (_jsxs("div", { className: "flex flex-col", children: [label && (_jsx("label", { className: "text-sm font-medium text-gray-900 dark:text-white cursor-pointer", children: label })), description && (_jsx("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: description }))] }))] }));
|
|
27
|
+
});
|
|
28
|
+
Radio.displayName = "Radio";
|
|
29
|
+
export { Radio };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
interface ScrollAreaProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
3
|
+
children: React.ReactNode;
|
|
4
|
+
className?: string;
|
|
5
|
+
orientation?: "vertical" | "horizontal" | "both";
|
|
6
|
+
scrollHideDelay?: number;
|
|
7
|
+
type?: "auto" | "always" | "scroll" | "hover";
|
|
8
|
+
}
|
|
9
|
+
declare const ScrollArea: React.ForwardRefExoticComponent<ScrollAreaProps & React.RefAttributes<HTMLDivElement>>;
|
|
10
|
+
interface ScrollBarProps {
|
|
11
|
+
orientation?: "vertical" | "horizontal";
|
|
12
|
+
className?: string;
|
|
13
|
+
}
|
|
14
|
+
declare const ScrollBar: React.ForwardRefExoticComponent<ScrollBarProps & React.RefAttributes<HTMLDivElement>>;
|
|
15
|
+
export { ScrollArea, ScrollBar };
|
|
16
|
+
//# sourceMappingURL=ScrollArea.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ScrollArea.d.ts","sourceRoot":"","sources":["../../src/components/ScrollArea.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,UAAU,eAAgB,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IACpE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,UAAU,GAAG,YAAY,GAAG,MAAM,CAAA;IAChD,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,IAAI,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAA;CAC9C;AAED,QAAA,MAAM,UAAU,wFA8Df,CAAA;AAID,UAAU,cAAc;IACtB,WAAW,CAAC,EAAE,UAAU,GAAG,YAAY,CAAA;IACvC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,QAAA,MAAM,SAAS,uFAed,CAAA;AAID,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,CAAA"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../lib/utils";
|
|
5
|
+
const ScrollArea = React.forwardRef(({ children, className, orientation = "vertical", scrollHideDelay = 600, type = "hover", ...props }, ref) => {
|
|
6
|
+
const [showScrollbar, setShowScrollbar] = React.useState(false);
|
|
7
|
+
const timeoutRef = React.useRef(undefined);
|
|
8
|
+
const handleMouseEnter = () => {
|
|
9
|
+
if (type === "hover" || type === "always") {
|
|
10
|
+
setShowScrollbar(true);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
const handleMouseLeave = () => {
|
|
14
|
+
if (type === "hover") {
|
|
15
|
+
if (timeoutRef.current) {
|
|
16
|
+
clearTimeout(timeoutRef.current);
|
|
17
|
+
}
|
|
18
|
+
timeoutRef.current = setTimeout(() => {
|
|
19
|
+
setShowScrollbar(false);
|
|
20
|
+
}, scrollHideDelay);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
React.useEffect(() => {
|
|
24
|
+
if (type === "always") {
|
|
25
|
+
setShowScrollbar(true);
|
|
26
|
+
}
|
|
27
|
+
}, [type]);
|
|
28
|
+
React.useEffect(() => {
|
|
29
|
+
return () => {
|
|
30
|
+
if (timeoutRef.current) {
|
|
31
|
+
clearTimeout(timeoutRef.current);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}, []);
|
|
35
|
+
return (_jsx("div", { ref: ref, className: cn("relative overflow-auto scrollbar-thin", orientation === "vertical" && "overflow-y-auto overflow-x-hidden", orientation === "horizontal" && "overflow-x-auto overflow-y-hidden", orientation === "both" && "overflow-auto", showScrollbar ? "scrollbar-visible" : "scrollbar-hidden", className), onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, ...props, children: children }));
|
|
36
|
+
});
|
|
37
|
+
ScrollArea.displayName = "ScrollArea";
|
|
38
|
+
const ScrollBar = React.forwardRef(({ orientation = "vertical", className, ...props }, ref) => {
|
|
39
|
+
return (_jsx("div", { ref: ref, className: cn("flex touch-none select-none transition-colors duration-150 ease-out", orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent p-[1px]", orientation === "horizontal" && "h-2.5 flex-col border-t border-t-transparent p-[1px]", className), ...props }));
|
|
40
|
+
});
|
|
41
|
+
ScrollBar.displayName = "ScrollBar";
|
|
42
|
+
export { ScrollArea, ScrollBar };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface ScrollIndicatorProps {
|
|
3
|
+
className?: string;
|
|
4
|
+
targetId?: string;
|
|
5
|
+
text?: string;
|
|
6
|
+
iconName?: string;
|
|
7
|
+
iconSize?: number;
|
|
8
|
+
position?: 'bottom-center' | 'bottom-left' | 'bottom-right';
|
|
9
|
+
variant?: 'default' | 'primary' | 'secondary' | 'outline';
|
|
10
|
+
size?: 'sm' | 'md' | 'lg';
|
|
11
|
+
animated?: boolean;
|
|
12
|
+
autoHide?: boolean;
|
|
13
|
+
hideThreshold?: number;
|
|
14
|
+
}
|
|
15
|
+
declare const ScrollIndicator: React.ForwardRefExoticComponent<ScrollIndicatorProps & React.RefAttributes<HTMLDivElement>>;
|
|
16
|
+
export { ScrollIndicator };
|
|
17
|
+
//# sourceMappingURL=ScrollIndicator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ScrollIndicator.d.ts","sourceRoot":"","sources":["../../src/components/ScrollIndicator.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA8B,MAAM,OAAO,CAAA;AAKlD,MAAM,WAAW,oBAAoB;IACnC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,eAAe,GAAG,aAAa,GAAG,cAAc,CAAA;IAC3D,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,CAAA;IACzD,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,QAAA,MAAM,eAAe,6FAuGnB,CAAA;AAIF,OAAO,EAAE,eAAe,EAAE,CAAA"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import React, { useState, useEffect } from 'react';
|
|
4
|
+
import { Button } from './Button';
|
|
5
|
+
import { Icon } from './Icon';
|
|
6
|
+
import { cn } from '../lib/utils';
|
|
7
|
+
const ScrollIndicator = React.forwardRef(({ className, targetId, text = 'Scroll down', iconName = 'arrowDown', iconSize = 20, position = 'bottom-center', variant = 'default', size = 'md', animated = true, autoHide = true, hideThreshold = 100, ...props }, ref) => {
|
|
8
|
+
const [isVisible, setIsVisible] = useState(true);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (!autoHide)
|
|
11
|
+
return;
|
|
12
|
+
const handleScroll = () => {
|
|
13
|
+
const scrollTop = window.scrollY;
|
|
14
|
+
setIsVisible(scrollTop < hideThreshold);
|
|
15
|
+
};
|
|
16
|
+
// 초기 실행
|
|
17
|
+
handleScroll();
|
|
18
|
+
window.addEventListener('scroll', handleScroll, { passive: true });
|
|
19
|
+
return () => window.removeEventListener('scroll', handleScroll);
|
|
20
|
+
}, [autoHide, hideThreshold]);
|
|
21
|
+
const scrollToTarget = () => {
|
|
22
|
+
if (targetId) {
|
|
23
|
+
const targetElement = document.getElementById(targetId);
|
|
24
|
+
if (targetElement) {
|
|
25
|
+
targetElement.scrollIntoView({ behavior: 'smooth' });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
// 기본적으로 다음 섹션으로 스크롤
|
|
30
|
+
const currentSection = ref;
|
|
31
|
+
if (currentSection.current) {
|
|
32
|
+
const nextSection = currentSection.current.nextElementSibling;
|
|
33
|
+
if (nextSection) {
|
|
34
|
+
nextSection.scrollIntoView({ behavior: 'smooth' });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const positionClasses = {
|
|
40
|
+
'bottom-center': 'bottom-8 left-1/2 transform -translate-x-1/2',
|
|
41
|
+
'bottom-left': 'bottom-8 left-8',
|
|
42
|
+
'bottom-right': 'bottom-8 right-8'
|
|
43
|
+
};
|
|
44
|
+
const sizeClasses = {
|
|
45
|
+
sm: 'text-sm',
|
|
46
|
+
md: 'text-base',
|
|
47
|
+
lg: 'text-lg'
|
|
48
|
+
};
|
|
49
|
+
const variantClasses = {
|
|
50
|
+
default: 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white',
|
|
51
|
+
primary: 'text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-200',
|
|
52
|
+
secondary: 'text-purple-600 dark:text-purple-400 hover:text-purple-800 dark:hover:text-purple-200',
|
|
53
|
+
outline: 'text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white'
|
|
54
|
+
};
|
|
55
|
+
if (!isVisible)
|
|
56
|
+
return null;
|
|
57
|
+
return (_jsx("div", { ref: ref, className: cn('absolute z-10', positionClasses[position], className), ...props, children: _jsxs(Button, { onClick: scrollToTarget, variant: "ghost", size: "sm", className: cn('flex flex-col items-center space-y-2 transition-all duration-300', sizeClasses[size], variantClasses[variant], animated && 'animate-in fade-in-0 slide-in-from-bottom-2 duration-500'), "aria-label": text, children: [_jsx("span", { className: "text-xs opacity-80", children: text }), _jsx(Icon, { name: iconName, size: iconSize, className: cn(animated && 'animate-bounce') })] }) }));
|
|
58
|
+
});
|
|
59
|
+
ScrollIndicator.displayName = 'ScrollIndicator';
|
|
60
|
+
export { ScrollIndicator };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface ScrollProgressProps {
|
|
3
|
+
className?: string;
|
|
4
|
+
height?: number;
|
|
5
|
+
color?: 'default' | 'primary' | 'secondary' | 'gradient';
|
|
6
|
+
position?: 'top' | 'bottom';
|
|
7
|
+
animated?: boolean;
|
|
8
|
+
showPercentage?: boolean;
|
|
9
|
+
}
|
|
10
|
+
declare const ScrollProgress: React.ForwardRefExoticComponent<ScrollProgressProps & React.RefAttributes<HTMLDivElement>>;
|
|
11
|
+
export { ScrollProgress };
|
|
12
|
+
//# sourceMappingURL=ScrollProgress.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ScrollProgress.d.ts","sourceRoot":"","sources":["../../src/components/ScrollProgress.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA8B,MAAM,OAAO,CAAA;AAGlD,MAAM,WAAW,mBAAmB;IAClC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,UAAU,CAAA;IACxD,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAA;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,cAAc,CAAC,EAAE,OAAO,CAAA;CACzB;AAED,QAAA,MAAM,cAAc,4FA6ElB,CAAA;AAIF,OAAO,EAAE,cAAc,EAAE,CAAA"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import React, { useState, useEffect } from 'react';
|
|
4
|
+
import { cn } from '../lib/utils';
|
|
5
|
+
const ScrollProgress = React.forwardRef(({ className, height = 2, color = 'gradient', position = 'top', animated = true, showPercentage = false, ...props }, ref) => {
|
|
6
|
+
const [progress, setProgress] = useState(0);
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
const updateProgress = () => {
|
|
9
|
+
const scrollTop = window.scrollY;
|
|
10
|
+
const docHeight = document.documentElement.scrollHeight - window.innerHeight;
|
|
11
|
+
const currentProgress = docHeight > 0 ? (scrollTop / docHeight) * 100 : 0;
|
|
12
|
+
setProgress(currentProgress);
|
|
13
|
+
};
|
|
14
|
+
// 초기 실행
|
|
15
|
+
updateProgress();
|
|
16
|
+
window.addEventListener('scroll', updateProgress, { passive: true });
|
|
17
|
+
window.addEventListener('resize', updateProgress, { passive: true });
|
|
18
|
+
return () => {
|
|
19
|
+
window.removeEventListener('scroll', updateProgress);
|
|
20
|
+
window.removeEventListener('resize', updateProgress);
|
|
21
|
+
};
|
|
22
|
+
}, []);
|
|
23
|
+
const colorClasses = {
|
|
24
|
+
default: 'bg-blue-600',
|
|
25
|
+
primary: 'bg-purple-600',
|
|
26
|
+
secondary: 'bg-gray-600',
|
|
27
|
+
gradient: 'bg-gradient-to-r from-purple-600 via-blue-600 to-purple-600'
|
|
28
|
+
};
|
|
29
|
+
const positionClasses = {
|
|
30
|
+
top: 'top-0 left-0 right-0',
|
|
31
|
+
bottom: 'bottom-0 left-0 right-0'
|
|
32
|
+
};
|
|
33
|
+
return (_jsxs("div", { ref: ref, className: cn('fixed z-50', positionClasses[position], className), style: { height: `${height}px` }, ...props, children: [_jsx("div", { className: "w-full h-full bg-gray-200 dark:bg-gray-700" }), _jsx("div", { className: cn('h-full origin-left transition-all duration-100 ease-out', colorClasses[color]), style: {
|
|
34
|
+
width: `${progress}%`,
|
|
35
|
+
transformOrigin: 'left'
|
|
36
|
+
} }), showPercentage && (_jsxs("div", { className: "absolute top-2 right-2 text-xs text-gray-600 dark:text-gray-400 bg-white dark:bg-gray-800 px-2 py-1 rounded", children: [Math.round(progress), "%"] }))] }));
|
|
37
|
+
});
|
|
38
|
+
ScrollProgress.displayName = 'ScrollProgress';
|
|
39
|
+
export { ScrollProgress };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface ScrollToTopProps {
|
|
3
|
+
className?: string;
|
|
4
|
+
threshold?: number;
|
|
5
|
+
position?: 'bottom-right' | 'bottom-left' | 'bottom-center';
|
|
6
|
+
size?: 'sm' | 'md' | 'lg';
|
|
7
|
+
variant?: 'default' | 'primary' | 'secondary' | 'outline';
|
|
8
|
+
showIcon?: boolean;
|
|
9
|
+
iconName?: string;
|
|
10
|
+
iconSize?: number;
|
|
11
|
+
animated?: boolean;
|
|
12
|
+
}
|
|
13
|
+
declare const ScrollToTop: React.ForwardRefExoticComponent<ScrollToTopProps & React.RefAttributes<HTMLDivElement>>;
|
|
14
|
+
export { ScrollToTop };
|
|
15
|
+
//# sourceMappingURL=ScrollToTop.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ScrollToTop.d.ts","sourceRoot":"","sources":["../../src/components/ScrollToTop.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA8B,MAAM,OAAO,CAAA;AAKlD,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,cAAc,GAAG,aAAa,GAAG,eAAe,CAAA;IAC3D,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;IACzB,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,CAAA;IACzD,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,QAAA,MAAM,WAAW,yFAsGf,CAAA;AAIF,OAAO,EAAE,WAAW,EAAE,CAAA"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import React, { useState, useEffect } from 'react';
|
|
4
|
+
import { Button } from './Button';
|
|
5
|
+
import { cn } from '../lib/utils';
|
|
6
|
+
const ScrollToTop = React.forwardRef(({ className, threshold = 300, position = 'bottom-right', size = 'md', variant = 'default', showIcon = true, iconName = 'arrowUp', iconSize = 20, animated = true, ...props }, ref) => {
|
|
7
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
const toggleVisibility = () => {
|
|
10
|
+
const scrollTop = window.scrollY;
|
|
11
|
+
const shouldShow = scrollTop > threshold;
|
|
12
|
+
setIsVisible(shouldShow);
|
|
13
|
+
};
|
|
14
|
+
// 초기 실행
|
|
15
|
+
toggleVisibility();
|
|
16
|
+
window.addEventListener('scroll', toggleVisibility, { passive: true });
|
|
17
|
+
return () => window.removeEventListener('scroll', toggleVisibility);
|
|
18
|
+
}, [threshold]);
|
|
19
|
+
const scrollToTop = () => {
|
|
20
|
+
window.scrollTo({
|
|
21
|
+
top: 0,
|
|
22
|
+
behavior: 'smooth',
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
const positionClasses = {
|
|
26
|
+
'bottom-right': 'bottom-8 right-8', // 32px 여백
|
|
27
|
+
'bottom-left': 'bottom-8 left-8', // 32px 여백
|
|
28
|
+
'bottom-center': 'bottom-8 left-1/2 transform -translate-x-1/2' // 32px 여백
|
|
29
|
+
};
|
|
30
|
+
const sizeClasses = {
|
|
31
|
+
sm: 'w-12 h-12', // 48px - 더 넉넉한 크기
|
|
32
|
+
md: 'w-14 h-14', // 56px - 더 넉넉한 크기
|
|
33
|
+
lg: 'w-16 h-16' // 64px - 더 넉넉한 크기
|
|
34
|
+
};
|
|
35
|
+
const variantClasses = {
|
|
36
|
+
default: 'bg-gray-800 dark:bg-gray-200 text-white dark:text-gray-800 hover:bg-gray-700 dark:hover:bg-gray-300',
|
|
37
|
+
primary: 'bg-blue-600 text-white hover:bg-blue-700',
|
|
38
|
+
secondary: 'bg-gray-600 text-white hover:bg-gray-700',
|
|
39
|
+
outline: 'bg-white dark:bg-gray-800 border-2 border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700'
|
|
40
|
+
};
|
|
41
|
+
if (!isVisible)
|
|
42
|
+
return null;
|
|
43
|
+
return (_jsx("div", { ref: ref, className: cn('fixed z-50', positionClasses[position], className), ...props, children: _jsx(Button, { onClick: scrollToTop, size: "icon", variant: "default", className: cn('rounded-full shadow-lg hover:shadow-xl transition-all duration-300', sizeClasses[size], variantClasses[variant], animated && 'animate-in fade-in-0 slide-in-from-bottom-2 duration-300'), "aria-label": "Scroll to top", children: showIcon && (_jsx("svg", { className: cn('text-white', animated && 'animate-bounce'), width: iconSize, height: iconSize, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M5 10l7-7m0 0l7 7m-7-7v18" }) })) }) }));
|
|
44
|
+
});
|
|
45
|
+
ScrollToTop.displayName = 'ScrollToTop';
|
|
46
|
+
export { ScrollToTop };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export interface SelectProps extends Omit<React.SelectHTMLAttributes<HTMLSelectElement>, 'size'> {
|
|
3
|
+
variant?: "default" | "outline" | "filled" | "ghost";
|
|
4
|
+
size?: "sm" | "md" | "lg";
|
|
5
|
+
error?: boolean;
|
|
6
|
+
success?: boolean;
|
|
7
|
+
leftIcon?: React.ReactNode;
|
|
8
|
+
placeholder?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface SelectOptionProps extends React.OptionHTMLAttributes<HTMLOptionElement> {
|
|
11
|
+
value: string;
|
|
12
|
+
children: React.ReactNode;
|
|
13
|
+
}
|
|
14
|
+
declare const Select: React.ForwardRefExoticComponent<SelectProps & React.RefAttributes<HTMLSelectElement>>;
|
|
15
|
+
declare const SelectOption: React.ForwardRefExoticComponent<SelectOptionProps & React.RefAttributes<HTMLOptionElement>>;
|
|
16
|
+
export { Select, SelectOption };
|
|
17
|
+
//# sourceMappingURL=Select.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Select.d.ts","sourceRoot":"","sources":["../../src/components/Select.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B,MAAM,WAAW,WAAY,SAAQ,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC9F,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAA;IACpD,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;IACzB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,iBAAkB,SAAQ,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC;IACtF,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B;AAED,QAAA,MAAM,MAAM,uFAgEX,CAAA;AAGD,QAAA,MAAM,YAAY,6FAQjB,CAAA;AAGD,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,CAAA"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../lib/utils";
|
|
5
|
+
import { Icon } from "./Icon";
|
|
6
|
+
const Select = React.forwardRef(({ className, variant = "default", size = "md", error = false, success = false, leftIcon, placeholder, children, ...props }, ref) => {
|
|
7
|
+
const variantClasses = {
|
|
8
|
+
default: "border-gray-300 bg-white text-gray-900 focus:border-blue-500 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-800 dark:text-white dark:focus:border-blue-400 dark:focus:ring-blue-400",
|
|
9
|
+
outline: "border-2 border-gray-200 bg-transparent text-gray-900 focus:border-blue-500 focus:ring-blue-500 dark:border-gray-700 dark:text-white dark:focus:border-blue-400 dark:focus:ring-blue-400",
|
|
10
|
+
filled: "border-transparent bg-gray-50 text-gray-900 focus:bg-white focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-700 dark:text-white dark:focus:bg-gray-800 dark:focus:border-blue-400 dark:focus:ring-blue-400",
|
|
11
|
+
ghost: "border-transparent bg-transparent text-gray-900 focus:bg-gray-50 focus:border-gray-300 focus:ring-gray-500 dark:text-white dark:focus:bg-gray-800 dark:focus:border-gray-600 dark:focus:ring-gray-400"
|
|
12
|
+
};
|
|
13
|
+
const sizeClasses = {
|
|
14
|
+
sm: "h-8 px-3 text-sm",
|
|
15
|
+
md: "h-10 px-4 text-base",
|
|
16
|
+
lg: "h-12 px-4 text-lg"
|
|
17
|
+
};
|
|
18
|
+
const stateClasses = error
|
|
19
|
+
? "border-red-500 focus:border-red-500 focus:ring-red-500 dark:border-red-400 dark:focus:border-red-400 dark:focus:ring-red-400"
|
|
20
|
+
: success
|
|
21
|
+
? "border-green-500 focus:border-green-500 focus:ring-green-500 dark:border-green-400 dark:focus:border-green-400 dark:focus:ring-green-400"
|
|
22
|
+
: "";
|
|
23
|
+
return (_jsxs("div", { className: "relative", children: [leftIcon && (_jsx("div", { className: "absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 dark:text-gray-500 pointer-events-none", children: leftIcon })), _jsxs("select", { className: cn("flex w-full appearance-none rounded-md border transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", variantClasses[variant], sizeClasses[size], stateClasses, leftIcon ? "pl-10" : "", "pr-10", // 화살표 아이콘을 위한 공간
|
|
24
|
+
className), ref: ref, ...props, children: [placeholder && (_jsx("option", { value: "", disabled: true, children: placeholder })), children] }), _jsx("div", { className: "absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 dark:text-gray-500 pointer-events-none", children: _jsx(Icon, { name: "chevronDown", size: 16 }) })] }));
|
|
25
|
+
});
|
|
26
|
+
Select.displayName = "Select";
|
|
27
|
+
const SelectOption = React.forwardRef(({ className, ...props }, ref) => (_jsx("option", { className: cn("", className), ref: ref, ...props })));
|
|
28
|
+
SelectOption.displayName = "SelectOption";
|
|
29
|
+
export { Select, SelectOption };
|