Skip to content

Commit

Permalink
사이드바 컴포넌트 작업 (#46)
Browse files Browse the repository at this point in the history
* chore: sheet ui 추가

* feat: sheet ui 위치 속성 변경

* style: 햄버거 메뉴 svg 추가

* feat: 사이드바 ui 구현

* feat: separator ui 추가

* style: 로그아웃 아이콘 추가

* feat: 로그아웃 버튼 추가

* feat: 프로필 클릭 시 마이페이지로 이동

* chore: 윈도우 husky 설정

* style: 모바일 푸터 스타일 수정

* refactor: dom 로직 제거
  • Loading branch information
hyoribogo authored Dec 27, 2023
1 parent fc9f523 commit 2580663
Show file tree
Hide file tree
Showing 13 changed files with 298 additions and 49 deletions.
9 changes: 8 additions & 1 deletion .husky/pre-push
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npm run build
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
npm.cmd run build
;;
*)
npm run build
;;
esac
36 changes: 2 additions & 34 deletions app/(main)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
import { createClient } from '@/utils/supabase/server';
import { cookies } from 'next/headers';
import TypeTimeLogo from '@/assets/images/type-time-logo.png';
import Image from 'next/image';
import Button from '@/components/common/buttons/button';
import {
HydrationBoundary,
QueryClient,
dehydrate,
} from '@tanstack/react-query';
import quizOptions from '@/services/quiz/options';
import QuizTable from './quiz-table';
import KakaoButton from '../auth/kakao-button';
import Header from '@/components/common/headers/header';
import BaseHeader from '@/components/common/headers/base-header';

