-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
481 additions
and
1,082 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
"use client" | ||
|
||
import React, { useState, useEffect } from 'react' | ||
import { motion, AnimatePresence } from "framer-motion" | ||
import { ChevronLeft, ChevronRight } from "lucide-react" | ||
import Image from "next/image" | ||
|
||
|
||
interface TemplateProps { | ||
templates: {id: string, name: string, path: string, src: string, dark: string, light: string}[]; | ||
initialTheme?: "light" | "dark"; | ||
onThemeChange: (theme: "light" | "dark") => void; | ||
onTemplateChange: (template: string) => void; | ||
} | ||
|
||
export default function ChooseTemplate({ templates, initialTheme, onThemeChange, onTemplateChange }: TemplateProps) { | ||
const [currentTemplate, setCurrentTemplate] = useState(0); | ||
const [dragStart, setDragStart] = useState(0); | ||
const [currentTheme, setCurrentTheme] = useState<"light" | "dark">(initialTheme || "dark"); | ||
const [direction, setDirection] = useState(1); // 방향 상태 유지 | ||
|
||
useEffect(() => { | ||
onTemplateChange(templates[currentTemplate].id); | ||
}, [currentTemplate]); | ||
|
||
const handleThemeChange = (theme: "light" | "dark") => { | ||
setCurrentTheme(theme) | ||
onThemeChange(theme) | ||
} | ||
|
||
const handleNext = () => { | ||
setDirection(1); // 다음으로 이동 시 방향을 1로 설정 | ||
setCurrentTemplate((prev) => { | ||
const newIndex = (prev + 1) % templates.length; | ||
onTemplateChange(templates[newIndex].id); | ||
return newIndex; | ||
}) | ||
} | ||
|
||
const handlePrev = () => { | ||
setDirection(-1); // 이전으로 이동 시 방향을 -1로 설정 | ||
setCurrentTemplate((prev) => { | ||
const newIndex = (prev - 1 + templates.length) % templates.length; | ||
onTemplateChange(templates[newIndex].id); | ||
return newIndex; | ||
}) | ||
} | ||
|
||
const handleDragStart = (event: React.TouchEvent | React.MouseEvent) => { | ||
const clientX = "touches" in event ? event.touches[0].clientX : event.clientX | ||
setDragStart(clientX) | ||
} | ||
|
||
const handleDragEnd = (event: React.TouchEvent | React.MouseEvent) => { | ||
const clientX = "changedTouches" in event ? event.changedTouches[0].clientX : event.clientX | ||
const diff = dragStart - clientX | ||
|
||
if (Math.abs(diff) > 50) { | ||
if (diff > 0) { | ||
handleNext() | ||
} else { | ||
handlePrev() | ||
} | ||
} | ||
} | ||
|
||
// Variants 정의 | ||
const variants = { | ||
enter: (direction: number) => ({ | ||
x: direction * 300, | ||
opacity: 0 | ||
}), | ||
center: { | ||
x: 0, | ||
opacity: 1 | ||
}, | ||
exit: (direction: number) => ({ | ||
x: -direction * 300, | ||
opacity: 0 | ||
}) | ||
}; | ||
|
||
return ( | ||
<div className="w-full max-w-md mx-auto space-y-8"> | ||
<div className="relative rounded-2xl bg-zinc-900"> | ||
<h1 className="text-2xl font-bold m-2 text-center">원하는 카드를 골라주세요</h1> | ||
<p className="text-sm mb-6 text-gray-400 text-center">{templates[currentTemplate].name}</p> | ||
|
||
<div | ||
className="relative w-full flex justify-center items-center" | ||
onTouchStart={handleDragStart} | ||
onTouchEnd={handleDragEnd} | ||
onMouseDown={handleDragStart} | ||
onMouseUp={handleDragEnd} | ||
> | ||
<AnimatePresence custom={direction} mode="wait"> | ||
<motion.div | ||
key={currentTemplate} | ||
custom={direction} | ||
variants={variants} | ||
initial="enter" | ||
animate="center" | ||
exit="exit" | ||
transition={{ type: "spring", stiffness: 300, damping: 30 }} | ||
className="relative" | ||
> | ||
<Image | ||
src={templates[currentTemplate].src !== '' ? templates[currentTemplate].src : (currentTheme === 'dark' ? templates[currentTemplate].dark : templates[currentTemplate].light)} | ||
alt={templates[currentTemplate].name} | ||
className={`aspect-[1/2] object-cover transition-colors duration-200`} | ||
width={282} | ||
height={568} | ||
/> | ||
</motion.div> | ||
</AnimatePresence> | ||
</div> | ||
<button | ||
type="button" | ||
onClick={handlePrev} | ||
className="absolute left-2 top-1/2 -translate-y-1/2 p-2 rounded-full bg-black/50 text-white hover:bg-black/70 transition-colors" | ||
> | ||
<ChevronLeft className="w-4 h-4" /> | ||
</button> | ||
<button | ||
type="button" | ||
onClick={handleNext} | ||
className="absolute right-2 top-1/2 -translate-y-1/2 p-2 rounded-full bg-black/50 text-white hover:bg-black/70 transition-colors" | ||
> | ||
<ChevronRight className="w-4 h-4" /> | ||
</button> | ||
</div> | ||
|
||
{(templates[currentTemplate].src === '') && | ||
<div className="flex justify-center items-center gap-4"> | ||
<button | ||
type="button" | ||
onClick={() => handleThemeChange("light")} | ||
className={`w-8 h-8 rounded-full border-2 bg-white transition-all ${currentTheme === "light" ? "border-blue-500 scale-110" : "border-transparent" | ||
}`} | ||
aria-label="Light theme" | ||
/> | ||
<button | ||
type="button" | ||
onClick={() => handleThemeChange("dark")} | ||
className={`w-8 h-8 rounded-full border-2 bg-black transition-all ${currentTheme === "dark" ? "border-blue-500 scale-110" : "border-transparent" | ||
}`} | ||
aria-label="Dark theme" | ||
/> | ||
</div>} | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
export interface Insta1Form { | ||
template: string; | ||
thumbnail?: string; | ||
name?: string; | ||
id?: string; | ||
bio?: string; | ||
code: string; | ||
email: string; | ||
theme?: "light" | "dark"; | ||
} | ||
|
||
export interface InstaSpecialForm { | ||
template: string; | ||
thumbnail?: string; | ||
name?: string; | ||
id?: string; | ||
bio?: string; | ||
code: string; | ||
email: string; | ||
} | ||
|
||
export interface Linkedin1Form { | ||
template: string; | ||
thumbnail?: string; | ||
name?: string; | ||
role?: string; | ||
company?: string; | ||
joinDate?: string; | ||
code: string; | ||
email: string; | ||
theme?: "light" | "dark"; | ||
} | ||
|
||
export interface Linkedin2Form { | ||
template: string; | ||
thumbnail?: string; | ||
name?: string; | ||
role?: string; | ||
company?: string; | ||
code: string; | ||
email: string; | ||
theme?: "light" | "dark"; | ||
} | ||
|
||
export type FormDataTypes = Insta1Form | InstaSpecialForm | Linkedin1Form | Linkedin2Form; |
Oops, something went wrong.