diff --git a/package.json b/package.json index 6bcba3b3..8a5de93e 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "react-dom": "^18", "react-hook-form": "^7.49.2", "react-redux": "^9.0.4", - "swiper": "^10" + "swiper": "^11" }, "devDependencies": { "@storybook/addon-essentials": "^7.6.6", @@ -75,4 +75,4 @@ "msw": { "workerDirectory": "public" } -} +} \ No newline at end of file diff --git a/public/assets/banner.png b/public/assets/banner.png index acd98915..f7b3f40c 100644 Binary files a/public/assets/banner.png and b/public/assets/banner.png differ diff --git a/public/assets/recommandItem1.png b/public/assets/recommandItem1.png new file mode 100644 index 00000000..ad9454d2 Binary files /dev/null and b/public/assets/recommandItem1.png differ diff --git a/public/assets/recommandItem2.png b/public/assets/recommandItem2.png new file mode 100644 index 00000000..47629618 Binary files /dev/null and b/public/assets/recommandItem2.png differ diff --git a/public/assets/recommandItem3.png b/public/assets/recommandItem3.png new file mode 100644 index 00000000..d1062a32 Binary files /dev/null and b/public/assets/recommandItem3.png differ diff --git a/public/assets/recommandItem4.png b/public/assets/recommandItem4.png new file mode 100644 index 00000000..7113b2dc Binary files /dev/null and b/public/assets/recommandItem4.png differ diff --git a/src/app/globals.css b/src/app/globals.css index 512b5bc8..67437e92 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -14,6 +14,7 @@ --pink: #F67272; /* -------------- z-index -------------- */ + --header-zindex: 2; --dimmed-zindex: 10; --modal-zindex: 11; } diff --git a/src/app/page.module.css b/src/app/page.module.css deleted file mode 100644 index 6676d2c6..00000000 --- a/src/app/page.module.css +++ /dev/null @@ -1,229 +0,0 @@ -.main { - display: flex; - flex-direction: column; - justify-content: space-between; - align-items: center; - padding: 6rem; - min-height: 100vh; -} - -.description { - display: inherit; - justify-content: inherit; - align-items: inherit; - font-size: 0.85rem; - max-width: var(--max-width); - width: 100%; - z-index: 2; - font-family: var(--font-mono); -} - -.description a { - display: flex; - justify-content: center; - align-items: center; - gap: 0.5rem; -} - -.description p { - position: relative; - margin: 0; - padding: 1rem; - background-color: rgba(var(--callout-rgb), 0.5); - border: 1px solid rgba(var(--callout-border-rgb), 0.3); - border-radius: var(--border-radius); -} - -.code { - font-weight: 700; - font-family: var(--font-mono); -} - -.grid { - display: grid; - grid-template-columns: repeat(4, minmax(25%, auto)); - max-width: 100%; - width: var(--max-width); -} - -.card { - padding: 1rem 1.2rem; - border-radius: var(--border-radius); - background: rgba(var(--card-rgb), 0); - border: 1px solid rgba(var(--card-border-rgb), 0); - transition: background 200ms, border 200ms; -} - -.card span { - display: inline-block; - transition: transform 200ms; -} - -.card h2 { - font-weight: 600; - margin-bottom: 0.7rem; -} - -.card p { - margin: 0; - opacity: 0.6; - font-size: 0.9rem; - line-height: 1.5; - max-width: 30ch; -} - -.center { - display: flex; - justify-content: center; - align-items: center; - position: relative; - padding: 4rem 0; -} - -.center::before { - background: var(--secondary-glow); - border-radius: 50%; - width: 480px; - height: 360px; - margin-left: -400px; -} - -.center::after { - background: var(--primary-glow); - width: 240px; - height: 180px; - z-index: -1; -} - -.center::before, -.center::after { - content: ''; - left: 50%; - position: absolute; - filter: blur(45px); - transform: translateZ(0); -} - -.logo { - position: relative; -} -/* Enable hover only on non-touch devices */ -@media (hover: hover) and (pointer: fine) { - .card:hover { - background: rgba(var(--card-rgb), 0.1); - border: 1px solid rgba(var(--card-border-rgb), 0.15); - } - - .card:hover span { - transform: translateX(4px); - } -} - -@media (prefers-reduced-motion) { - .card:hover span { - transform: none; - } -} - -/* Mobile */ -@media (max-width: 700px) { - .content { - padding: 4rem; - } - - .grid { - grid-template-columns: 1fr; - margin-bottom: 120px; - max-width: 320px; - text-align: center; - } - - .card { - padding: 1rem 2.5rem; - } - - .card h2 { - margin-bottom: 0.5rem; - } - - .center { - padding: 8rem 0 6rem; - } - - .center::before { - transform: none; - height: 300px; - } - - .description { - font-size: 0.8rem; - } - - .description a { - padding: 1rem; - } - - .description p, - .description div { - display: flex; - justify-content: center; - position: fixed; - width: 100%; - } - - .description p { - align-items: center; - inset: 0 0 auto; - padding: 2rem 1rem 1.4rem; - border-radius: 0; - border: none; - border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25); - background: linear-gradient( - to bottom, - rgba(var(--background-start-rgb), 1), - rgba(var(--callout-rgb), 0.5) - ); - background-clip: padding-box; - backdrop-filter: blur(24px); - } - - .description div { - align-items: flex-end; - pointer-events: none; - inset: auto 0 0; - padding: 2rem; - height: 200px; - background: linear-gradient( - to bottom, - transparent 0%, - rgb(var(--background-end-rgb)) 40% - ); - z-index: 1; - } -} - -/* Tablet and Smaller Desktop */ -@media (min-width: 701px) and (max-width: 1120px) { - .grid { - grid-template-columns: repeat(2, 50%); - } -} - -@media (prefers-color-scheme: dark) { - .vercelLogo { - filter: invert(1); - } - - .logo { - filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70); - } -} - -@keyframes rotate { - from { - transform: rotate(360deg); - } - to { - transform: rotate(0deg); - } -} diff --git a/src/app/page.module.scss b/src/app/page.module.scss new file mode 100644 index 00000000..07f5d293 --- /dev/null +++ b/src/app/page.module.scss @@ -0,0 +1,32 @@ +.headerWrapper { + padding: 0 24px; + + .header { + z-index: 999; + } +} + +.mainContainer { + .searchBarWrapper { + padding: 0 24px; + } + + .bannerWrapper { + max-height: 140px; + overflow-y: hidden; + } + + .recommandTextWrapper { + padding: 0 24px; + } + + .productListContainer { + padding: 0 24px 80px; + + .productArticleContainer { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 12px; + } + } +} diff --git a/src/app/page.tsx b/src/app/page.tsx index 7f4a4ff1..d4f49996 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,10 +1,172 @@ -import Link from 'next/link'; +import classNames from 'classnames/bind'; +import dynamic from 'next/dynamic'; + +import BottomNav from '@components/shared/bottom-nav/BottomNav'; +import Flex from '@components/shared/flex/Flex'; +import Header from '@components/shared/header/Header'; +import ProductArticle from '@components/shared/product-article/ProductArticle'; +import Radio from '@components/shared/radio/Radio'; +import SearchBar from '@components/shared/search-bar/SearchBar'; +import Spacing from '@components/shared/spacing/Spacing'; +import Text from '@components/shared/text/Text'; + +import styles from './page.module.scss'; + +const Banner = dynamic(() => { return import('@components/shared/carousel/Banner'); }); +const RecommandList = dynamic(() => { return import('@components/shared/carousel/RecommandList'); }); + +const cx = classNames.bind(styles); + +const bannerData = [ + { + id: 1, + link: '/', + src: '/assets/banner.png', + alt: '그림', + }, + { + id: 2, + link: '/', + src: '/assets/banner.png', + alt: '그림', + }, + { + id: 3, + link: '/', + src: '/assets/banner.png', + alt: '그림', + }, + { + id: 4, + link: '/', + src: '/assets/banner.png', + alt: '그림', + }, +]; + +const recommandListData = [ + { + id: 1, + link: '/', + src: '/assets/recommandItem1.png', + alt: '그림', + productName: '카샴푸', + }, + { + id: 2, + link: '/', + src: '/assets/recommandItem2.png', + alt: '그림', + productName: '휠 클리너', + }, + { + id: 3, + link: '/', + src: '/assets/recommandItem3.png', + alt: '그림', + productName: '타올', + + }, + { + id: 4, + link: '/', + src: '/assets/recommandItem4.png', + alt: '그림', + productName: '먼지털이개', + + }, + { + id: 5, + link: '/', + src: '/assets/recommandItem4.png', + alt: '그림', + productName: '먼지털이개', + + }, + { + id: 6, + link: '/', + src: '/assets/recommandItem4.png', + alt: '그림', + productName: '먼지털이개', + + }, +]; + +const productArticleData = [ + { + brand: '카믹스', + category: '코팅제', + id: 1, + img: '/assets/profile.JPG', + name: '아머올 세차용품 스피드 왁스 스프레이 500ml스피드 왁스 스프레이 500ml', + warningLevel: 'warning', + }, + { + brand: '카믹스', + category: '코팅제', + id: 2, + img: '/assets/profile.JPG', + name: '아머올 세차용품 스피드 왁스 스프레이 500ml스피드 왁스 스프레이 500ml', + warningLevel: 'warning', + }, + { + brand: '카믹스', + category: '코팅제', + id: 3, + img: '/assets/profile.JPG', + name: '아머올 세차용품 스피드 왁스 스프레이 500ml스피드 왁스 스프레이 500ml', + warningLevel: 'warning', + }, + { + brand: '카믹스', + category: '코팅제', + id: 4, + img: '/assets/profile.JPG', + name: '아머올 세차용품 스피드 왁스 스프레이 500ml스피드 왁스 스프레이 500ml', + warningLevel: 'warning', + }, +]; export default function Home() { + // TODO: 비로그인 회원과 부가정보를 입력하지 않은 회원은 부가정보 입력 배너 보이도록 UI 추가 return ( -
-

