Skip to content

Commit 8bc71ab

Browse files
committed
feat: add service introduction menu
1 parent 1da73c9 commit 8bc71ab

File tree

9 files changed

+399
-3
lines changed

9 files changed

+399
-3
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
},
1111
"dependencies": {
1212
"@radix-ui/react-dialog": "^1.1.1",
13+
"@radix-ui/react-dropdown-menu": "^2.1.4",
1314
"@radix-ui/react-popover": "^1.1.1",
1415
"@react-spring/web": "^9.7.4",
1516
"@tanstack/react-query": "^5.51.21",
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
'use client';
2+
3+
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
4+
import React from 'react';
5+
import { cn } from '@/shared/lib';
6+
7+
const DropdownMenu = DropdownMenuPrimitive.Root;
8+
9+
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
10+
11+
const DropdownMenuContent = React.forwardRef<
12+
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
13+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
14+
>(({ className, sideOffset = 4, ...props }, ref) => (
15+
<DropdownMenuPrimitive.Portal>
16+
<DropdownMenuPrimitive.Content
17+
ref={ref}
18+
sideOffset={sideOffset}
19+
className={cn(
20+
'z-50 min-w-[8rem] overflow-hidden rounded-[10px] border bg-popover p-1 text-popover-foreground shadow-md',
21+
'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-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
22+
className
23+
)}
24+
{...props}
25+
/>
26+
</DropdownMenuPrimitive.Portal>
27+
));
28+
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
29+
30+
const DropdownMenuItem = React.forwardRef<
31+
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
32+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
33+
inset?: boolean;
34+
}
35+
>(({ className, inset, ...props }, ref) => (
36+
<DropdownMenuPrimitive.Item
37+
ref={ref}
38+
className={cn(
39+
'relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0',
40+
inset && 'pl-8',
41+
className
42+
)}
43+
{...props}
44+
/>
45+
));
46+
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
47+
48+
export { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem };

src/shared/icons/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,6 @@ export { default as SunIcon } from './weather/sun';
1212
export { default as SnowRainIcon } from './weather/snow-rain';
1313
export { default as FogIcon } from './weather/fog';
1414
export { default as VoteIcon } from './vote';
15+
export { default as MenuIcon } from './menu';
16+
export { default as MessageIcon } from './message';
17+
export { default as MailIcon } from './mail';

src/shared/icons/mail.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import type { SVGProps } from 'react';
2+
3+
interface MailIconProps extends SVGProps<SVGSVGElement> {
4+
className?: string;
5+
}
6+
7+
const MailIcon = ({ className, ...props }: MailIconProps) => {
8+
return (
9+
<svg
10+
width="15"
11+
height="17"
12+
viewBox="0 0 15 17"
13+
fill="none"
14+
xmlns="http://www.w3.org/2000/svg"
15+
className={className}
16+
{...props}
17+
>
18+
<path
19+
fillRule="evenodd"
20+
clipRule="evenodd"
21+
d="M1.25 5.99572C1.25 4.61198 2.36929 3.49023 3.75 3.49023H11.25C12.6307 3.49023 13.75 4.61198 13.75 5.99572V11.0067C13.75 12.3904 12.6307 13.5122 11.25 13.5122H3.75C2.36929 13.5122 1.25 12.3904 1.25 11.0067V5.99572ZM3.75 4.74298C3.05964 4.74298 2.5 5.30385 2.5 5.99572V11.0067C2.5 11.6986 3.05964 12.2594 3.75 12.2594H11.25C11.9404 12.2594 12.5 11.6986 12.5 11.0067V5.99572C12.5 5.30385 11.9404 4.74298 11.25 4.74298H3.75Z"
22+
fill="currentColor"
23+
/>
24+
<path
25+
fillRule="evenodd"
26+
clipRule="evenodd"
27+
d="M3.88698 6.2312C4.10261 5.96107 4.49592 5.91727 4.76546 6.13337L7.50002 8.32582L10.2346 6.13337C10.5041 5.91727 10.8974 5.96107 11.1131 6.2312C11.3287 6.50133 11.285 6.8955 11.0155 7.1116L7.50002 9.93012L3.98459 7.1116C3.71505 6.8955 3.67135 6.50133 3.88698 6.2312Z"
28+
fill="currentColor"
29+
/>
30+
</svg>
31+
);
32+
};
33+
34+
export default MailIcon;

src/shared/icons/menu.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import type { SVGProps } from 'react';
2+
3+
interface MenuIconProps extends SVGProps<SVGSVGElement> {
4+
className?: string;
5+
}
6+
7+
const MenuIcon = ({ className, ...props }: MenuIconProps) => {
8+
return (
9+
<svg
10+
width="32"
11+
height="32"
12+
viewBox="0 0 32 32"
13+
fill="none"
14+
xmlns="http://www.w3.org/2000/svg"
15+
className={className}
16+
{...props}
17+
>
18+
<circle cx="16" cy="16" r="2" fill="currentColor" />
19+
<circle cx="8" cy="16" r="2" fill="currentColor" />
20+
<circle cx="24" cy="16" r="2" fill="currentColor" />
21+
</svg>
22+
);
23+
};
24+
25+
export default MenuIcon;

