@hunterchen/canvas 0.1.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/dist/components/canvas/canvas.d.ts +29 -0
- package/dist/components/canvas/canvas.d.ts.map +1 -0
- package/dist/components/canvas/canvas.js +419 -0
- package/dist/components/canvas/canvas.js.map +1 -0
- package/dist/components/canvas/component.d.ts +47 -0
- package/dist/components/canvas/component.d.ts.map +1 -0
- package/dist/components/canvas/component.js +177 -0
- package/dist/components/canvas/component.js.map +1 -0
- package/dist/components/canvas/cursor.d.ts +8 -0
- package/dist/components/canvas/cursor.d.ts.map +1 -0
- package/dist/components/canvas/cursor.js +32 -0
- package/dist/components/canvas/cursor.js.map +1 -0
- package/dist/components/canvas/draggable.d.ts +21 -0
- package/dist/components/canvas/draggable.d.ts.map +1 -0
- package/dist/components/canvas/draggable.js +163 -0
- package/dist/components/canvas/draggable.js.map +1 -0
- package/dist/components/canvas/navbar/index.d.ts +19 -0
- package/dist/components/canvas/navbar/index.d.ts.map +1 -0
- package/dist/components/canvas/navbar/index.js +106 -0
- package/dist/components/canvas/navbar/index.js.map +1 -0
- package/dist/components/canvas/navbar/single-button.d.ts +17 -0
- package/dist/components/canvas/navbar/single-button.d.ts.map +1 -0
- package/dist/components/canvas/navbar/single-button.js +97 -0
- package/dist/components/canvas/navbar/single-button.js.map +1 -0
- package/dist/components/canvas/offest.d.ts +6 -0
- package/dist/components/canvas/offest.d.ts.map +1 -0
- package/dist/components/canvas/offest.js +12 -0
- package/dist/components/canvas/offest.js.map +1 -0
- package/dist/components/canvas/reset.d.ts +5 -0
- package/dist/components/canvas/reset.d.ts.map +1 -0
- package/dist/components/canvas/reset.js +7 -0
- package/dist/components/canvas/reset.js.map +1 -0
- package/dist/components/canvas/toolbar.d.ts +7 -0
- package/dist/components/canvas/toolbar.d.ts.map +1 -0
- package/dist/components/canvas/toolbar.js +28 -0
- package/dist/components/canvas/toolbar.js.map +1 -0
- package/dist/components/canvas/wrapper.d.ts +26 -0
- package/dist/components/canvas/wrapper.d.ts.map +1 -0
- package/dist/components/canvas/wrapper.js +107 -0
- package/dist/components/canvas/wrapper.js.map +1 -0
- package/dist/components/ui/FolderIcon.d.ts +9 -0
- package/dist/components/ui/FolderIcon.d.ts.map +1 -0
- package/dist/components/ui/FolderIcon.js +25 -0
- package/dist/components/ui/FolderIcon.js.map +1 -0
- package/dist/components/ui/button.d.ts +14 -0
- package/dist/components/ui/button.d.ts.map +1 -0
- package/dist/components/ui/button.js +54 -0
- package/dist/components/ui/button.js.map +1 -0
- package/dist/components/ui/label.d.ts +6 -0
- package/dist/components/ui/label.d.ts.map +1 -0
- package/dist/components/ui/label.js +10 -0
- package/dist/components/ui/label.js.map +1 -0
- package/dist/components/ui/toast.d.ts +16 -0
- package/dist/components/ui/toast.d.ts.map +1 -0
- package/dist/components/ui/toast.js +41 -0
- package/dist/components/ui/toast.js.map +1 -0
- package/dist/components/ui/toaster.d.ts +2 -0
- package/dist/components/ui/toaster.d.ts.map +1 -0
- package/dist/components/ui/toaster.js +10 -0
- package/dist/components/ui/toaster.js.map +1 -0
- package/dist/contexts/CanvasContext.d.ts +26 -0
- package/dist/contexts/CanvasContext.d.ts.map +1 -0
- package/dist/contexts/CanvasContext.js +22 -0
- package/dist/contexts/CanvasContext.js.map +1 -0
- package/dist/contexts/PerformanceContext.d.ts +31 -0
- package/dist/contexts/PerformanceContext.d.ts.map +1 -0
- package/dist/contexts/PerformanceContext.js +56 -0
- package/dist/contexts/PerformanceContext.js.map +1 -0
- package/dist/hooks/use-mobile.d.ts +2 -0
- package/dist/hooks/use-mobile.d.ts.map +1 -0
- package/dist/hooks/use-mobile.js +16 -0
- package/dist/hooks/use-mobile.js.map +1 -0
- package/dist/hooks/use-toast.d.ts +45 -0
- package/dist/hooks/use-toast.d.ts.map +1 -0
- package/dist/hooks/use-toast.js +126 -0
- package/dist/hooks/use-toast.js.map +1 -0
- package/dist/hooks/usePerformanceMode.d.ts +6 -0
- package/dist/hooks/usePerformanceMode.d.ts.map +1 -0
- package/dist/hooks/usePerformanceMode.js +6 -0
- package/dist/hooks/usePerformanceMode.js.map +1 -0
- package/dist/hooks/useWindowDimensions.d.ts +7 -0
- package/dist/hooks/useWindowDimensions.d.ts.map +1 -0
- package/dist/hooks/useWindowDimensions.js +22 -0
- package/dist/hooks/useWindowDimensions.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/canvas.d.ts +35 -0
- package/dist/lib/canvas.d.ts.map +1 -0
- package/dist/lib/canvas.js +82 -0
- package/dist/lib/canvas.js.map +1 -0
- package/dist/lib/constants.d.ts +78 -0
- package/dist/lib/constants.d.ts.map +1 -0
- package/dist/lib/constants.js +122 -0
- package/dist/lib/constants.js.map +1 -0
- package/dist/lib/copy.d.ts +2 -0
- package/dist/lib/copy.d.ts.map +1 -0
- package/dist/lib/copy.js +20 -0
- package/dist/lib/copy.js.map +1 -0
- package/dist/lib/utils.d.ts +4 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +14 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/types/index.d.ts +19 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +14 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/performance.d.ts +9 -0
- package/dist/utils/performance.d.ts.map +1 -0
- package/dist/utils/performance.js +29 -0
- package/dist/utils/performance.js.map +1 -0
- package/package.json +55 -0
- package/src/components/canvas/canvas.tsx +728 -0
- package/src/components/canvas/component.tsx +230 -0
- package/src/components/canvas/cursor.tsx +161 -0
- package/src/components/canvas/draggable.tsx +298 -0
- package/src/components/canvas/navbar/index.tsx +213 -0
- package/src/components/canvas/navbar/single-button.tsx +199 -0
- package/src/components/canvas/offest.tsx +23 -0
- package/src/components/canvas/reset.tsx +21 -0
- package/src/components/canvas/toolbar.tsx +67 -0
- package/src/components/canvas/wrapper.tsx +219 -0
- package/src/components/ui/FolderIcon.tsx +116 -0
- package/src/components/ui/button.tsx +162 -0
- package/src/components/ui/label.tsx +24 -0
- package/src/components/ui/toast.tsx +136 -0
- package/src/components/ui/toaster.tsx +33 -0
- package/src/contexts/CanvasContext.tsx +54 -0
- package/src/contexts/PerformanceContext.tsx +81 -0
- package/src/hooks/use-mobile.ts +21 -0
- package/src/hooks/use-toast.ts +186 -0
- package/src/hooks/usePerformanceMode.ts +5 -0
- package/src/hooks/useWindowDimensions.ts +32 -0
- package/src/index.ts +36 -0
- package/src/lib/canvas.ts +132 -0
- package/src/lib/constants.ts +153 -0
- package/src/lib/copy.ts +18 -0
- package/src/lib/utils.ts +18 -0
- package/src/types/index.ts +20 -0
- package/src/utils/performance.ts +37 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { motion, type MotionValue, type Transition, useMotionValue } from "framer-motion";
|
|
2
|
+
import { useState, useEffect, useRef, type ReactNode } from "react";
|
|
3
|
+
import Image from "next/image";
|
|
4
|
+
import {
|
|
5
|
+
MAX_DIM_RATIO,
|
|
6
|
+
GROW_TRANSITION,
|
|
7
|
+
BLUR_TRANSITION,
|
|
8
|
+
INTRO_ASPECT_RATIO,
|
|
9
|
+
} from "../../lib/constants";
|
|
10
|
+
|
|
11
|
+
// Re-export for backward compatibility
|
|
12
|
+
export { GROW_TRANSITION as growTransition } from "../../lib/constants";
|
|
13
|
+
|
|
14
|
+
interface CanvasWrapperProps {
|
|
15
|
+
children: React.ReactNode;
|
|
16
|
+
/** Shared progress MV (0->1) for the grow animation */
|
|
17
|
+
introProgress: MotionValue<number>;
|
|
18
|
+
/** Callback when the grow (stage1) completes */
|
|
19
|
+
onIntroGrowComplete?: () => void;
|
|
20
|
+
|
|
21
|
+
// ============== Intro Customization ==============
|
|
22
|
+
/** Disable intro animation entirely (starts at full size) */
|
|
23
|
+
skipIntro?: boolean;
|
|
24
|
+
/** Custom intro content to show during loading */
|
|
25
|
+
introContent?: ReactNode;
|
|
26
|
+
/** Custom loading text (default: "LOADING CANVAS") */
|
|
27
|
+
loadingText?: string;
|
|
28
|
+
/** Background gradient for intro screen */
|
|
29
|
+
introBackgroundGradient?: string;
|
|
30
|
+
/** Canvas box gradient for blur mask */
|
|
31
|
+
canvasBoxGradient?: string;
|
|
32
|
+
/** Grow animation transition config */
|
|
33
|
+
growTransition?: Transition;
|
|
34
|
+
/** Blur animation transition config */
|
|
35
|
+
blurTransition?: Transition;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Default intro content (Hack Western branding)
|
|
40
|
+
* Positioned in the upper third of the screen
|
|
41
|
+
*/
|
|
42
|
+
const DefaultIntroContent = () => (
|
|
43
|
+
<div className="absolute left-1/2 top-24 flex -translate-x-1/2 flex-col items-center text-center">
|
|
44
|
+
<Image
|
|
45
|
+
src="/horse.svg"
|
|
46
|
+
alt="Hack Western Logo"
|
|
47
|
+
width={64}
|
|
48
|
+
height={64}
|
|
49
|
+
className="mb-4"
|
|
50
|
+
/>
|
|
51
|
+
<div className="font-jetbrains-mono font-semibold text-[#543C5AB2]">
|
|
52
|
+
HACK WESTERN 12
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
export const CanvasWrapper = ({
|
|
58
|
+
children,
|
|
59
|
+
introProgress,
|
|
60
|
+
onIntroGrowComplete,
|
|
61
|
+
skipIntro = false,
|
|
62
|
+
introContent,
|
|
63
|
+
loadingText = "LOADING CANVAS",
|
|
64
|
+
introBackgroundGradient = "linear-gradient(to top, #FEB6AF 0%, var(--salmon) 15%, var(--beige) 50%)",
|
|
65
|
+
canvasBoxGradient = "radial-gradient(130.38% 95% at 50.03% 97.25%, #EFB8A0 0%, #EAD2DF 48.09%, #EFE3E1 100%)",
|
|
66
|
+
growTransition = GROW_TRANSITION,
|
|
67
|
+
blurTransition = BLUR_TRANSITION,
|
|
68
|
+
}: CanvasWrapperProps) => {
|
|
69
|
+
const [dimensions, setDimensions] = useState<{
|
|
70
|
+
width: number;
|
|
71
|
+
height: number;
|
|
72
|
+
} | null>(null);
|
|
73
|
+
const [dots, setDots] = useState<string>("..");
|
|
74
|
+
const [stage1NotFinished, setStage1NotFinished] = useState(true);
|
|
75
|
+
const completedRef = useRef(false);
|
|
76
|
+
|
|
77
|
+
// If skipIntro is true, immediately complete the intro
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (skipIntro && !completedRef.current) {
|
|
80
|
+
completedRef.current = true;
|
|
81
|
+
introProgress.set(1);
|
|
82
|
+
onIntroGrowComplete?.();
|
|
83
|
+
}
|
|
84
|
+
}, [skipIntro, introProgress, onIntroGrowComplete]);
|
|
85
|
+
|
|
86
|
+
// add up to 4 dots, then go back down to 2
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
if (skipIntro) return; // Don't animate dots if skipping intro
|
|
89
|
+
|
|
90
|
+
const interval = setInterval(() => {
|
|
91
|
+
setDots((prevDots) => {
|
|
92
|
+
if (prevDots.length < 3) {
|
|
93
|
+
return prevDots + ".";
|
|
94
|
+
} else {
|
|
95
|
+
return ".";
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}, 500);
|
|
99
|
+
return () => clearInterval(interval);
|
|
100
|
+
}, [skipIntro]);
|
|
101
|
+
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
if (skipIntro) return; // Don't calculate dimensions if skipping intro
|
|
104
|
+
|
|
105
|
+
// calculate the initial 3:2 box size with margins (client-only)
|
|
106
|
+
const calculateInitialSize = () => {
|
|
107
|
+
const vw = window.innerWidth;
|
|
108
|
+
const vh = window.innerHeight;
|
|
109
|
+
|
|
110
|
+
const maxWidth = vw * MAX_DIM_RATIO.width;
|
|
111
|
+
const maxHeight = vh * MAX_DIM_RATIO.height;
|
|
112
|
+
|
|
113
|
+
// width or height as limiter
|
|
114
|
+
if (maxWidth / INTRO_ASPECT_RATIO <= maxHeight) {
|
|
115
|
+
return { width: maxWidth, height: maxWidth / INTRO_ASPECT_RATIO };
|
|
116
|
+
} else {
|
|
117
|
+
return { height: maxHeight, width: maxHeight * INTRO_ASPECT_RATIO };
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
setDimensions(calculateInitialSize());
|
|
122
|
+
}, [skipIntro]);
|
|
123
|
+
|
|
124
|
+
// If skipIntro, render children directly without animation wrapper
|
|
125
|
+
if (skipIntro) {
|
|
126
|
+
return (
|
|
127
|
+
<motion.div
|
|
128
|
+
className="fixed inset-0 overflow-hidden"
|
|
129
|
+
style={{
|
|
130
|
+
touchAction: "none",
|
|
131
|
+
userSelect: "none",
|
|
132
|
+
pointerEvents: "none",
|
|
133
|
+
}}
|
|
134
|
+
onContextMenu={(e) => e.preventDefault()}
|
|
135
|
+
>
|
|
136
|
+
{children}
|
|
137
|
+
</motion.div>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<motion.div
|
|
143
|
+
className="fixed inset-0 overflow-hidden"
|
|
144
|
+
style={{
|
|
145
|
+
backgroundImage: stage1NotFinished ? introBackgroundGradient : undefined,
|
|
146
|
+
touchAction: "none",
|
|
147
|
+
userSelect: "none",
|
|
148
|
+
pointerEvents: "none",
|
|
149
|
+
}}
|
|
150
|
+
onContextMenu={(e) => e.preventDefault()}
|
|
151
|
+
>
|
|
152
|
+
{stage1NotFinished && (
|
|
153
|
+
<>
|
|
154
|
+
{/* Render custom intro content or default */}
|
|
155
|
+
{introContent !== undefined ? introContent : <DefaultIntroContent />}
|
|
156
|
+
</>
|
|
157
|
+
)}
|
|
158
|
+
|
|
159
|
+
{dimensions && (
|
|
160
|
+
<>
|
|
161
|
+
{/* Blurring mask box */}
|
|
162
|
+
<motion.div
|
|
163
|
+
initial={{
|
|
164
|
+
width: dimensions.width,
|
|
165
|
+
height: dimensions.height,
|
|
166
|
+
opacity: 1,
|
|
167
|
+
backgroundImage: canvasBoxGradient,
|
|
168
|
+
}}
|
|
169
|
+
animate={{
|
|
170
|
+
opacity: 0,
|
|
171
|
+
display: "none",
|
|
172
|
+
}}
|
|
173
|
+
transition={blurTransition}
|
|
174
|
+
className="absolute left-1/2 top-1/2 z-20 origin-center -translate-x-1/2 -translate-y-1/2 overflow-hidden rounded-lg"
|
|
175
|
+
/>
|
|
176
|
+
{/* Growing wrapper drives introProgress */}
|
|
177
|
+
<motion.div
|
|
178
|
+
initial={{
|
|
179
|
+
width: dimensions.width,
|
|
180
|
+
height: dimensions.height,
|
|
181
|
+
}}
|
|
182
|
+
animate={{
|
|
183
|
+
width: "100vw",
|
|
184
|
+
height: "100vh",
|
|
185
|
+
}}
|
|
186
|
+
transition={growTransition}
|
|
187
|
+
onUpdate={(latest: { width?: number; height?: number }) => {
|
|
188
|
+
if (completedRef.current) return;
|
|
189
|
+
if (typeof latest.width === "number") {
|
|
190
|
+
const w0 = dimensions.width;
|
|
191
|
+
const w1 = window.innerWidth;
|
|
192
|
+
const progress =
|
|
193
|
+
w1 === w0 ? 1 : (latest.width - w0) / (w1 - w0);
|
|
194
|
+
const clamped = Math.min(Math.max(progress, 0), 1);
|
|
195
|
+
introProgress.set(clamped);
|
|
196
|
+
}
|
|
197
|
+
}}
|
|
198
|
+
onAnimationComplete={() => {
|
|
199
|
+
if (!completedRef.current) {
|
|
200
|
+
completedRef.current = true;
|
|
201
|
+
introProgress.set(1);
|
|
202
|
+
setStage1NotFinished(false);
|
|
203
|
+
onIntroGrowComplete?.();
|
|
204
|
+
}
|
|
205
|
+
}}
|
|
206
|
+
className="absolute left-1/2 top-1/2 z-10 origin-center -translate-x-1/2 -translate-y-1/2 overflow-hidden rounded-lg shadow-[0_20px_40px_rgba(103,86,86,0.15)]"
|
|
207
|
+
>
|
|
208
|
+
<div className="h-full w-full">{children}</div>
|
|
209
|
+
</motion.div>
|
|
210
|
+
</>
|
|
211
|
+
)}
|
|
212
|
+
{stage1NotFinished && loadingText && (
|
|
213
|
+
<div className="absolute bottom-24 left-1/2 -translate-x-1/2 text-center font-jetbrains-mono font-semibold text-[#543C5AB2]">
|
|
214
|
+
{loadingText}{dots}
|
|
215
|
+
</div>
|
|
216
|
+
)}
|
|
217
|
+
</motion.div>
|
|
218
|
+
);
|
|
219
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { motion } from "framer-motion";
|
|
2
|
+
import useWindowDimensions from "../../hooks/useWindowDimensions";
|
|
3
|
+
|
|
4
|
+
type FolderIconProps = {
|
|
5
|
+
className?: string;
|
|
6
|
+
gradientId?: string;
|
|
7
|
+
strokeColor?: string;
|
|
8
|
+
isOpen?: boolean;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const AnimatedFolderIcon = ({
|
|
12
|
+
className = "",
|
|
13
|
+
gradientId = "defaultGradient",
|
|
14
|
+
strokeColor = "rgba(119, 103, 128, 0.1)",
|
|
15
|
+
isOpen = false,
|
|
16
|
+
}: FolderIconProps) => {
|
|
17
|
+
const { width } = useWindowDimensions();
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<svg
|
|
21
|
+
viewBox="0 0 208 163"
|
|
22
|
+
className={className}
|
|
23
|
+
fill="none"
|
|
24
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
25
|
+
>
|
|
26
|
+
<g filter="url(#folderShadow)">
|
|
27
|
+
{/* Closed Folder */}
|
|
28
|
+
<motion.g
|
|
29
|
+
initial={false}
|
|
30
|
+
animate={{
|
|
31
|
+
opacity: !isOpen || width < 640 ? 1 : 0,
|
|
32
|
+
scale: isOpen ? 1 : 1,
|
|
33
|
+
y: isOpen ? 5 : 0,
|
|
34
|
+
x: isOpen ? 15 : 15,
|
|
35
|
+
}}
|
|
36
|
+
transition={{
|
|
37
|
+
duration: 0.2,
|
|
38
|
+
ease: "easeInOut",
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
41
|
+
<path
|
|
42
|
+
d="M4.34444 14.3331C4.1561 8.68286 8.68546 4 14.3389 4L76.1646 4C80.3832 4 84.4311 5.66604 87.4274 8.63558L98.3226 19.4333C101.319 22.4028 105.367 24.0689 109.585 24.0689H173.601C179.277 24.0689 183.815 28.7881 183.593 34.4601L179.876 129.391C179.666 134.758 175.255 139 169.884 139H18.1722C12.7791 139 8.35744 134.723 8.17777 129.333L4.34444 14.3331Z"
|
|
43
|
+
fill={`url(#${gradientId})`}
|
|
44
|
+
stroke={strokeColor}
|
|
45
|
+
strokeWidth="0.75"
|
|
46
|
+
fillOpacity={width < 640 ? "1" : "0.8"}
|
|
47
|
+
/>
|
|
48
|
+
</motion.g>
|
|
49
|
+
|
|
50
|
+
{/* Open Folder */}
|
|
51
|
+
<motion.g
|
|
52
|
+
initial={false}
|
|
53
|
+
animate={{
|
|
54
|
+
opacity: isOpen ? 1 : 0,
|
|
55
|
+
scale: isOpen ? 1.15 : 1.15,
|
|
56
|
+
y: isOpen ? 20 : 20,
|
|
57
|
+
x: isOpen ? 23 : 23,
|
|
58
|
+
}}
|
|
59
|
+
transition={{
|
|
60
|
+
duration: 0.2,
|
|
61
|
+
ease: "easeInOut",
|
|
62
|
+
}}
|
|
63
|
+
>
|
|
64
|
+
<path
|
|
65
|
+
d="M3.72714 12.7562C3.28295 7.78306 7.20051 3.5 12.1934 3.5L67.2044 3.5C70.2086 3.5 73.1283 4.49474 75.5076 6.32893L89.3807 17.0235C91.76 18.8577 94.6797 19.8524 97.6839 19.8524H154.462C159.507 19.8524 163.442 24.2216 162.916 29.2393L154.874 105.887C154.42 110.214 150.772 113.5 146.421 113.5H20.5C16.0986 113.5 12.4253 110.14 12.0337 105.756L3.72714 12.7562Z"
|
|
66
|
+
fill={`url(#${gradientId})`}
|
|
67
|
+
stroke={strokeColor}
|
|
68
|
+
strokeOpacity="1"
|
|
69
|
+
strokeWidth="0.85"
|
|
70
|
+
fillOpacity="1"
|
|
71
|
+
/>
|
|
72
|
+
</motion.g>
|
|
73
|
+
</g>
|
|
74
|
+
<defs>
|
|
75
|
+
<linearGradient id="red" x1="0" y1="0" x2="0" y2="1">
|
|
76
|
+
<stop offset="0%" stopColor="rgba(226, 141, 159, 1)" />
|
|
77
|
+
<stop offset="100%" stopColor="rgba(244, 193, 204, 1)" />
|
|
78
|
+
</linearGradient>
|
|
79
|
+
<linearGradient id="blue" x1="0" y1="0" x2="0" y2="1">
|
|
80
|
+
<stop offset="0%" stopColor="rgba(90, 149, 208, 1)" />
|
|
81
|
+
<stop offset="100%" stopColor="rgba(158, 202, 246, 1)" />
|
|
82
|
+
</linearGradient>
|
|
83
|
+
<linearGradient id="green" x1="0" y1="0" x2="0" y2="1">
|
|
84
|
+
<stop offset="0%" stopColor="rgba(127, 202, 132, 1)" />
|
|
85
|
+
<stop offset="100%" stopColor="rgba(185, 227, 188, 1)" />
|
|
86
|
+
</linearGradient>
|
|
87
|
+
<linearGradient id="purple" x1="0" y1="0" x2="0" y2="1">
|
|
88
|
+
<stop offset="0%" stopColor="rgba(143, 87, 173, 1)" />
|
|
89
|
+
<stop offset="100%" stopColor="rgba(209, 154, 238, 1)" />
|
|
90
|
+
</linearGradient>
|
|
91
|
+
<linearGradient id="orange" x1="0" y1="0" x2="0" y2="1">
|
|
92
|
+
<stop offset="0%" stopColor="rgba(233, 148, 111, 1)" />
|
|
93
|
+
<stop offset="100%" stopColor="rgba(246, 185, 158, 1)" />
|
|
94
|
+
</linearGradient>
|
|
95
|
+
|
|
96
|
+
<filter
|
|
97
|
+
id="folderShadow"
|
|
98
|
+
x="-10"
|
|
99
|
+
y="-10"
|
|
100
|
+
width="208"
|
|
101
|
+
height="163"
|
|
102
|
+
filterUnits="userSpaceOnUse"
|
|
103
|
+
>
|
|
104
|
+
<feDropShadow
|
|
105
|
+
dx="0"
|
|
106
|
+
dy="0"
|
|
107
|
+
stdDeviation="3"
|
|
108
|
+
floodColor="rgba(119, 115, 149, 0.35)"
|
|
109
|
+
/>
|
|
110
|
+
</filter>
|
|
111
|
+
</defs>
|
|
112
|
+
</svg>
|
|
113
|
+
);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export default AnimatedFolderIcon;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import Image from "next/image";
|
|
3
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
4
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
5
|
+
import { cn } from "../../lib/utils";
|
|
6
|
+
|
|
7
|
+
const buttonBase =
|
|
8
|
+
"inline-flex items-center justify-center whitespace-nowrap rounded-lg text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 text-white font-figtree cursor-pointer";
|
|
9
|
+
|
|
10
|
+
const lift =
|
|
11
|
+
"-translate-y-[3px] group-hover:-translate-y-[4px] group-active:-translate-y-[1px] transition-all duration-100";
|
|
12
|
+
|
|
13
|
+
export const buttonVariants = cva(buttonBase, {
|
|
14
|
+
variants: {
|
|
15
|
+
variant: {
|
|
16
|
+
default: "",
|
|
17
|
+
primary: cn(
|
|
18
|
+
"bg-button-primary shadow-button-primary hover:bg-button-primary-hover active:bg-button-primary-active",
|
|
19
|
+
lift,
|
|
20
|
+
),
|
|
21
|
+
secondary: cn(
|
|
22
|
+
"text-[#625679] bg-button-secondary hover:bg-button-secondary-hover active:bg-button-secondary-active active:shadow-button-secondary",
|
|
23
|
+
lift,
|
|
24
|
+
),
|
|
25
|
+
tertiary: "bg-transparent text-[#625679] px-4 active:text-[#8F57AD]",
|
|
26
|
+
"tertiary-arrow":
|
|
27
|
+
"bg-transparent text-[#625679] px-4 active:text-[#8F57AD] flex items-center gap-2",
|
|
28
|
+
destructive:
|
|
29
|
+
"bg-destructive text-destructive-foreground hover:bg-destructive-dark",
|
|
30
|
+
outline: "bg-violet-100 hover:bg-muted border border-[1px] border-muted",
|
|
31
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
32
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
33
|
+
"apply-ghost":
|
|
34
|
+
"bg-[#ebdff7] bg-opacity-50 text-heavy font-semibold hover:bg-[#e6cdff] w-full justify-start",
|
|
35
|
+
apply: "text-medium hover:bg-[#ebdff7] hover:text-heavy",
|
|
36
|
+
},
|
|
37
|
+
size: {
|
|
38
|
+
default: "h-10 px-4 py-2",
|
|
39
|
+
sm: "h-8 rounded-md px-3",
|
|
40
|
+
lg: "h-11 rounded-md px-8",
|
|
41
|
+
icon: "h-10 w-10",
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
defaultVariants: { variant: "default", size: "default" },
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export interface ButtonProps
|
|
48
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
49
|
+
VariantProps<typeof buttonVariants> {
|
|
50
|
+
asChild?: boolean;
|
|
51
|
+
isPending?: boolean;
|
|
52
|
+
full?: boolean;
|
|
53
|
+
secondClass?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const overlays = {
|
|
57
|
+
primary: { bg: "bg-button-primary-back", border: "border border-white/30" },
|
|
58
|
+
secondary: {
|
|
59
|
+
bg: "bg-button-secondary-back",
|
|
60
|
+
border: "border border-white/50",
|
|
61
|
+
},
|
|
62
|
+
} as const;
|
|
63
|
+
|
|
64
|
+
const pressedByVariant: Record<"primary" | "secondary", string> = {
|
|
65
|
+
primary:
|
|
66
|
+
"!-translate-y-[1px] shadow-none bg-button-primary-active hover:!bg-button-primary-active",
|
|
67
|
+
secondary:
|
|
68
|
+
"!-translate-y-[1px] shadow-button-secondary bg-button-secondary-active hover:!bg-button-secondary-active",
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const noLift =
|
|
72
|
+
"group-hover:!translate-y-[1px] group-active:!translate-y-[1px] transition-none";
|
|
73
|
+
|
|
74
|
+
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
75
|
+
(
|
|
76
|
+
{
|
|
77
|
+
className,
|
|
78
|
+
variant = "default",
|
|
79
|
+
size,
|
|
80
|
+
isPending = false,
|
|
81
|
+
full = false,
|
|
82
|
+
asChild = false,
|
|
83
|
+
disabled,
|
|
84
|
+
secondClass = "",
|
|
85
|
+
children,
|
|
86
|
+
...props
|
|
87
|
+
},
|
|
88
|
+
ref,
|
|
89
|
+
) => {
|
|
90
|
+
const Comp = asChild ? Slot : "button";
|
|
91
|
+
const lockPressed =
|
|
92
|
+
isPending && (variant === "primary" || variant === "secondary");
|
|
93
|
+
|
|
94
|
+
const btnClasses = cn(
|
|
95
|
+
buttonVariants({ variant, size, className }),
|
|
96
|
+
lockPressed && [pressedByVariant[variant], noLift],
|
|
97
|
+
full && "w-full",
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const overlay = overlays[variant as keyof typeof overlays];
|
|
101
|
+
|
|
102
|
+
const wrapperClasses = cn(
|
|
103
|
+
"group relative inline-block w-max",
|
|
104
|
+
full && "block w-full",
|
|
105
|
+
lockPressed && ["pointer-events-none", noLift],
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<div className={wrapperClasses}>
|
|
110
|
+
{overlay && (
|
|
111
|
+
<>
|
|
112
|
+
<span
|
|
113
|
+
className={cn(
|
|
114
|
+
"pointer-events-none absolute inset-0 h-full w-full rounded-lg",
|
|
115
|
+
overlay.bg,
|
|
116
|
+
)}
|
|
117
|
+
/>
|
|
118
|
+
<span
|
|
119
|
+
className={cn(
|
|
120
|
+
"pointer-events-none absolute inset-0 z-50 h-full w-full rounded-lg",
|
|
121
|
+
overlay.border,
|
|
122
|
+
lockPressed ? "-translate-y-[1px] " + noLift : lift,
|
|
123
|
+
)}
|
|
124
|
+
/>
|
|
125
|
+
</>
|
|
126
|
+
)}
|
|
127
|
+
|
|
128
|
+
<Comp
|
|
129
|
+
ref={ref}
|
|
130
|
+
{...props}
|
|
131
|
+
className={cn("flex flex-row gap-2", btnClasses)}
|
|
132
|
+
disabled={disabled ?? isPending}
|
|
133
|
+
>
|
|
134
|
+
{variant === "tertiary-arrow" ? (
|
|
135
|
+
<div className="w-full">
|
|
136
|
+
<Image
|
|
137
|
+
src="/arrow-left.svg"
|
|
138
|
+
alt="Left Arrow"
|
|
139
|
+
width={10}
|
|
140
|
+
height={10}
|
|
141
|
+
/>
|
|
142
|
+
{children}
|
|
143
|
+
</div>
|
|
144
|
+
) : (
|
|
145
|
+
children
|
|
146
|
+
)}
|
|
147
|
+
</Comp>
|
|
148
|
+
|
|
149
|
+
{(variant === "tertiary" || variant === "tertiary-arrow") && (
|
|
150
|
+
<span
|
|
151
|
+
className={cn(
|
|
152
|
+
"block h-0 max-w-0 border-b-2 border-dashed border-[#625679] transition-all duration-200 group-hover:max-w-full group-active:border-[#8F57AD]",
|
|
153
|
+
secondClass,
|
|
154
|
+
)}
|
|
155
|
+
/>
|
|
156
|
+
)}
|
|
157
|
+
</div>
|
|
158
|
+
);
|
|
159
|
+
},
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
Button.displayName = "Button";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as LabelPrimitive from "@radix-ui/react-label";
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
+
|
|
5
|
+
import { cn } from "../../lib/utils";
|
|
6
|
+
|
|
7
|
+
const labelVariants = cva(
|
|
8
|
+
"text-[#5f476c] text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
const Label = React.forwardRef<
|
|
12
|
+
React.ElementRef<typeof LabelPrimitive.Root>,
|
|
13
|
+
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
|
14
|
+
VariantProps<typeof labelVariants>
|
|
15
|
+
>(({ className, ...props }, ref) => (
|
|
16
|
+
<LabelPrimitive.Root
|
|
17
|
+
ref={ref}
|
|
18
|
+
className={cn(labelVariants(), className)}
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
));
|
|
22
|
+
Label.displayName = LabelPrimitive.Root.displayName;
|
|
23
|
+
|
|
24
|
+
export { Label };
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as ToastPrimitives from "@radix-ui/react-toast";
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
+
import { X } from "lucide-react";
|
|
5
|
+
|
|
6
|
+
import { cn } from "../../lib/utils";
|
|
7
|
+
|
|
8
|
+
const ToastProvider = ToastPrimitives.Provider;
|
|
9
|
+
|
|
10
|
+
const ToastViewport = React.forwardRef<
|
|
11
|
+
React.ElementRef<typeof ToastPrimitives.Viewport>,
|
|
12
|
+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
|
|
13
|
+
>(({ className, ...props }, ref) => (
|
|
14
|
+
<ToastPrimitives.Viewport
|
|
15
|
+
ref={ref}
|
|
16
|
+
className={cn(
|
|
17
|
+
"fixed left-1/2 top-4 z-[100] flex max-h-screen w-auto -translate-x-1/2 flex-col p-4 sm:max-w-fit",
|
|
18
|
+
className,
|
|
19
|
+
)}
|
|
20
|
+
{...props}
|
|
21
|
+
/>
|
|
22
|
+
));
|
|
23
|
+
ToastViewport.displayName = ToastPrimitives.Viewport.displayName;
|
|
24
|
+
|
|
25
|
+
const toastVariants = cva(
|
|
26
|
+
"group pointer-events-auto relative flex w-auto min-w-fit items-center justify-between space-x-4 overflow-hidden rounded-md border px-4 py-3 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-top-full data-[state=open]:slide-in-from-top-full",
|
|
27
|
+
{
|
|
28
|
+
variants: {
|
|
29
|
+
variant: {
|
|
30
|
+
default: "border bg-background text-foreground",
|
|
31
|
+
destructive:
|
|
32
|
+
"destructive group border-destructive bg-destructive text-destructive-foreground",
|
|
33
|
+
success: "border-green-200 bg-green-50 text-green-900 shadow-lg",
|
|
34
|
+
cute: cn(
|
|
35
|
+
"border-purple-200 bg-gradient-to-r from-purple-50 to-pink-50 text-purple-900 shadow-lg rounded-lg", // Base styling
|
|
36
|
+
"w-[calc(100vw-4rem)]", // Mobile: take (100vw - 4rem) width for near full-bleed with margin
|
|
37
|
+
"mx-auto", // Center it (viewport already centers, but keep safety)
|
|
38
|
+
"min-w-0", // Prevent it from shrinking too small if content wraps
|
|
39
|
+
"sm:w-auto", // On larger screens revert to intrinsic sizing
|
|
40
|
+
"justify-center text-center space-x-0", // Center content & text horizontally
|
|
41
|
+
),
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
defaultVariants: {
|
|
45
|
+
variant: "default",
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const Toast = React.forwardRef<
|
|
51
|
+
React.ElementRef<typeof ToastPrimitives.Root>,
|
|
52
|
+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
|
|
53
|
+
VariantProps<typeof toastVariants>
|
|
54
|
+
>(({ className, variant, ...props }, ref) => {
|
|
55
|
+
return (
|
|
56
|
+
<ToastPrimitives.Root
|
|
57
|
+
ref={ref}
|
|
58
|
+
className={cn(toastVariants({ variant }), className)}
|
|
59
|
+
{...props}
|
|
60
|
+
/>
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
Toast.displayName = ToastPrimitives.Root.displayName;
|
|
64
|
+
|
|
65
|
+
const ToastAction = React.forwardRef<
|
|
66
|
+
React.ElementRef<typeof ToastPrimitives.Action>,
|
|
67
|
+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
|
|
68
|
+
>(({ className, ...props }, ref) => (
|
|
69
|
+
<ToastPrimitives.Action
|
|
70
|
+
ref={ref}
|
|
71
|
+
className={cn(
|
|
72
|
+
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
|
|
73
|
+
className,
|
|
74
|
+
)}
|
|
75
|
+
{...props}
|
|
76
|
+
/>
|
|
77
|
+
));
|
|
78
|
+
ToastAction.displayName = ToastPrimitives.Action.displayName;
|
|
79
|
+
|
|
80
|
+
const ToastClose = React.forwardRef<
|
|
81
|
+
React.ElementRef<typeof ToastPrimitives.Close>,
|
|
82
|
+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
|
|
83
|
+
>(({ className, ...props }, ref) => (
|
|
84
|
+
<ToastPrimitives.Close
|
|
85
|
+
ref={ref}
|
|
86
|
+
className={cn(
|
|
87
|
+
"absolute right-1 top-1 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
|
|
88
|
+
className,
|
|
89
|
+
)}
|
|
90
|
+
toast-close=""
|
|
91
|
+
{...props}
|
|
92
|
+
>
|
|
93
|
+
<X className="h-3 w-3" />
|
|
94
|
+
</ToastPrimitives.Close>
|
|
95
|
+
));
|
|
96
|
+
ToastClose.displayName = ToastPrimitives.Close.displayName;
|
|
97
|
+
|
|
98
|
+
const ToastTitle = React.forwardRef<
|
|
99
|
+
React.ElementRef<typeof ToastPrimitives.Title>,
|
|
100
|
+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
|
|
101
|
+
>(({ className, ...props }, ref) => (
|
|
102
|
+
<ToastPrimitives.Title
|
|
103
|
+
ref={ref}
|
|
104
|
+
className={cn("text-sm font-semibold", className)}
|
|
105
|
+
{...props}
|
|
106
|
+
/>
|
|
107
|
+
));
|
|
108
|
+
ToastTitle.displayName = ToastPrimitives.Title.displayName;
|
|
109
|
+
|
|
110
|
+
const ToastDescription = React.forwardRef<
|
|
111
|
+
React.ElementRef<typeof ToastPrimitives.Description>,
|
|
112
|
+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
|
|
113
|
+
>(({ className, ...props }, ref) => (
|
|
114
|
+
<ToastPrimitives.Description
|
|
115
|
+
ref={ref}
|
|
116
|
+
className={cn("text-sm opacity-90", className)}
|
|
117
|
+
{...props}
|
|
118
|
+
/>
|
|
119
|
+
));
|
|
120
|
+
ToastDescription.displayName = ToastPrimitives.Description.displayName;
|
|
121
|
+
|
|
122
|
+
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;
|
|
123
|
+
|
|
124
|
+
type ToastActionElement = React.ReactElement<typeof ToastAction>;
|
|
125
|
+
|
|
126
|
+
export {
|
|
127
|
+
type ToastProps,
|
|
128
|
+
type ToastActionElement,
|
|
129
|
+
ToastProvider,
|
|
130
|
+
ToastViewport,
|
|
131
|
+
Toast,
|
|
132
|
+
ToastTitle,
|
|
133
|
+
ToastDescription,
|
|
134
|
+
ToastClose,
|
|
135
|
+
ToastAction,
|
|
136
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { useToast } from "../../hooks/use-toast";
|
|
2
|
+
import {
|
|
3
|
+
Toast,
|
|
4
|
+
ToastClose,
|
|
5
|
+
ToastDescription,
|
|
6
|
+
ToastProvider,
|
|
7
|
+
ToastTitle,
|
|
8
|
+
ToastViewport,
|
|
9
|
+
} from "./toast";
|
|
10
|
+
|
|
11
|
+
export function Toaster() {
|
|
12
|
+
const { toasts } = useToast();
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<ToastProvider>
|
|
16
|
+
{toasts.map(function ({ id, title, description, action, ...props }) {
|
|
17
|
+
return (
|
|
18
|
+
<Toast key={id} {...props}>
|
|
19
|
+
<div className="grid gap-1">
|
|
20
|
+
{title && <ToastTitle>{title}</ToastTitle>}
|
|
21
|
+
{description && (
|
|
22
|
+
<ToastDescription>{description}</ToastDescription>
|
|
23
|
+
)}
|
|
24
|
+
</div>
|
|
25
|
+
{action}
|
|
26
|
+
<ToastClose />
|
|
27
|
+
</Toast>
|
|
28
|
+
);
|
|
29
|
+
})}
|
|
30
|
+
<ToastViewport />
|
|
31
|
+
</ToastProvider>
|
|
32
|
+
);
|
|
33
|
+
}
|