Home

- About -
+ <> +
+
+
+ + + +
+
+ +
+ +
+ 추천 세차용품 +
+ + + +
+ WashPedia 랭킹 + + + + + + + + +
+ {productArticleData.map((item) => { + return ; + })} +
+
+
+ + ); } diff --git a/src/components/shared/bottom-nav/BottomNav.module.scss b/src/components/shared/bottom-nav/BottomNav.module.scss index 1828c956..6134830f 100644 --- a/src/components/shared/bottom-nav/BottomNav.module.scss +++ b/src/components/shared/bottom-nav/BottomNav.module.scss @@ -4,7 +4,8 @@ bottom: 0; left: 0; height: 44px; - padding: 12px 20px 30px; + padding: 12px 20px 10px; + background-color: var(--white); ul { display: flex; diff --git a/src/components/shared/carousel/Banner.stories.tsx b/src/components/shared/carousel/Banner.stories.tsx new file mode 100644 index 00000000..cdef9c83 --- /dev/null +++ b/src/components/shared/carousel/Banner.stories.tsx @@ -0,0 +1,36 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import Banner from './Banner'; + +const meta = { + title: 'Shared/Banner', + component: Banner, + parameters: { + }, + tags: ['autodocs'], + argTypes: { + bannerData: { + control: 'object', + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const AdBanner: Story = { + args: { + bannerData: [{ + id: 1, link: '/', src: '/assets/banner.png', alt: '그림', + }, + { + id: 2, link: '/', src: '/assets/banner.png', alt: '그림', + }, + { + id: 3, link: '/', src: '/assets/banner.png', alt: '그림', + }, + { + id: 4, link: '/', src: '/assets/banner.png', alt: '그림', + }], + }, +}; diff --git a/src/components/shared/carousel/Carousel.tsx b/src/components/shared/carousel/Banner.tsx similarity index 53% rename from src/components/shared/carousel/Carousel.tsx rename to src/components/shared/carousel/Banner.tsx index 1e579bd3..1ec250bb 100644 --- a/src/components/shared/carousel/Carousel.tsx +++ b/src/components/shared/carousel/Banner.tsx @@ -1,3 +1,5 @@ +'use client'; + import Image from 'next/image'; import Link from 'next/link'; import { Autoplay, Pagination } from 'swiper/modules'; @@ -6,39 +8,30 @@ import { Swiper, SwiperSlide } from 'swiper/react'; import 'swiper/scss'; import 'swiper/scss/autoplay'; import 'swiper/scss/pagination'; -import { Idata } from './types/Carousel'; - -interface CarouselProps { - data: Idata[] - slidesPerView?: number - spaceBetween?: number - aspectRatio: number -} +import { IBannerdata } from './types/carousel.type'; -function Carousel({ - data, slidesPerView = 1, spaceBetween = 50, aspectRatio, -}: CarouselProps) { +function Banner({ bannerData }: { bannerData: IBannerdata[] }) { return ( - {data.map((slide) => { + {bannerData.map((slide) => { return ( {slide.alt} @@ -48,4 +41,4 @@ function Carousel({ ); } -export default Carousel; +export default Banner; diff --git a/src/components/shared/carousel/Carousel.stories.tsx b/src/components/shared/carousel/Carousel.stories.tsx deleted file mode 100644 index 5bf4e1da..00000000 --- a/src/components/shared/carousel/Carousel.stories.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import Carousel from './Carousel'; - -const meta = { - title: 'Shared/Carousel', - component: Carousel, - parameters: { - }, - tags: ['autodocs'], - argTypes: { - data: { - control: 'object', - }, - aspectRatio: { control: 'number' }, - }, -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -export const AdBanner: Story = { - args: { - data: [{ - id: 1, link: '/', src: '/assets/banner.png', alt: '그림', - }, { - id: 2, link: '/', src: '/assets/banner.png', alt: '그림', - }, - { - id: 3, link: '/', src: '/assets/banner.png', alt: '그림', - }, - { - id: 4, link: '/', src: '/assets/banner.png', alt: '그림', - }], - aspectRatio: 1, - }, -}; - -export const RecommandProduct: Story = { - args: { - data: [{ - id: 1, link: '/', src: '/assets/banner.png', alt: '그림', - }, { - id: 2, link: '/', src: '/assets/banner.png', alt: '그림', - }, - { - id: 3, link: '/', src: '/assets/banner.png', alt: '그림', - }, - { - id: 4, link: '/', src: '/assets/banner.png', alt: '그림', - }], - slidesPerView: 3, - spaceBetween: 10, - aspectRatio: 1, - }, -}; diff --git a/src/components/shared/carousel/RecommandList.stories.tsx b/src/components/shared/carousel/RecommandList.stories.tsx new file mode 100644 index 00000000..7f4b96bc --- /dev/null +++ b/src/components/shared/carousel/RecommandList.stories.tsx @@ -0,0 +1,72 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import RecommandList from './RecommandList'; + +const meta = { + title: 'Shared/RecommandList', + component: RecommandList, + parameters: { + }, + tags: ['autodocs'], + argTypes: { + recommandListData: { + control: 'object', + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const AdBanner: Story = { + args: { + recommandListData: [ + { + id: 1, + link: '/', + src: '/assets/recommandItem1.png', + alt: '그림', + productName: '카샴푸', + }, + { + id: 2, + link: '/', + src: '/assets/recommandItem2.png', + alt: '그림', + productName: '휠 클리너', + }, + { + id: 3, + link: '/', + src: '/assets/recommandItem3.png', + alt: '그림', + productName: '타올', + + }, + { + id: 4, + link: '/', + src: '/assets/recommandItem4.png', + alt: '그림', + productName: '먼지털이개', + + }, + { + id: 5, + link: '/', + src: '/assets/recommandItem4.png', + alt: '그림', + productName: '먼지털이개', + + }, + { + id: 6, + link: '/', + src: '/assets/recommandItem4.png', + alt: '그림', + productName: '먼지털이개', + + }, + ], + }, +}; diff --git a/src/components/shared/carousel/RecommandList.tsx b/src/components/shared/carousel/RecommandList.tsx new file mode 100644 index 00000000..9269fabd --- /dev/null +++ b/src/components/shared/carousel/RecommandList.tsx @@ -0,0 +1,47 @@ +'use client'; + +import Image from 'next/image'; +import Link from 'next/link'; +import { Autoplay } from 'swiper/modules'; +import { Swiper, SwiperSlide } from 'swiper/react'; + +import 'swiper/scss'; +import 'swiper/scss/autoplay'; +import 'swiper/scss/pagination'; +import Flex from '@shared/flex/Flex'; +import Text from '@shared/text/Text'; + +import { IRecommandList } from './types/carousel.type'; + +function RecommandList({ recommandListData }: { recommandListData: IRecommandList[] }) { + return ( + + {recommandListData.map((slide) => { + return ( + + + + {slide.alt} + + {slide.productName} + + + ); + })} + + ); +} + +export default RecommandList; diff --git a/src/components/shared/carousel/types/Carousel.ts b/src/components/shared/carousel/types/Carousel.ts deleted file mode 100644 index 311545e9..00000000 --- a/src/components/shared/carousel/types/Carousel.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface Idata { - id: number - link: string - src: string - alt: string -} diff --git a/src/components/shared/carousel/types/carousel.type.ts b/src/components/shared/carousel/types/carousel.type.ts new file mode 100644 index 00000000..f1ccef2d --- /dev/null +++ b/src/components/shared/carousel/types/carousel.type.ts @@ -0,0 +1,10 @@ +export interface IBannerdata { + id: number + link: string + src: string + alt: string +} + +export interface IRecommandList extends IBannerdata { + productName: string +} diff --git a/src/components/shared/header/Header.module.scss b/src/components/shared/header/Header.module.scss index e3ba8b3d..6e2bce91 100644 --- a/src/components/shared/header/Header.module.scss +++ b/src/components/shared/header/Header.module.scss @@ -26,6 +26,11 @@ $white: var(--white); .hide { display: none; } + + &.home { + z-index: var(--header-zindex); + padding: 0 24px; + } } .container { diff --git a/src/components/shared/header/Header.tsx b/src/components/shared/header/Header.tsx index dd732dfe..89762c10 100644 --- a/src/components/shared/header/Header.tsx +++ b/src/components/shared/header/Header.tsx @@ -8,11 +8,11 @@ import { HeaderProps } from './types/headerType'; export default function Header({ isLogin = false, - displayLogo = true, isTransparent = false, displayRightIcon = false, -}:HeaderProps) { + displayLogo = true, isTransparent = false, displayRightIcon = false, className, +}: HeaderProps) { const cx = classNames.bind(styles); return ( -