Skip to content

Commit 4778c0c

Browse files
authored
Merge pull request #133 from YAPP-Github/feature/PRODUCT-244
feat: 플로팅 버튼 컴포넌트 생성 (#132)
2 parents cbca3ca + 6123030 commit 4778c0c

File tree

11 files changed

+175
-1
lines changed

11 files changed

+175
-1
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { style } from "@vanilla-extract/css";
2+
3+
import { semantic } from "@/styles";
4+
5+
export const plusIcon = style({
6+
color: semantic.icon.white,
7+
});
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
"use client";
2+
3+
import { useRouter } from "next/navigation";
4+
5+
import PlusIcon from "@/assets/plus.svg";
6+
import { FloatingButton } from "@/components/ui/FloatingButton";
7+
import { HStack } from "@/components/ui/Stack";
8+
import { Text } from "@/components/ui/Text";
9+
import { useScrollThreshold } from "@/hooks";
10+
11+
import * as styles from "./RegisterFloatingButton.css";
12+
13+
export const RegisterFloatingButton = () => {
14+
const router = useRouter();
15+
const { isScrolled } = useScrollThreshold(200);
16+
17+
const handleClick = () => {
18+
router.push("/stores/register");
19+
};
20+
21+
return (
22+
<FloatingButton
23+
variant={isScrolled ? "scrolled" : "initial"}
24+
aria-label='응원 등록 페이지로 이동'
25+
onClick={handleClick}
26+
>
27+
{isScrolled ? (
28+
<PlusIcon width={24} height={24} className={styles.plusIcon} />
29+
) : (
30+
<HStack align='center' gap={4}>
31+
<PlusIcon width={16} height={16} className={styles.plusIcon} />
32+
<Text typo='body2Sb' color='text.white'>
33+
응원 등록
34+
</Text>
35+
</HStack>
36+
)}
37+
</FloatingButton>
38+
);
39+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./RegisterFloatingButton";

src/app/(home)/page.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { VStack } from "@/components/ui/Stack";
66

77
import { RecentCheers, RegisterPopup, Story } from "./_components";
88
import { ServiceIntroBottomSheet } from "./_components/ServiceIntroBottomSheet";
9+
import { RegisterFloatingButton } from "./_shared/RegisterFloatingButton";
910

1011
export default function HomePage() {
1112
return (
@@ -19,6 +20,7 @@ export default function HomePage() {
1920
<VStack gap={40}>
2021
<RecentCheers />
2122
</VStack>
23+
<RegisterFloatingButton />
2224
<ServiceIntroBottomSheet />
2325
<RegisterPopup />
2426
</>

src/app/(store)/stores/(list)/page.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { Suspense } from "@suspensive/react";
44

5+
import { RegisterFloatingButton } from "@/app/(home)/_shared/RegisterFloatingButton";
56
import { Bleed } from "@/components/ui/Bleed";
67
import { VStack } from "@/components/ui/Stack";
78

@@ -27,6 +28,7 @@ export default function StoreListPage() {
2728
<Suspense>
2829
<StoreList category={selectedCategory.name} />
2930
</Suspense>
31+
<RegisterFloatingButton />
3032
</VStack>
3133
);
3234
}

src/assets/plus.svg

Lines changed: 1 addition & 1 deletion
Loading
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { recipe, type RecipeVariants } from "@vanilla-extract/recipes";
2+
3+
import { semantic } from "@/styles";
4+
5+
export const floatingButton = recipe({
6+
base: {
7+
position: "fixed",
8+
right: "calc((100vw - min(100vw, 480px)) / 2 + 1rem)",
9+
bottom: "2rem",
10+
borderRadius: "4rem",
11+
zIndex: 1000,
12+
boxShadow: "0 0 24px 0 rgba(79, 79, 79, 0.24)",
13+
backgroundColor: semantic.button.primaryNormalSolid,
14+
display: "flex",
15+
alignItems: "center",
16+
justifyContent: "center",
17+
whiteSpace: "nowrap",
18+
transition: "all 0.3s ease",
19+
20+
selectors: {
21+
"&:hover": { transform: "scale(1.05)" },
22+
"&:active": { transform: "scale(0.9)" },
23+
},
24+
},
25+
variants: {
26+
variant: {
27+
initial: {
28+
padding: "1.1rem 1.6rem",
29+
},
30+
scrolled: {
31+
padding: "1.4rem",
32+
},
33+
},
34+
},
35+
defaultVariants: {
36+
variant: "initial",
37+
},
38+
});
39+
40+
export type FloatingButtonVariants = RecipeVariants<typeof floatingButton>;
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
"use client";
2+
3+
import { type ComponentProps, type ReactNode } from "react";
4+
5+
import {
6+
floatingButton,
7+
type FloatingButtonVariants,
8+
} from "./FloatingButton.css";
9+
10+
export type FloatingButtonProps = {
11+
children: ReactNode;
12+
className?: string;
13+
} & ComponentProps<"button"> &
14+
FloatingButtonVariants;
15+
16+
/**
17+
* 플로팅 버튼 컴포넌트
18+
* @description 플로팅 버튼 컴포넌트는 화면에 고정되어 표시되는 액션 버튼입니다. 스크롤 상태에 따라 크기와 내용이 변경됩니다.'
19+
* @example
20+
* ```tsx
21+
* <FloatingButton variant="initial">
22+
* <HStack align="center" gap={4}>
23+
* <PlusIcon width={16} height={16} />
24+
* <Text typo="body2Sb" color="text.white">
25+
* 응원 등록
26+
* </Text>
27+
* </HStack>
28+
* </FloatingButton>
29+
*
30+
* <FloatingButton variant="scrolled">
31+
* <PlusIcon width={24} height={24} />
32+
* </FloatingButton>
33+
* ```
34+
*/
35+
export const FloatingButton = ({
36+
children,
37+
className,
38+
variant,
39+
...props
40+
}: FloatingButtonProps) => {
41+
const floatingButtonStyle = floatingButton({ variant });
42+
43+
return (
44+
<button
45+
type='button'
46+
className={`${floatingButtonStyle} ${className || ""}`}
47+
{...props}
48+
>
49+
{children}
50+
</button>
51+
);
52+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { FloatingButton } from "./FloatingButton";

src/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./useScrollThreshold";

0 commit comments

Comments
 (0)