src/shared/icons/message.tsx

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import type { SVGProps } from 'react';
2+
3+
interface MessageIconProps extends SVGProps<SVGSVGElement> {
4+
className?: string;
5+
}
6+
7+
const MessageIcon = ({ className, ...props }: MessageIconProps) => {
8+
return (
9+
<svg
10+
width="15"
11+
height="17"
12+
viewBox="0 0 15 17"
13+
fill="none"
14+
xmlns="http://www.w3.org/2000/svg"
15+
className={className}
16+
{...props}
17+
>
18+
<path
19+
fillRule="evenodd"
20+
clipRule="evenodd"
21+
d="M7.5 3.49102C4.73858 3.49102 2.5 5.73451 2.5 8.50199C2.5 9.56189 2.82765 10.5432 3.38698 11.3522C3.57208 11.6199 3.61671 11.9773 3.47008 12.2935L2.90456 13.513H7.5C10.2614 13.513 12.5 11.2695 12.5 8.50199C12.5 5.73451 10.2614 3.49102 7.5 3.49102ZM1.25 8.50199C1.25 5.04264 4.04822 2.23828 7.5 2.23828C10.9518 2.23828 13.75 5.04264 13.75 8.50199C13.75 11.9613 10.9518 14.7657 7.5 14.7657H2.41349C1.7046 14.7657 1.25616 14.0099 1.58378 13.3887L2.2636 11.9228C1.62263 10.9391 1.25 9.7633 1.25 8.50199Z"
22+
fill="currentColor"
23+
/>
24+
<path
25+
fillRule="evenodd"
26+
clipRule="evenodd"
27+
d="M5 9.31606C5.44873 9.31606 5.8125 8.9515 5.8125 8.50178C5.8125 8.05207 5.44873 7.6875 5 7.6875C4.55127 7.6875 4.1875 8.05207 4.1875 8.50178C4.1875 8.9515 4.55127 9.31606 5 9.31606Z"
28+
fill="currentColor"
29+
/>
30+
<path
31+
fillRule="evenodd"
32+
clipRule="evenodd"
33+
d="M10 9.31606C10.4487 9.31606 10.8125 8.9515 10.8125 8.50178C10.8125 8.05207 10.4487 7.6875 10 7.6875C9.55127 7.6875 9.1875 8.05207 9.1875 8.50178C9.1875 8.9515 9.55127 9.31606 10 9.31606Z"
34+
fill="currentColor"
35+
/>
36+
<path
37+
fillRule="evenodd"
38+
clipRule="evenodd"
39+
d="M7.5 9.31606C7.94873 9.31606 8.3125 8.9515 8.3125 8.50178C8.3125 8.05207 7.94873 7.6875 7.5 7.6875C7.05127 7.6875 6.6875 8.05207 6.6875 8.50178C6.6875 8.9515 7.05127 9.31606 7.5 9.31606Z"
40+
fill="currentColor"
41+
/>
42+
</svg>
43+
);
44+
};
45+
46+
export default MessageIcon;

src/views/resort-list/ui/resort-list-page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const ResortListPage = () => {
2121

2222
return (
2323
<div className={cn('size-full bg-gradient-to-b from-[rgba(141,163,221,0.2)] to-transparent')}>
24-
<Header />
24+
<Header hasMenuButton />
2525
<ResortList resorts={resorts} />
2626
</div>
2727
);

src/widgets/header/ui/header.tsx

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,32 @@
11
import Image from 'next/image';
2+
import Link from 'next/link';
23
import { useRouter } from 'next/navigation';
34
import logo from '@public/assets/logo.svg';
45
import ShareDialog from '@/features/resort-detail/ui/share-dialog';
5-
import { ChevronLeftIcon, ShareIcon } from '@/shared/icons';
6+
import {
7+
DropdownMenu,
8+
DropdownMenuContent,
9+
DropdownMenuItem,
10+
DropdownMenuTrigger,
11+
} from '@/features/resort-detail/ui/share-dropdown';
12+
import { ChevronLeftIcon, MailIcon, MenuIcon, MessageIcon, ShareIcon } from '@/shared/icons';
613
import { cn } from '@/shared/lib';
714

815
interface HeaderProps {
916
resortId?: number;
1017
resortName?: string;
1118
hasBackButton?: boolean;
1219
hasShareButton?: boolean;
20+
hasMenuButton?: boolean;
1321
}
1422

15-
const Header = ({ resortId, resortName, hasBackButton, hasShareButton }: HeaderProps) => {
23+
const Header = ({
24+
resortId,
25+
resortName,
26+
hasBackButton,
27+
hasShareButton,
28+
hasMenuButton,
29+
}: HeaderProps) => {
1630
const router = useRouter();
1731

1832
return (
@@ -41,6 +55,37 @@ const Header = ({ resortId, resortName, hasBackButton, hasShareButton }: HeaderP
4155
name={resortName}
4256
/>
4357
)}
58+
{hasMenuButton && (
59+
<div className={cn('absolute right-7 top-1/2 -translate-y-1/2')}>
60+
<DropdownMenu>
61+
<DropdownMenuTrigger>
62+
<MenuIcon />
63+
</DropdownMenuTrigger>
64+
<DropdownMenuContent>
65+
<DropdownMenuItem>
66+
<Link
67+
href="https://lizzie00.notion.site/weski?pvs=4"
68+
target="_blank"
69+
className={cn('flex items-center gap-1')}
70+
>
71+
<MessageIcon />
72+
<p className={cn('py-1')}>서비스 소개보기</p>
73+
</Link>
74+
</DropdownMenuItem>
75+
<DropdownMenuItem>
76+
<Link
77+
href="https://joey.team/block?block_id=oPWTFUezsWA61tdpDRoD&id=SoD0lftsYVQBUT65Hcpgp9mIBzj2"
78+
target="_blank"
79+
className={cn('flex items-center gap-1')}
80+
>
81+
<MailIcon />
82+
<p className={cn('py-1')}>버그 제보하기</p>
83+
</Link>
84+
</DropdownMenuItem>
85+
</DropdownMenuContent>
86+
</DropdownMenu>
87+
</div>
88+
)}
4489
</div>
4590
);
4691
};

0 commit comments

Comments
 (0)