Skip to content

Commit

Permalink
πŸ’„ style: ν”„λ‘œμ νŠΈ λ””ν…ŒμΌ λ ˆμ΄μ•„μ›ƒ κ°œμ„ 
Browse files Browse the repository at this point in the history
  • Loading branch information
gr22nist committed Nov 20, 2024
1 parent 18cea5d commit 59891ea
Show file tree
Hide file tree
Showing 9 changed files with 213 additions and 80 deletions.
29 changes: 20 additions & 9 deletions components/FloatingContact.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { usePathname } from 'next/navigation'
import { useState } from "react"
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "./ui/dialog"
import { Mail } from "lucide-react"
import { Mail, MessageCircle } from "lucide-react"
import { motion } from "framer-motion"
import { Button } from "./ui/button"

Expand Down Expand Up @@ -41,18 +41,29 @@ export function FloatingContact() {
</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<Button asChild variant="outline" className="w-full">
<a href="mailto:[email protected]">
<Button
asChild
variant="outline"
className="w-full hover:bg-foreground/5 hover:border-foreground/20 transition-colors"
>
<a href="mailto:[email protected]" className="flex items-center gap-2">
<Mail className="h-4 w-4" />
이메일 보내기
</a>
</Button>
<Button asChild variant="outline" className="w-full">
<a href="https://open.kakao.com/o/sHj0RZZg" target="_blank" rel="noopener noreferrer">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="22" viewBox="0 0 24 22" fill="currentColor">
<path fillRule="evenodd" clipRule="evenodd" d="M0 9.54732C0 4.04892 5.64534 0 11.9753 0C18.3604 0 24 4.0534 24 9.64417C24 15.1426 18.3547 19.1915 12.0247 19.1915C11.4399 19.1915 10.8844 19.1593 10.3348 19.0947L6.01939 21.9516H5.81928C5.81852 21.9518 5.81776 21.952 5.81699 21.9522C5.78749 21.9601 5.74415 21.9708 5.69231 21.98C5.64319 21.9886 5.56367 22 5.46643 22C5.15978 22 4.9202 21.8848 4.84707 21.8489C4.73768 21.7951 4.58425 21.7047 4.44266 21.5657C4.26165 21.3879 4.11724 21.1606 4.03876 20.9037C3.97365 20.6906 3.92955 20.3658 4.05365 20.0209L4.82498 17.2436C1.98989 15.5429 0 12.7982 0 9.54732ZM11.9753 2.34043C6.37235 2.34043 2.35294 5.84543 2.35294 9.54732C2.35294 11.9834 3.99892 14.2547 6.73366 15.6195L7.59739 16.0505L6.90152 18.5562L9.77914 16.6511L10.2289 16.7191C10.8099 16.8068 11.3919 16.8511 12.0247 16.8511C17.6276 16.8511 21.6471 13.3461 21.6471 9.64417C21.6471 5.84096 17.6219 2.34043 11.9753 2.34043Z" />
</svg>
μ˜€ν”ˆ μ±„νŒ… μ°Έμ—¬ν•˜κΈ°
<Button
asChild
variant="outline"
className="w-full hover:bg-foreground/5 hover:border-foreground/20 transition-colors"
>
<a
href="https://open.kakao.com/o/sLPR7Qaf"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2"
>
<MessageCircle className="h-4 w-4" />
μ˜€ν”ˆμ±„νŒ… μ°Έμ—¬ν•˜κΈ°
</a>
</Button>
</div>
Expand Down
124 changes: 124 additions & 0 deletions components/project/AccordionSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
"use client"

import { useState } from "react"
import { ChevronDown, ChevronUp } from "lucide-react"
import { Section } from "./Section"
import { AccordionCard } from "./AccordionCard"
import { Button } from "../ui/button"
import { Badge } from "../ui/badge"

interface Section {
title: string;
content: string | string[];
}

interface AccordionSectionProps {
title: string;
items: {
title: string;
tags?: string[];
relatedTech?: string[];
sections: Section[];
}[];
action?: React.ReactNode;
}

export function AccordionSection({ title, items, action }: AccordionSectionProps) {
const [openIndexes, setOpenIndexes] = useState<number[]>([])
const allOpen = openIndexes.length === items.length

const toggleAll = () => {
if (allOpen) {
setOpenIndexes([])
} else {
setOpenIndexes(items.map((_, index) => index))
}
}

const toggleItem = (index: number) => {
setOpenIndexes(prev =>
prev.includes(index)
? prev.filter(i => i !== index)
: [...prev, index]
)
}

const renderContent = (content: string | string[]) => {
if (Array.isArray(content)) {
return (
<ul className="list-disc list-inside text-muted-foreground space-y-1">
{content.map((item, i) => (
<li key={i}>{item}</li>
))}
</ul>
)
}
return <p className="text-muted-foreground">{content}</p>
}

return (
<Section
title={title}
action={
action || (
<Button
variant="ghost"
size="sm"
onClick={toggleAll}
className="flex items-center gap-1"
>
{allOpen ? (
<>
<ChevronUp className="h-4 w-4" />
λͺ¨λ‘ μ ‘κΈ°
</>
) : (
<>
<ChevronDown className="h-4 w-4" />
λͺ¨λ‘ 펼치기
</>
)}
</Button>
)
}
>
<div className="grid gap-4">
{items.map((item, index) => (
<AccordionCard
key={index}
title={item.title}
tags={item.tags?.map(tag => (
<Badge key={tag} variant="secondary">{tag}</Badge>
))}
isOpen={openIndexes.includes(index)}
onToggle={() => toggleItem(index)}
>
<div className="space-y-6">
{/* μ„Ήμ…˜ λ‚΄μš© */}
<div className="grid gap-4">
{item.sections.map((section, i) => (
<div key={i} className="space-y-2">
<h4 className="font-medium">{section.title}</h4>
{renderContent(section.content)}
</div>
))}
</div>

{/* κ΄€λ ¨ 기술 νƒœκ·Έ */}
{item.relatedTech && item.relatedTech.length > 0 && (
<div className="pt-2 border-t">
<h4 className="font-medium mb-2">κ΄€λ ¨ 기술</h4>
<div className="flex flex-wrap gap-2">
{item.relatedTech.map((tech) => (
<Badge key={tech} variant="outline">{tech}</Badge>
))}
</div>
</div>
)}
</div>
</AccordionCard>
))}
</div>
</Section>
)
}
4 changes: 3 additions & 1 deletion components/project/Challenges.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ export function Challenges({ challenges }: ChallengesProps) {
key={index}
title={challenge.title}
tags={challenge.tags?.map(tag => (
<Badge key={tag} variant="secondary">{tag}</Badge>
<Badge key={tag} variant="challenge">
{tag}
</Badge>
))}
isOpen={openIndexes.includes(index)}
onToggle={() => toggleItem(index)}
Expand Down
50 changes: 30 additions & 20 deletions components/project/Features.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client"

import { Section } from "./Section"
import { Badge } from "../ui/badge"

interface Feature {
title: string
Expand All @@ -15,28 +16,37 @@ interface FeaturesProps {
export function Features({ features }: FeaturesProps) {
return (
<Section title="μ£Όμš” κΈ°λŠ₯">
<div className="space-y-8">
<div className="grid gap-6">
{features.map((feature, index) => (
<div key={index} className="space-y-4">
<h3 className="text-xl font-semibold">{feature.title}</h3>
<div className="text-muted-foreground">
{Array.isArray(feature.description) ? (
feature.description.map((desc, i) => (
<p key={i} className="mt-1">{desc}</p>
))
) : (
<p>{feature.description}</p>
)}
</div>
{feature.tags && feature.tags.length > 0 && (
<div className="flex flex-wrap gap-2">
{feature.tags.map((tag, i) => (
<span key={i} className="text-sm text-muted-foreground">
#{tag}
</span>
))}
<div
key={index}
className="p-5 rounded-lg bg-gradient-to-br from-foreground/5 to-foreground/10 hover:from-foreground/10 hover:to-foreground/15 transition-all"
>
<div className="flex flex-col gap-3">
<div className="flex items-center justify-between">
<h3 className="text-xl font-semibold">{feature.title}</h3>
{feature.tags && (
<div className="flex gap-2">
{feature.tags.map((tag, i) => (
<Badge key={i} variant="feature">
{tag}
</Badge>
))}
</div>
)}
</div>
<div className="text-muted-foreground">
{Array.isArray(feature.description) ? (
<ul className="space-y-2 list-disc list-inside">
{feature.description.map((desc, i) => (
<li key={i}>{desc}</li>
))}
</ul>
) : (
<p>{feature.description}</p>
)}
</div>
)}
</div>
</div>
))}
</div>
Expand Down
8 changes: 4 additions & 4 deletions components/project/ProjectDetailClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ export function ProjectDetailClient({ project }: ProjectDetailClientProps) {
className="space-y-12"
>
{/* 1. ν”„λ‘œμ νŠΈ μ†Œκ°œ */}
<div className="space-y-4">
<h1 className="text-4xl font-bold text-center">{project.title}</h1>
<div className="text-lg text-muted-foreground text-center max-w-2xl mx-auto">
<div className="space-y-4 px-4 sm:px-6">
<h1 className="text-3xl sm:text-4xl font-bold text-center">{project.title}</h1>
<div className="text-base sm:text-lg text-muted-foreground text-center mx-auto max-w-[90%] sm:max-w-2xl">
{Array.isArray(project.description) ? (
project.description.map((desc, index) => (
<p key={index}>{desc}</p>
Expand Down Expand Up @@ -82,7 +82,7 @@ export function ProjectDetailClient({ project }: ProjectDetailClientProps) {
<div className="grid lg:grid-cols-2 gap-6">
{/* μ™Όμͺ½: ν”„λ‘œμ νŠΈ κ°œμš” */}
<div className="order-first">
<div className="lg:sticky lg:top-20">
<div className="lg:sticky lg:top-20 max-h-[calc(100vh-6rem)] overflow-y-auto">
<Suspense fallback={<ProjectSkeleton />}>
<div className="p-6 rounded-lg border bg-card/80 backdrop-blur-sm">
<ProjectOverview
Expand Down
10 changes: 8 additions & 2 deletions components/project/ProjectHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,16 @@ export function ProjectHeader() {
<header className="fixed top-0 left-0 right-0 h-16 z-40 bg-background/70 backdrop-blur-sm">
<div className="max-w-screen-xl mx-auto px-4 sm:px-6 lg:px-8 h-full flex items-center justify-between">
<div className="flex items-center gap-4">
<Button variant="ghost" size="sm" asChild className="gap-2">
<Button
variant="ghost"
size="sm"
asChild
className="gap-1 sm:gap-2 px-2 sm:px-4"
>
<Link href="/main/#projects">
<ArrowLeft className="h-4 w-4" />
λŒμ•„κ°€κΈ°
<span className="hidden sm:inline">λŒμ•„κ°€κΈ°</span>
<span className="sm:hidden">λ’€λ‘œ</span>
</Link>
</Button>
</div>
Expand Down
32 changes: 5 additions & 27 deletions components/project/ProjectOverview.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
import { Badge } from "../ui/badge"
import type { BadgeProps } from "../ui/badge"
import { Button } from "../ui/button"
import { ExternalLink, Github } from "lucide-react"
import { Section } from "./Section"
import { ProjectStatusType, ProjectOverviewProps } from "@/types/project"

type BadgeVariant = NonNullable<BadgeProps["variant"]>
import { ProjectOverviewProps } from "@/types/project"

export function ProjectOverview({ overview, tech, role, links, status, period }: ProjectOverviewProps) {
return (
<>
<Section title="ν”„λ‘œμ νŠΈ 정보">
<div className="flex flex-col gap-8">
<div className="flex flex-wrap items-center gap-2">
<Badge variant={getStatusVariant(status.main)}>
<Badge variant="status">
{status.main}
</Badge>
{status.additional?.map((additionalStatus) => (
<Badge
key={additionalStatus}
variant={getStatusVariant(additionalStatus)}
variant="status"
>
{additionalStatus}
</Badge>
Expand All @@ -39,7 +36,7 @@ export function ProjectOverview({ overview, tech, role, links, status, period }:
<h4 className="text-lg font-semibold">μ‚¬μš© 기술</h4>
<div className="flex flex-wrap gap-2">
{tech.map((item) => (
<Badge key={item} variant="outline">
<Badge key={item} variant="tech">
{item}
</Badge>
))}
Expand All @@ -51,7 +48,7 @@ export function ProjectOverview({ overview, tech, role, links, status, period }:
<div className="space-y-3">
<div className="flex flex-col sm:flex-row items-start gap-2">
<span className="font-medium">{role.main}</span>
<Badge>μ°Έμ—¬μœ¨ {role.participation}%</Badge>
<Badge variant="status">μ°Έμ—¬μœ¨ {role.participation}%</Badge>
</div>
<ul className="list-disc list-inside text-muted-foreground space-y-2 ml-2">
{role.tasks.map((task, index) => (
Expand Down Expand Up @@ -81,23 +78,4 @@ export function ProjectOverview({ overview, tech, role, links, status, period }:
</Section>
</>
)
}

function getStatusVariant(status: ProjectStatusType): BadgeVariant {
switch (status) {
case '운영 쀑':
return 'success'
case '버전 1.0':
return 'outline'
case '개발 쀑':
return 'default'
case 'MVP':
return 'warning'
case '리뉴얼 μ˜ˆμ •':
return 'info'
case 'μœ μ§€λ³΄μˆ˜ 쀑':
return 'secondary'
default:
return 'outline'
}
}
14 changes: 7 additions & 7 deletions components/project/TechStacks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ export function TechStacks({ stacks }: TechStacksProps) {
<Section title="기술 μ„ μ • 이유">
<div className="grid gap-4">
{stacks.map((stack, index) => (
<div
key={index}
className="p-4 rounded-lg bg-foreground/5"
>
<div className="flex flex-wrap items-center gap-3 mb-3">
<h3 className="text-lg font-semibold">{stack.name}</h3>
<Badge variant="secondary">
<div key={index} className="p-3 sm:p-4 rounded-lg bg-foreground/5">
<div className="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-3 mb-3">
<h3 className="text-base sm:text-lg font-semibold">{stack.name}</h3>
<Badge
variant="tech"
className="w-fit"
>
{stack.reason}
</Badge>
</div>
Expand Down
Loading

0 comments on commit 59891ea

Please sign in to comment.