export default async function Page() {
const queryClient = new QueryClient();
Expand All @@ -25,37 +21,9 @@ export default async function Page() {

await queryClient.prefetchQuery(quizOptions.all(user?.id));

const signOut = async () => {
'use server';

const cookieStore = cookies();
const supabase = createClient(cookieStore);

await supabase.auth.signOut();
};

const HeaderRightArea = user ? (
<form action={signOut}>
<Button className="h-full">로그아웃</Button>
</form>
) : (
<KakaoButton />
);

return (
<HydrationBoundary state={dehydrate(queryClient)}>
<Header
leftArea={
<Image
src={TypeTimeLogo}
alt="타입타임 로고"
width={158}
height={63}
priority
/>
}
rightArea={HeaderRightArea}
/>
<BaseHeader />

<QuizTable userId={user?.id} />

Expand Down
2 changes: 1 addition & 1 deletion app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default function RootLayout({
<PcScreen />

<div id="app-background" />
<div id="app-screen" className="bg-background">
<div id="app-screen" className="relative bg-background">
{/* NOTE: OverlayProvider에 div가 감싸져야 한다. */}
<OverlayProvider>{children}</OverlayProvider>
</div>
Expand Down
3 changes: 3 additions & 0 deletions assets/svgs/hamburger-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions assets/svgs/logout-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions assets/svgs/profile-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions components/common/headers/back-header.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import BackButton from '../buttons/back-button';
import Header from './header';
import Profile from './profile';
import Menu from './menu';

export default function BackHeader() {
return (
Expand All @@ -10,7 +10,7 @@ export default function BackHeader() {
centerArea={
<h1 className="text-xl font-bold text-blue-primary">TypeTime</h1>
}
rightArea={<Profile />}
rightArea={<Menu />}
/>
</>
);
Expand Down
4 changes: 2 additions & 2 deletions components/common/headers/base-header.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Header from './header';
import Image from 'next/image';
import TypeTimeLogo from '@/assets/images/type-time-logo.png';
import Profile from './profile';
import Menu from './menu';

export default function BaseHeader() {
return (
Expand All @@ -15,7 +15,7 @@ export default function BaseHeader() {
priority
/>
}
rightArea={<Profile />}
rightArea={<Menu />}
/>
);
}
80 changes: 80 additions & 0 deletions components/common/headers/menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { createClient } from '@/utils/supabase/server';
import { cookies } from 'next/headers';
import HamburgerIcon from '@/assets/svgs/hamburger-icon.svg';
import LogoutIcon from '@/assets/svgs/logout-icon.svg';
import ProfileIcon from '@/assets/svgs/profile-icon.svg';
import {
Sheet,
SheetContent,
SheetFooter,
SheetTrigger,
} from '@/components/ui/sheet';
import Image from 'next/image';
import Profile from './profile';
import { Separator } from '@/components/ui/separator';
import Link from 'next/link';

export default async function Menu() {
const cookieStore = cookies();
const supabase = createClient(cookieStore);

const {
data: { user },
} = await supabase.auth.getUser();

const signOut = async () => {
'use server';

const cookieStore = cookies();
const supabase = createClient(cookieStore);

await supabase.auth.signOut();
};

return (
<Sheet>
<SheetTrigger asChild className="cursor-pointer">
<Image
src={HamburgerIcon}
className="mr-4"
width={30}
height={30}
alt="메뉴"
/>
</SheetTrigger>
<SheetContent side="right" className="flex flex-col justify-between">
<div className="mt-4">
<Profile />
<Separator className="my-4" />

<Link
href={user ? '/my' : '/auth'}
scroll={false}
className="flex gap-4"
>
<Image src={ProfileIcon} width={24} height={24} alt="프로필" />
마이페이지
</Link>
</div>

<SheetFooter className="flex-row justify-end">
{user && (
<>
<form action={signOut}>
<button className="flex flex-row items-center gap-2 text-blue-primary focus:outline-none">
<Image
src={LogoutIcon}
width={20}
height={20}
alt="로그아웃"
/>
로그아웃
</button>
</form>
</>
)}
</SheetFooter>
</SheetContent>
</Sheet>
);
}
23 changes: 15 additions & 8 deletions components/common/headers/profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,21 @@ export default async function Profile() {
} = await supabase.auth.getUser();

return (
<Link href={user ? `users/${user.id}` : '/auth'} scroll={false}>
<Image
src={UserProfile}
className="mr-4"
width={32}
height={32}
alt="유저 프로필"
/>
<Link href={user ? '/my' : '/auth'} scroll={false}>
<div className="flex items-center">
<Image
src={UserProfile}
className="mr-4"
width={32}
height={32}
alt="유저 프로필"
/>
{user ? (
<p className="text-center font-bold">{user.user_metadata.name}</p>
) : (
<p className="text-center text-gray-400">로그인을 해주세요.</p>
)}
</div>
</Link>
);
}
139 changes: 139 additions & 0 deletions components/ui/sheet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
'use client';

import * as React from 'react';
import * as SheetPrimitive from '@radix-ui/react-dialog';
import { cva, type VariantProps } from 'class-variance-authority';
import { X } from 'lucide-react';
import { cn } from '@/libs/utils';

const Sheet = SheetPrimitive.Root;

const SheetTrigger = SheetPrimitive.Trigger;

const SheetClose = SheetPrimitive.Close;

const SheetPortal = SheetPrimitive.Portal;

const SheetOverlay = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Overlay
className={cn(
'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
className
)}
{...props}
ref={ref}
/>
));
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;

const sheetVariants = cva(
'fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out',
{
variants: {
side: {
top: 'inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
bottom:
'inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
left: 'inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm',
right:
'inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm',
},
},
defaultVariants: {
side: 'right',
},
}
);

interface SheetContentProps
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
VariantProps<typeof sheetVariants> {}

const SheetContent = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Content>,
SheetContentProps
>(({ side = 'right', className, children, ...props }, ref) => (
<SheetPortal>
<SheetOverlay />
<SheetPrimitive.Content
ref={ref}
className={cn(sheetVariants({ side }), className)}
{...props}
>
{children}
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none data-[state=open]:bg-secondary">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
</SheetPrimitive.Content>
</SheetPortal>
));
SheetContent.displayName = SheetPrimitive.Content.displayName;

const SheetHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
'flex flex-col space-y-2 text-center sm:text-left',
className
)}
{...props}
/>
);
SheetHeader.displayName = 'SheetHeader';

const SheetFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
className
)}
{...props}
/>
);
SheetFooter.displayName = 'SheetFooter';

const SheetTitle = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Title
ref={ref}
className={cn('text-lg font-semibold text-foreground', className)}
{...props}
/>
));
SheetTitle.displayName = SheetPrimitive.Title.displayName;

const SheetDescription = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Description
ref={ref}
className={cn('text-sm text-muted-foreground', className)}
{...props}
/>
));
SheetDescription.displayName = SheetPrimitive.Description.displayName;

export {
Sheet,
SheetPortal,
SheetOverlay,
SheetTrigger,
SheetClose,
SheetContent,
SheetHeader,
SheetFooter,
SheetTitle,
SheetDescription,
};
Loading

0 comments on commit 2580663

Please sign in to comment.