Skip to content

Commit

Permalink
feat: 마법의 애니메이션 한 자루 🍯
Browse files Browse the repository at this point in the history
  • Loading branch information
bepyan committed Aug 23, 2024
1 parent 2e0b06d commit 9467e34
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 58 deletions.
2 changes: 1 addition & 1 deletion src/app/(main)/dashboard/invitation-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default function InvitationItem({
return (
<Link
href={`/i/${invitation.eventUrl}/edit`}
className="relative inline-flex flex-col overflow-hidden rounded border px-4 pb-5 pt-6 transition hover:scale-[1.02] focus:outline-none active:scale-[0.98]"
className="relative inline-flex h-full w-full flex-col overflow-hidden rounded border bg-background px-4 pb-5 pt-6 transition hover:scale-[1.02] focus:outline-none active:scale-[0.98]"
>
<div className="relative flex aspect-[11/4] w-full items-center justify-center overflow-hidden rounded border bg-muted">
{invitation.thumbnailUrl && (
Expand Down
20 changes: 14 additions & 6 deletions src/app/(main)/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Link from "next/link";
import { redirect } from "next/navigation";
import InvitationItem from "~/app/(main)/dashboard/invitation-item";
import TemplateItem from "~/app/(main)/dashboard/template-item";
import { Stagger, StaggerItem } from "~/components/core/stagger";
import ProfileDropDown from "~/components/profile-dropdown";
import { getAuth } from "~/lib/auth/utils";
import { getInvitationsByAuth } from "~/lib/db/schema/invitations.query";
Expand Down Expand Up @@ -60,11 +61,13 @@ export default async function Page() {
</h2>
</div>
</div>
<div className="mt-9 grid grid-cols-[repeat(1,_minmax(15rem,_1fr))] gap-8 sm:grid-cols-3 md:grid-cols-4 xl:grid-cols-5">
<Stagger className="mt-9 grid grid-cols-[repeat(1,_minmax(15rem,_1fr))] gap-8 sm:grid-cols-3 md:grid-cols-4 xl:grid-cols-5">
{sortedTemplates.map((template) => (
<TemplateItem key={template.id} template={template} />
<StaggerItem key={template.id}>
<TemplateItem template={template} />
</StaggerItem>
))}
</div>
</Stagger>
</div>
{/* 초대장 목록 */}
{!!sortedInvitations.length && (
Expand All @@ -76,11 +79,16 @@ export default async function Page() {
</h2>
</div>
</div>
<div className="mt-9 grid grid-cols-[repeat(1,_minmax(15rem,_1fr))] gap-8 sm:grid-cols-3 md:grid-cols-4 xl:grid-cols-5">
<Stagger
className="mt-9 grid grid-cols-[repeat(1,_minmax(15rem,_1fr))] gap-8 sm:grid-cols-3 md:grid-cols-4 xl:grid-cols-5"
delay={0.1}
>
{sortedInvitations.map((invitation) => (
<InvitationItem key={invitation.id} invitation={invitation} />
<StaggerItem key={invitation.id}>
<InvitationItem invitation={invitation} />
</StaggerItem>
))}
</div>
</Stagger>
</div>
)}
</main>
Expand Down
36 changes: 18 additions & 18 deletions src/app/(main)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import MainImage2 from "~/assets/main2.png";
import MainImage3 from "~/assets/main3.png";
import MainImage4 from "~/assets/main4.png";
import MainPiece from "~/assets/main_piece.png";
import { InView } from "~/components/core/in-view";
import { Badge } from "~/components/ui/badge";
import { Button } from "~/components/ui/button";

export default function Home() {
return (
<div className="mx-auto flex min-h-screen max-w-7xl flex-col px-7">
<div className="mx-auto flex min-h-screen max-w-xl flex-col px-7 pb-14">
<header className="pt-[58px]">
<Image
src="/landing-logo.png"
Expand All @@ -29,18 +30,18 @@ export default function Home() {
</h3>
</section>
</header>
<div className="mt-[156px] flex justify-center">
<InView className="mt-[156px] flex justify-center">
<Image src={MainImage} alt="main" width={185} height={269} />
</div>
<section className="my-[152px] mt-20 flex justify-center">
</InView>
<InView className="my-[152px] mt-20 flex justify-center">
<Image src={MainPiece} alt="main" width={20} height={24} />
</section>
</InView>
<ul className="flex flex-col gap-y-[180px] pb-[100px]">
<section>
<InView>
<Badge className="h-[38px] rounded-[10px] bg-[#424242] px-4 text-sm leading-none">
최적화 템플릿 사용
</Badge>
<div className="my-6 flex justify-center py-9">
<div className="my-6 flex justify-start py-9">
<Image src={MainImage2} alt="main" width={185} height={135} />
</div>
<article className="flex flex-col gap-y-3">
Expand All @@ -51,24 +52,24 @@ export default function Home() {
{`상황에 따라 제공되는 다양한 템플릿을\n사용해 멋진 초대장을 빠르게 만들 수 있어요`}
</p>
</article>
</section>
<section>
<Badge className="h-[38px] rounded-[10px] bg-[#424242] px-4 text-sm leading-none">
</InView>
<InView className="flex flex-col">
<Badge className="ml-auto h-[38px] rounded-[10px] bg-[#424242] px-4 text-sm leading-none">
자유로운 커스터마이징
</Badge>
<div className="my-6 flex justify-center py-9">
<div className="my-6 flex justify-end py-9">
<Image src={MainImage3} alt="main" width={240} height={120} />
</div>
<article className="flex flex-col gap-y-3">
<article className="ml-auto flex flex-col gap-y-3 text-right">
<p className="whitespace-pre-wrap text-3xl font-semibold leading-[40px] -tracking-[0.5px] text-[#222]">
{`원하는대로 커스텀해\n나만의 초대장 만들기`}
</p>
<p className="whitespace-pre text-base leading-6 -tracking-[0.2px] text-[#333]">
{`높은 커스터마이징 자유도로 세상에\n하나뿐인 나만의 초대장을 만들 수 있어요`}
</p>
</article>
</section>
<section>
</InView>
<InView>
<Badge className="h-[38px] rounded-[10px] bg-[#424242] px-4 text-sm leading-none">
초대장 특화 기능
</Badge>
Expand All @@ -89,17 +90,16 @@ export default function Home() {
{`쉬운 SNS 공유, 지도 연동, 참석여부 조사 등\n초대장 특화 기능으로 참석자를 초대할 수 있어요`}
</p>
</article>
</section>
</InView>
</ul>

<footer className="flex justify-center px-4 py-8">
<div className="fixed inset-x-0 bottom-4 mx-auto flex max-w-xl justify-center px-4 py-8">
<Button
asChild
className="h-[60px] w-full rounded-[12px] bg-[#2B2D36] text-lg font-bold"
>
<Link href="/dashboard">무료로 시작하기</Link>
</Button>
</footer>
</div>
</div>
);
}
84 changes: 53 additions & 31 deletions src/app/(main)/sign-in/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import Bee from "~/assets/bee.png";
import GoogleLogo from "~/assets/google-logo.png";
import Mail from "~/assets/mail.png";
import Star from "~/assets/star.png";
import { InView } from "~/components/core/in-view";
import { TextEffect } from "~/components/core/text-effect";
import { Button } from "~/components/ui/button";
import { getAuth } from "~/lib/auth/utils";

Expand All @@ -17,46 +19,40 @@ export default async function Page() {

return (
<>
<header className="flex items-center pl-10 pt-8">
<header className="fixed left-10 top-8 z-10 flex items-center">
<Link href="/">
<Image src="/landing-logo.png" alt="logo" width={85} height={29} />
</Link>
</header>
<main className="mx-auto flex min-h-screen w-fit flex-col items-center justify-center">
<main className="mx-auto flex min-h-screen w-fit flex-col items-center justify-center px-4">
<div className="relative">
<Image
src={Bee}
alt="bee"
width={125}
height={111}
className="absolute -right-[30px] -top-[90px]"
/>
<Image
src={Star}
alt="star"
width={63}
height={56}
className="absolute -left-[80px] top-[100px]"
/>
<Image
src={Mail}
alt="mail"
width={100}
height={100}
className="absolute -right-[100px] top-[270px]"
/>
<InView className="absolute -right-[0px] -top-[90px] sm:-right-[30px]">
<Image src={Bee} alt="bee" width={125} height={111} />
</InView>
<InView
className="absolute -left-[80px] top-[100px] hidden sm:block"
transition={{ delay: 0.2 }}
>
<Image src={Star} alt="star" width={63} height={56} />
</InView>
<InView
className="absolute -right-[100px] top-[270px] hidden sm:block"
transition={{ delay: 0.4 }}
>
<Image src={Mail} alt="mail" width={100} height={100} />
</InView>
<section className="mb-[56px] flex flex-col items-center gap-y-6">
<h2 className="text-[2.5em] font-semibold -tracking-[0.2px]">
당신의 환대,
<span className="font-[950]"> INVI</span>
</h2>
<p className="text-xl -tracking-[0.2px] text-[#333333]">
<TextEffect className="text-xl -tracking-[0.2px] text-[#333333]">
인비와 함께 나만의 초대장을 만들어보세요
</p>
</TextEffect>
</section>
<section className="flex min-w-[417px] flex-col items-center gap-y-7">
<Button variant="outline" className="h-16 w-full" asChild>
<Link
<a
href="/sign-in/google"
className="flex gap-x-2 rounded-md px-6 py-4"
>
Expand All @@ -68,16 +64,42 @@ export default async function Page() {
className="rounded-full"
/>
<p className="text-xl">Google 로그인</p>
</Link>
</a>
</Button>
<span className="flex gap-x-2 text-gray-800">
<p className="text-gray-800">아직 계정이 없으신가요?</p>
<Link
<TextEffect
className="text-gray-800"
variants={{
container: {
visible: {
transition: {
staggerChildren: 0.05,
delayChildren: 0.25,
},
},
} as any,
}}
>
아직 계정이 없으신가요?
</TextEffect>
<a
href="/sign-in/google"
className="font-semibold text-gray-950 underline underline-offset-2"
>
가입하기
</Link>
<TextEffect
variants={{
container: {
visible: {
transition: {
delayChildren: 0.6,
},
},
} as any,
}}
>
가입하기
</TextEffect>
</a>
</span>
</section>
</div>
Expand Down
52 changes: 52 additions & 0 deletions src/components/core/stagger.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"use client";

import { motion, type MotionProps } from "framer-motion";

const container = {
hidden: { opacity: 0 },
show: {
opacity: 1,
transition: {
staggerChildren: 0.05,
},
},
};

const item = {
hidden: { opacity: 0.3 },
show: { opacity: 1 },
};

export function Stagger({
children,
delay,
...props
}: MotionProps & React.ComponentProps<"ul"> & { delay?: number }) {
const variants = {
...container,
show: {
...container.show,
transition: {
...container.show.transition,
delayChildren: delay,
},
},
};

return (
<motion.ul {...props} variants={variants} initial="hidden" animate="show">
{children}
</motion.ul>
);
}

export function StaggerItem({
children,
...props
}: MotionProps & React.ComponentProps<"li">) {
return (
<motion.li {...props} variants={item}>
{children}
</motion.li>
);
}
6 changes: 4 additions & 2 deletions src/components/core/text-effect.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"use client";

import { motion, type Variants } from "framer-motion";
import { motion, type MotionProps, type Variants } from "framer-motion";
import React from "react";

type PresetType = "blur" | "shake" | "scale" | "fade" | "slide";

type TextEffectProps = {
type TextEffectProps = MotionProps & {
children: string;
per?: "word" | "char";
as?: keyof JSX.IntrinsicElements;
Expand Down Expand Up @@ -117,6 +117,7 @@ export function TextEffect({
variants,
className,
preset,
...props
}: TextEffectProps) {
const words = children.split(/(\S+)/);
const MotionTag = motion[as as keyof typeof motion];
Expand All @@ -133,6 +134,7 @@ export function TextEffect({
aria-label={children}
variants={containerVariants}
className={className}
{...props}
>
{words.map((word, wordIndex) => (
<AnimationComponent
Expand Down

0 comments on commit 9467e34

Please sign in to comment.