Skip to content

Commit

Permalink
feat: 디스커버리 상세 페이지 개발 (#8)
Browse files Browse the repository at this point in the history
* feat: Add routing to detail page

* feat: Update discovery layout

* feat: Add discovery summary widget

* refactor: Delete unused files

* feat: Add tailwind cn code snippet

* feat: Add webcam components in detail page

* feat: Add buttons in header

* feat: Add responsive screen xs size

* feat: Add discovery detail tab

* refactor: Update discovery detail widgets

* feat: Add @radix-ui/react-dialog dependency

* feat: Add vote dialog

* fix: Fix console error
  • Loading branch information
Najeong-Kim authored Aug 17, 2024
1 parent b018b28 commit 223af70
Show file tree
Hide file tree
Showing 26 changed files with 542 additions and 93 deletions.
8 changes: 8 additions & 0 deletions .vscode/vscode.code-snippets
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Add className={cn('')}": {
"scope": "javascript,javascriptreact,typescript,typescriptreact",
"prefix": "tw-cn",
"body": ["className={cn('${1}')}"],
"description": "className={cn('${1}')}",
},
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"lint": "next lint"
},
"dependencies": {
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-popover": "^1.1.1",
"@react-spring/web": "^9.7.4",
"@tanstack/react-query": "^5.51.21",
Expand Down
1 change: 1 addition & 0 deletions src/app/(web)/[resortId]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from '@/pages/discovery-detail/ui/discovery-detail-page';
6 changes: 5 additions & 1 deletion src/app/(web)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
'use client';

import { cn } from '@/shared/lib';

export default function Layout({ children }: { children: React.ReactNode }) {
return (
<div className={cn('flex size-full justify-center bg-main-5 antialiased')}>
<div className={cn('size-full max-w-[670px]')}>{children}</div>
<div className={cn('size-full max-w-[670px] bg-opacity-65 bg-[url("/background.png")]')}>
{children}
</div>
</div>
);
}
5 changes: 5 additions & 0 deletions src/entities/discovery/model/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export const WeeklyWeatherData: WeeklyWeather[] = [

export const DiscoveryData: Discovery[] = [
{
id: 1,
name: '용평스키장 모나',
slope: null,
weather: {
Expand All @@ -79,6 +80,7 @@ export const DiscoveryData: Discovery[] = [
weeklyWeather: WeeklyWeatherData,
},
{
id: 2,
name: '휘닉스파크',
slope: 8,
weather: {
Expand All @@ -89,6 +91,7 @@ export const DiscoveryData: Discovery[] = [
weeklyWeather: WeeklyWeatherData,
},
{
id: 3,
name: '하이원스키장',
slope: 10,
weather: {
Expand All @@ -99,6 +102,7 @@ export const DiscoveryData: Discovery[] = [
weeklyWeather: WeeklyWeatherData,
},
{
id: 4,
name: '비발디파크',
slope: 14,
weather: {
Expand All @@ -109,6 +113,7 @@ export const DiscoveryData: Discovery[] = [
weeklyWeather: WeeklyWeatherData,
},
{
id: 5,
name: '곤지암스키장',
slope: 8,
weather: {
Expand Down
1 change: 1 addition & 0 deletions src/entities/discovery/model/model.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export type WeeklyWeather = {
};

export type Discovery = {
id: number;
name: string;
slope: number | null;
weather: {
Expand Down
22 changes: 22 additions & 0 deletions src/pages/discovery-detail/ui/discovery-detail-page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use client';

import DiscoveryContent from '@/widgets/discovery-detail/ui/discovery-content';
import DiscoverySummary from '@/widgets/discovery-detail/ui/discovery-summary';
import { Header } from '@/widgets/header/ui';
import { DiscoveryData } from '@/entities/discovery';
import { cn } from '@/shared/lib';

const DiscoveryDetailPage = ({ params }: { params: { resortId: number } }) => {
const discovery = DiscoveryData.find((discovery) => discovery.id === +params?.resortId);
if (!discovery) return null;

return (
<div className={cn('size-full')}>
<Header hasBackButton hasShareButton />
<DiscoverySummary {...discovery} />
<DiscoveryContent />
</div>
);
};

export default DiscoveryDetailPage;
8 changes: 3 additions & 5 deletions src/pages/discovery/ui/discovery-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,9 @@ const DiscoveryPageContent = () => {
if (!discoveryData) return null;

return (
<div className={cn('relative size-full bg-opacity-65 bg-[url("/background.png")]')}>
<div className={cn('size-full bg-gradient-to-b from-[#8DA3DD/20] to-transparent')}>
<Header />
<DiscoveryList discoveryData={discoveryData} />
</div>
<div className={cn('size-full bg-gradient-to-b from-[#8DA3DD/20] to-transparent')}>
<Header />
<DiscoveryList discoveryData={discoveryData} />
</div>
);
};
Expand Down
4 changes: 2 additions & 2 deletions src/shared/icons/camera.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ const CameraIcon = () => {
width="10.0996"
height="9.19995"
filterUnits="userSpaceOnUse"
color-interpolation-filters="sRGB"
colorInterpolationFilters="sRGB"
>
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feFlood floodOpacity="0" result="BackgroundImageFix" />
<feGaussianBlur in="BackgroundImageFix" stdDeviation="0.5" />
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_693_2934" />
<feBlend
Expand Down
33 changes: 33 additions & 0 deletions src/shared/icons/chevron-left.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { SVGProps } from 'react';
import React from 'react';

interface ChevronLeftIconProps extends SVGProps<SVGSVGElement> {
className?: string;
}

const ChevronLeftIcon = ({ className, ...args }: ChevronLeftIconProps) => {
return (
<svg
width="26"
height="26"
viewBox="0 0 26 26"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
{...args}
>
<g id="icn_chevron-left">
<path
id="Icon"
d="M16.25 19.5L9.75 13L16.25 6.5"
stroke="#171D23"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</g>
</svg>
);
};

export default ChevronLeftIcon;
30 changes: 30 additions & 0 deletions src/shared/icons/share.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { SVGProps } from 'react';
import React from 'react';

interface ShareIconProps extends SVGProps<SVGSVGElement> {
className?: string;
}

const ShareIcon = ({ className, ...args }: ShareIconProps) => {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
{...args}
>
<g id="icn_share">
<path
id="Vector"
d="M18 16.08C17.24 16.08 16.56 16.38 16.04 16.85L8.91 12.7C8.96 12.47 9 12.24 9 12C9 11.76 8.96 11.53 8.91 11.3L15.96 7.19C16.5 7.69 17.21 8 18 8C19.66 8 21 6.66 21 5C21 3.34 19.66 2 18 2C16.34 2 15 3.34 15 5C15 5.24 15.04 5.47 15.09 5.7L8.04 9.81C7.5 9.31 6.79 9 6 9C4.34 9 3 10.34 3 12C3 13.66 4.34 15 6 15C6.79 15 7.5 14.69 8.04 14.19L15.16 18.35C15.11 18.56 15.08 18.78 15.08 19C15.08 20.61 16.39 21.92 18 21.92C19.61 21.92 20.92 20.61 20.92 19C20.92 17.39 19.61 16.08 18 16.08Z"
fill="#171D23"
/>
</g>
</svg>
);
};

export default ShareIcon;
19 changes: 19 additions & 0 deletions src/shared/ui/card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { cn } from '../lib';

interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
className?: string;
}

const Card = ({ className, children, ...props }: CardProps) => (
<div
className={cn(
'rounded-[15px] border border-white bg-gradient-to-br from-white/90 to-white/60 shadow-[2px_10px_29px_0px_rgba(47,49,65,0.02)] backdrop-blur-[25px]',
className
)}
{...props}
>
{children}
</div>
);

export default Card;
87 changes: 87 additions & 0 deletions src/shared/ui/dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
'use client';

import * as DialogPrimitive from '@radix-ui/react-dialog';
import * as React from 'react';
import { cn } from '../lib';

const Dialog = DialogPrimitive.Root;

const DialogTrigger = DialogPrimitive.Trigger;

const DialogPortal = DialogPrimitive.Portal;

const DialogClose = DialogPrimitive.Close;

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

const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-[412px] translate-x-[-50%] translate-y-[-50%] gap-5 rounded-2xl border bg-background px-10 py-11 antialiased shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]',
className
)}
{...props}
>
{children}
</DialogPrimitive.Content>
</DialogPortal>
));
DialogContent.displayName = DialogPrimitive.Content.displayName;

const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn('flex flex-col gap-6', className)} {...props} />
);
DialogHeader.displayName = 'DialogHeader';

const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn('flex flex-col gap-5', className)} {...props} />
);
DialogFooter.displayName = 'DialogFooter';

const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title ref={ref} className={cn('h3-semibold', className)} {...props} />
));
DialogTitle.displayName = DialogPrimitive.Title.displayName;

const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description ref={ref} className={cn('title3-semibold', className)} {...props} />
));
DialogDescription.displayName = DialogPrimitive.Description.displayName;

export {
Dialog,
DialogPortal,
DialogOverlay,
DialogClose,
DialogTrigger,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
};
23 changes: 23 additions & 0 deletions src/widgets/discovery-detail/model/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export const DiscoverySummaryActionList = [
{
name: 'bus',
title: '셔틀버스',
},
{ name: 'homepage', title: '홈페이지' },
{ name: 'vote', title: '설질 투표' },
] as const;

export const DiscoveryContentTabList = [
{
name: 'webcam',
title: '웹캠 정보',
},
{
name: 'weather',
title: '날씨',
},
{
name: 'slop',
title: '슬로프',
},
] as const;
50 changes: 50 additions & 0 deletions src/widgets/discovery-detail/ui/discovery-content.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { useState } from 'react';
// eslint-disable-next-line boundaries/element-types
import { WebcamMap, WebcamSlopList } from '@/widgets/webcam/ui';
import { JISAN } from '@/entities/slop/model';
import { cn } from '@/shared/lib';
import { DiscoveryContentTabList } from '../model/constants';

const DUMMY2 = JISAN;

const DiscoveryContent = () => {
const [selectedTab, setSelectedTab] = useState('webcam');
const [selectedSlop, setSelectedSlop] = useState<string | null>(null);

return (
<>
<ul className={cn('flex size-full h-[53px] bg-white')}>
{DiscoveryContentTabList.map((tab) => (
<li
key={tab.name}
className={cn(
'title3-semibold flex h-[51px] flex-1 cursor-pointer items-center justify-center text-gray-90',
selectedTab === tab.name ? 'box-content border-b-2 border-gray-90' : 'text-gray-40'
)}
onClick={() => setSelectedTab(tab.name)}
>
{tab.title}
</li>
))}
</ul>
{selectedTab === 'webcam' && (
<>
<WebcamMap slops={DUMMY2.slops} mapSrc={DUMMY2.map} selectedSlop={selectedSlop} />
<WebcamSlopList
className={cn('bg-white')}
list={DUMMY2.slops.map((item) => ({
...item,
isWebcam: !!item.webcam,
}))}
selectedSlop={selectedSlop}
setSelectedSlop={setSelectedSlop}
/>
</>
)}
{selectedTab === 'weather' && <div>날씨</div>}
{selectedTab === 'slop' && <div>슬로프</div>}
</>
);
};

export default DiscoveryContent;
Loading

0 comments on commit 223af70

Please sign in to comment.