diff --git a/package.json b/package.json
index 8a5de93e..9c5b9924 100644
--- a/package.json
+++ b/package.json
@@ -14,6 +14,7 @@
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build",
"test": "vitest",
+ "test:ui": "vitest --ui",
"chromatic": "npx chromatic --project-token=chpt_228d999e438e234"
},
"dependencies": {
@@ -28,7 +29,7 @@
"react-dom": "^18",
"react-hook-form": "^7.49.2",
"react-redux": "^9.0.4",
- "swiper": "^11"
+ "react-slick": "^0.29.0"
},
"devDependencies": {
"@storybook/addon-essentials": "^7.6.6",
@@ -47,9 +48,11 @@
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
+ "@types/react-slick": "^0.23.13",
"@typescript-eslint/eslint-plugin": "^6.15.0",
"@typescript-eslint/parser": "^6.15.0",
"@vitejs/plugin-react": "^4.2.1",
+ "@vitest/ui": "^1.2.1",
"chromatic": "^10.2.0",
"eslint": "^8",
"eslint-config-airbnb": "^19.0.4",
@@ -62,6 +65,7 @@
"jsdom": "^23.2.0",
"msw": "^2.0.13",
"sass": "^1.69.6",
+ "slick-carousel": "^1.8.1",
"storybook": "^7.6.6",
"storybook-react-context": "^0.6.0",
"stylelint": "^16.1.0",
@@ -75,4 +79,4 @@
"msw": {
"workerDirectory": "public"
}
-}
\ No newline at end of file
+}
diff --git a/public/assets/icons/star.svg b/public/assets/icons/star.svg
new file mode 100644
index 00000000..c402ff4a
--- /dev/null
+++ b/public/assets/icons/star.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/assets/product.png b/public/assets/product.png
new file mode 100644
index 00000000..590c698e
Binary files /dev/null and b/public/assets/product.png differ
diff --git a/public/assets/recommandItem1.png b/public/assets/recommendItem1.png
similarity index 100%
rename from public/assets/recommandItem1.png
rename to public/assets/recommendItem1.png
diff --git a/public/assets/recommandItem2.png b/public/assets/recommendItem2.png
similarity index 100%
rename from public/assets/recommandItem2.png
rename to public/assets/recommendItem2.png
diff --git a/public/assets/recommandItem3.png b/public/assets/recommendItem3.png
similarity index 100%
rename from public/assets/recommandItem3.png
rename to public/assets/recommendItem3.png
diff --git a/public/assets/recommandItem4.png b/public/assets/recommendItem4.png
similarity index 100%
rename from public/assets/recommandItem4.png
rename to public/assets/recommendItem4.png
diff --git a/src/app/globals.css b/src/app/globals.css
index b1e55284..930516a9 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -1,3 +1,6 @@
+@import "~slick-carousel/slick/slick.css";
+@import "~slick-carousel/slick/slick-theme.css";
+
:root {
--primary: #0075FF;
--secondary: #B2D6FF;
diff --git a/src/app/page.module.scss b/src/app/page.module.scss
index 07f5d293..edb0f304 100644
--- a/src/app/page.module.scss
+++ b/src/app/page.module.scss
@@ -11,12 +11,7 @@
padding: 0 24px;
}
- .bannerWrapper {
- max-height: 140px;
- overflow-y: hidden;
- }
-
- .recommandTextWrapper {
+ .recommendTextWrapper {
padding: 0 24px;
}
diff --git a/src/app/page.tsx b/src/app/page.tsx
index f083de34..49a07985 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,19 +1,18 @@
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 Spacing from '@components/shared/spacing/Spacing';
-import Text from '@components/shared/text/Text';
+import BottomNav from '@shared/bottom-nav/BottomNav';
+import Banner from '@shared/carousel/Banner';
+import RecommendList from '@shared/carousel/RecommendList';
+import Flex from '@shared/flex/Flex';
+import Header from '@shared/header/Header';
+import ProductArticle from '@shared/product-article/ProductArticle';
+import Radio from '@shared/radio/Radio';
+import SearchBar from '@shared/search-bar/SearchBar';
+import Spacing from '@shared/spacing/Spacing';
+import Text from '@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 = [
@@ -43,25 +42,25 @@ const bannerData = [
},
];
-const recommandListData = [
+const recommendListData = [
{
id: 1,
link: '/',
- src: '/assets/recommandItem1.png',
+ src: '/assets/recommendItem1.png',
alt: '그림',
productName: '카샴푸',
},
{
id: 2,
link: '/',
- src: '/assets/recommandItem2.png',
+ src: '/assets/recommendItem2.png',
alt: '그림',
productName: '휠 클리너',
},
{
id: 3,
link: '/',
- src: '/assets/recommandItem3.png',
+ src: '/assets/recommendItem3.png',
alt: '그림',
productName: '타올',
@@ -69,7 +68,7 @@ const recommandListData = [
{
id: 4,
link: '/',
- src: '/assets/recommandItem4.png',
+ src: '/assets/recommendItem4.png',
alt: '그림',
productName: '먼지털이개',
@@ -77,7 +76,7 @@ const recommandListData = [
{
id: 5,
link: '/',
- src: '/assets/recommandItem4.png',
+ src: '/assets/recommendItem4.png',
alt: '그림',
productName: '먼지털이개',
@@ -85,7 +84,7 @@ const recommandListData = [
{
id: 6,
link: '/',
- src: '/assets/recommandItem4.png',
+ src: '/assets/recommendItem4.png',
alt: '그림',
productName: '먼지털이개',
@@ -137,15 +136,13 @@ export default function Home() {
-
-
-
+
-
+
추천 세차용품
-
+
WashPedia 랭킹
@@ -153,7 +150,7 @@ export default function Home() {
-
+
diff --git a/src/app/product/[id]/page.module.scss b/src/app/product/[id]/page.module.scss
new file mode 100644
index 00000000..3f56f6c3
--- /dev/null
+++ b/src/app/product/[id]/page.module.scss
@@ -0,0 +1,7 @@
+.product {
+ padding: 0 24px;
+}
+
+.productInfo {
+ padding: 16px 24px;
+}
diff --git a/src/app/product/[id]/page.tsx b/src/app/product/[id]/page.tsx
new file mode 100644
index 00000000..d91fce7d
--- /dev/null
+++ b/src/app/product/[id]/page.tsx
@@ -0,0 +1,43 @@
+import classNames from 'classnames/bind';
+import Image from 'next/image';
+
+import Star from '@components/icons/Star';
+import Flex from '@shared/flex/Flex';
+import Header from '@shared/header/Header';
+import Radio from '@shared/radio/Radio';
+import Spacing from '@shared/spacing/Spacing';
+import Text from '@shared/text/Text';
+
+import styles from './page.module.scss';
+
+const cx = classNames.bind(styles);
+
+function SuppliesPage() {
+ return (
+ <>
+
+
+
+ 카믹스
+ 아머올 세차용품 스피드 왁스 스프레이
+
+ 코팅제
+
+ •
+
+
+
+ 4.5
+
+ (20)
+
+
+
+
+
+
+ >
+ );
+}
+
+export default SuppliesPage;
diff --git a/src/components/icons/Star.tsx b/src/components/icons/Star.tsx
new file mode 100644
index 00000000..c67a08b0
--- /dev/null
+++ b/src/components/icons/Star.tsx
@@ -0,0 +1,10 @@
+function Star({ size }: { size: number }) {
+ return (
+
+
+ );
+}
+
+export default Star;
diff --git a/src/components/shared/accordion/Accordion.stories.tsx b/src/components/shared/accordion/Accordion.stories.tsx
index eb32ca3e..affded8f 100644
--- a/src/components/shared/accordion/Accordion.stories.tsx
+++ b/src/components/shared/accordion/Accordion.stories.tsx
@@ -1,11 +1,13 @@
-/* eslint-disable react/jsx-closing-tag-location */
-/* eslint-disable max-len */
-import type { Meta, StoryObj } from '@storybook/react';
+import type { Meta } from '@storybook/react';
import Minus from '@components/icons/Minus';
import Plus from '@components/icons/Plus';
+import Text from '@shared/text/Text';
import Accordion from './Accordion';
+import AccordionBody from './body/AccordionBody';
+import AccordionHeader from './header/AccordionHeader';
+import AccordionItem from './item/AccordionItem';
const meta = {
title: 'Shared/Accordion',
@@ -18,35 +20,41 @@ const meta = {
} satisfies Meta
;
export default meta;
-type Story = StoryObj;
-export const Horizontal: Story = {
- args: {
- children: <>
-
- } closeIcon={}>Accordion Item #1
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
- eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
- minim veniam, quis nostrud exercitation ullamco laboris nisi ut
- aliquip ex ea commodo consequat. Duis aute irure dolor in
- reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
- pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
- culpa qui officia deserunt mollit anim id est laborum.
-
-
-
- } closeIcon={}>Accordion Item #1
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
- eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
- minim veniam, quis nostrud exercitation ullamco laboris nisi ut
- aliquip ex ea commodo consequat. Duis aute irure dolor in
- reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
- pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
- culpa qui officia deserunt mollit anim id est laborum.
-
-
- >,
+export const AccordionStory = {
+ render: () => {
+ return (
+
+
+ } closeIcon={}>
+
+ 목록1
+
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
+ eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
+ minim veniam, quis nostrud exercitation ullamco laboris nisi ut
+
+
+
+
+
+
+ 목록2
+
+
+
+
+ aliquip ex ea commodo consequat. Duis aute irure dolor in
+ reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
+ pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
+ culpa qui officia deserunt mollit anim id est laborum.
+
+
+
+
+ );
},
};
diff --git a/src/components/shared/accordion/Accordion.tsx b/src/components/shared/accordion/Accordion.tsx
index 39606173..a130fcbc 100644
--- a/src/components/shared/accordion/Accordion.tsx
+++ b/src/components/shared/accordion/Accordion.tsx
@@ -1,35 +1,37 @@
-import { useCallback, useMemo, useState } from 'react';
+import {
+ forwardRef, useCallback, useMemo, useState,
+} from 'react';
-import AccordionContext from '@contexts/AccordionContext';
+import AccordionContext from '@/contexts/AccordionContext';
-import AccordionBody from './body';
-import AccordionHeader from './header';
-import AccordionItem from './item';
+import { AccordionProps } from './type/accordion.type';
-function Accordion({ children }: { children: React.ReactNode | React.ReactNode[] }) {
- const [activeItem, setActiveItem] = useState('');
+// eslint-disable-next-line max-len
+const Accordion = forwardRef(({ defaultActiveItems = [], children, ...props }, ref) => {
+ const [activeItems, setActiveItems] = useState(defaultActiveItems);
- const changeActiveItem = useCallback((value: string) => {
- if (activeItem !== value) setActiveItem(value);
- if (activeItem === value) setActiveItem('');
- }, [setActiveItem, activeItem]);
+ const handleSetActiveItem = useCallback((item: string) => {
+ if (activeItems?.includes(item)) {
+ setActiveItems(activeItems.filter((activeItem) => { return activeItem !== item; }));
+ } else {
+ setActiveItems([...activeItems, item]);
+ }
+ }, [activeItems]);
const values = useMemo(() => {
return {
- activeItem,
- changeSelectedItem: changeActiveItem,
+ activeItems,
+ setActiveItem: handleSetActiveItem,
};
- }, [activeItem, changeActiveItem]);
+ }, [activeItems, handleSetActiveItem]);
return (
- {children}
+
+ {children}
+
);
-}
-
-Accordion.Item = AccordionItem;
-Accordion.Header = AccordionHeader;
-Accordion.Body = AccordionBody;
+});
export default Accordion;
diff --git a/src/components/shared/accordion/body/AccordionBody.module.scss b/src/components/shared/accordion/body/AccordionBody.module.scss
new file mode 100644
index 00000000..6894b165
--- /dev/null
+++ b/src/components/shared/accordion/body/AccordionBody.module.scss
@@ -0,0 +1,9 @@
+.container {
+ width: 100%;
+ overflow: hidden;
+ transition: height 0.3s ease;
+
+ & > div[data-name="body-inner"] {
+ padding: 16px;
+ }
+}
diff --git a/src/components/shared/accordion/body/AccordionBody.tsx b/src/components/shared/accordion/body/AccordionBody.tsx
new file mode 100644
index 00000000..8c766983
--- /dev/null
+++ b/src/components/shared/accordion/body/AccordionBody.tsx
@@ -0,0 +1,46 @@
+import {
+ forwardRef, useEffect, useState, useRef,
+} from 'react';
+
+import classNames from 'classnames/bind';
+
+import { useAccordion } from '@/contexts/AccordionContext';
+
+import { AccordionBodyProps } from '../type/accordion.type';
+
+import styles from './AccordionBody.module.scss';
+
+const cx = classNames.bind(styles);
+
+const AccordionBody = forwardRef(({
+ itemName = '', children, className, ...props
+}, ref) => {
+ const innerRef = useRef(null);
+
+ const { activeItems } = useAccordion();
+ const isActive = activeItems.includes(itemName);
+
+ const [currentBodyHeight, setCurrentBodyHeight] = useState();
+
+ useEffect(() => {
+ if (innerRef.current == null) return;
+
+ setCurrentBodyHeight(isActive ? `${innerRef.current.clientHeight}px` : '0');
+ }, [isActive]);
+
+ return (
+
+ );
+});
+
+export default AccordionBody;
diff --git a/src/components/shared/accordion/body/index.module.scss b/src/components/shared/accordion/body/index.module.scss
deleted file mode 100644
index 7e378242..00000000
--- a/src/components/shared/accordion/body/index.module.scss
+++ /dev/null
@@ -1,12 +0,0 @@
-.container {
- box-sizing: border-box;
- max-height: 0;
- overflow: hidden;
- transition: all 0.3s ease-in-out;
-
- &.showItem {
- max-height: 350px;
- padding: 16px;
- background-color: var(--gray);
- }
-}
diff --git a/src/components/shared/accordion/body/index.tsx b/src/components/shared/accordion/body/index.tsx
deleted file mode 100644
index 11e685bb..00000000
--- a/src/components/shared/accordion/body/index.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import classNames from 'classnames/bind';
-
-import { useAccordion } from '@contexts/AccordionContext';
-
-import styles from './index.module.scss';
-
-const cx = classNames.bind(styles);
-
-interface AccordionBodyProps {
- children: React.ReactNode;
- label?: string;
- className?: string;
-}
-
-function AccordionBody({ children, label, className }: AccordionBodyProps) {
- const { activeItem } = useAccordion();
-
- return (
-
- {children}
-
- );
-}
-
-export default AccordionBody;
diff --git a/src/components/shared/accordion/header/index.module.scss b/src/components/shared/accordion/header/AccordionHeader.module.scss
similarity index 90%
rename from src/components/shared/accordion/header/index.module.scss
rename to src/components/shared/accordion/header/AccordionHeader.module.scss
index db665823..d0b5e751 100644
--- a/src/components/shared/accordion/header/index.module.scss
+++ b/src/components/shared/accordion/header/AccordionHeader.module.scss
@@ -2,6 +2,7 @@
display: flex;
align-items: center;
justify-content: space-between;
+ width: 100%;
padding: 16px;
border-bottom: 1px solid var(--gray-100);
}
diff --git a/src/components/shared/accordion/header/AccordionHeader.tsx b/src/components/shared/accordion/header/AccordionHeader.tsx
new file mode 100644
index 00000000..eaf3eb7a
--- /dev/null
+++ b/src/components/shared/accordion/header/AccordionHeader.tsx
@@ -0,0 +1,35 @@
+import {
+ forwardRef, useCallback,
+} from 'react';
+
+import classNames from 'classnames/bind';
+
+import { useAccordion } from '@contexts/AccordionContext';
+
+import { AccordionHeaderProps } from '../type/accordion.type';
+
+import styles from './AccordionHeader.module.scss';
+
+const cx = classNames.bind(styles);
+
+const AccordionHeader = forwardRef(({
+ itemName = '', children, onClick, className, openIcon, closeIcon, ...props
+}, ref) => {
+ const { setActiveItem, activeItems } = useAccordion();
+
+ const handleClick = useCallback((event: React.MouseEvent) => {
+ setActiveItem(itemName);
+ onClick?.(event);
+ }, [itemName, onClick, setActiveItem]);
+
+ const isActive = activeItems.includes(itemName);
+
+ return (
+
+ );
+});
+
+export default AccordionHeader;
diff --git a/src/components/shared/accordion/header/index.tsx b/src/components/shared/accordion/header/index.tsx
deleted file mode 100644
index 5e9355c4..00000000
--- a/src/components/shared/accordion/header/index.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { useCallback } from 'react';
-
-import classNames from 'classnames/bind';
-
-import { useAccordion } from '@contexts/AccordionContext';
-
-import styles from './index.module.scss';
-
-const cx = classNames.bind(styles);
-
-interface AccordionHeaderProps {
- children: React.ReactNode
- label?: string
- className?: string
- openIcon?: React.ReactNode
- closeIcon?: React.ReactNode
-}
-
-function AccordionHeader({
- label, children, className, openIcon, closeIcon,
-}: AccordionHeaderProps) {
- const { changeSelectedItem, activeItem } = useAccordion();
-
- const handleClickAccordionHeader = useCallback(() => {
- changeSelectedItem(label || '');
- }, [changeSelectedItem, label]);
-
- return (
-
- {children}
- {label === activeItem ? closeIcon : openIcon}
-
- );
-}
-
-export default AccordionHeader;
diff --git a/src/components/shared/accordion/item/AccordionItem.module.scss b/src/components/shared/accordion/item/AccordionItem.module.scss
new file mode 100644
index 00000000..6ec3262a
--- /dev/null
+++ b/src/components/shared/accordion/item/AccordionItem.module.scss
@@ -0,0 +1,8 @@
+.container {
+ width: 100%;
+ border-top: 1px solid var(--gray);
+
+ &:last-of-type {
+ border-bottom: 1px solid var(--gray);
+ }
+}
diff --git a/src/components/shared/accordion/item/AccordionItem.tsx b/src/components/shared/accordion/item/AccordionItem.tsx
new file mode 100644
index 00000000..d08682e0
--- /dev/null
+++ b/src/components/shared/accordion/item/AccordionItem.tsx
@@ -0,0 +1,33 @@
+import {
+ Children, cloneElement, forwardRef, isValidElement,
+} from 'react';
+
+import classNames from 'classnames/bind';
+
+import { AccordionItemProps } from '../type/accordion.type';
+
+import styles from './AccordionItem.module.scss';
+
+const cx = classNames.bind(styles);
+
+const AccordionItem = forwardRef(({
+ itemName, children, className, ...props
+}, ref) => {
+ const childrenWithProps = Children.toArray(children);
+
+ const accordionItemChildren = childrenWithProps.map((child) => {
+ if (isValidElement(child)) {
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
+ return cloneElement(child, { ...child.props, itemName });
+ }
+
+ return null;
+ });
+ return (
+
+ {accordionItemChildren}
+
+ );
+});
+
+export default AccordionItem;
diff --git a/src/components/shared/accordion/item/index.tsx b/src/components/shared/accordion/item/index.tsx
deleted file mode 100644
index 9caadddb..00000000
--- a/src/components/shared/accordion/item/index.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-/* eslint-disable @typescript-eslint/no-unsafe-argument */
-import { Children, cloneElement, isValidElement } from 'react';
-
-interface AccordionItemProps {
- children: React.ReactNode[]
- label: string
- className?: string
-}
-
-function AccordionItem({ children, label, className }: AccordionItemProps) {
- const childrenArray = Children.toArray(children);
-
- const accordionItemChildren = childrenArray.map((child) => {
- if (isValidElement(child)) {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- return cloneElement(child as any, { ...child.props, label });
- }
- return null;
- });
-
- return {accordionItemChildren}
;
-}
-
-export default AccordionItem;
diff --git a/src/components/shared/accordion/type/accordion.type.ts b/src/components/shared/accordion/type/accordion.type.ts
new file mode 100644
index 00000000..1df80dc9
--- /dev/null
+++ b/src/components/shared/accordion/type/accordion.type.ts
@@ -0,0 +1,19 @@
+export type AccordionProps = {
+ defaultActiveItems?: string[]
+ children: React.ReactNode | React.ReactNode[]
+} & Omit, 'children'>;
+
+export type AccordionItemProps = {
+ children: React.ReactNode[]
+ itemName: string
+} & Omit, 'children'>;
+
+export type AccordionHeaderProps = {
+ itemName?: string
+ openIcon?: React.ReactNode
+ closeIcon?: React.ReactNode
+} & React.ButtonHTMLAttributes;
+
+export type AccordionBodyProps = {
+ itemName?: string
+} & React.HTMLAttributes;
diff --git a/src/components/shared/carousel/Banner.scss b/src/components/shared/carousel/Banner.scss
new file mode 100644
index 00000000..78e32547
--- /dev/null
+++ b/src/components/shared/carousel/Banner.scss
@@ -0,0 +1,38 @@
+.container {
+ position: relative;
+
+ img {
+ width: auto;
+ margin: 0 auto;
+ }
+
+ .dotsCustom {
+ position: absolute;
+ bottom: 5px;
+ left: 50%;
+ transform: translateX(-50%);
+ }
+
+ .dotsCustom li {
+ display: inline-block;
+ margin: 0 6px;
+ cursor: pointer;
+ }
+
+ .dotsCustom li button {
+ display: block;
+ width: 16px;
+ height: 8px;
+ border-radius: 8px;
+ background: var(--gray-100);
+ color: transparent;
+ cursor: pointer;
+ }
+
+ /* stylelint-disable-next-line selector-class-pattern */
+ .dotsCustom li.slick-active button {
+ width: 24px;
+ border-radius: 8px;
+ background-color: var(--primary);
+ }
+}
diff --git a/src/components/shared/carousel/Banner.tsx b/src/components/shared/carousel/Banner.tsx
index 1ec250bb..9d8ec36f 100644
--- a/src/components/shared/carousel/Banner.tsx
+++ b/src/components/shared/carousel/Banner.tsx
@@ -1,32 +1,32 @@
'use client';
+import Slider from 'react-slick';
+
import Image from 'next/image';
import Link from 'next/link';
-import { Autoplay, Pagination } from 'swiper/modules';
-import { Swiper, SwiperSlide } from 'swiper/react';
-import 'swiper/scss';
-import 'swiper/scss/autoplay';
-import 'swiper/scss/pagination';
-import { IBannerdata } from './types/carousel.type';
+import './Banner.scss';
+import { IBannerData } from './types/carousel.type';
+
+const settings = {
+ dots: true,
+ infinite: true,
+ speed: 2000,
+ slidesToShow: 1,
+ slidesToScroll: 1,
+ autoplay: true,
+ adaptiveHeight: true,
+ autoplaySpeed: 5000,
+ arrows: false,
+};
-function Banner({ bannerData }: { bannerData: IBannerdata[] }) {
+function Banner({ bannerData }: { bannerData: IBannerData[] }) {
return (
-
- {bannerData.map((slide) => {
- return (
-
-
+
+
+ {bannerData.map((slide) => {
+ return (
+
-
- );
- })}
-
+ );
+ })}
+
+
);
}
diff --git a/src/components/shared/carousel/RecommandList.tsx b/src/components/shared/carousel/RecommandList.tsx
deleted file mode 100644
index 9269fabd..00000000
--- a/src/components/shared/carousel/RecommandList.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-'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.productName}
-
-
- );
- })}
-
- );
-}
-
-export default RecommandList;
diff --git a/src/components/shared/carousel/RecommandList.stories.tsx b/src/components/shared/carousel/RecommendList.stories.tsx
similarity index 67%
rename from src/components/shared/carousel/RecommandList.stories.tsx
rename to src/components/shared/carousel/RecommendList.stories.tsx
index 7f4b96bc..d5afd231 100644
--- a/src/components/shared/carousel/RecommandList.stories.tsx
+++ b/src/components/shared/carousel/RecommendList.stories.tsx
@@ -1,44 +1,44 @@
import type { Meta, StoryObj } from '@storybook/react';
-import RecommandList from './RecommandList';
+import RecommendList from './RecommendList';
const meta = {
- title: 'Shared/RecommandList',
- component: RecommandList,
+ title: 'Shared/RecommendList',
+ component: RecommendList,
parameters: {
},
tags: ['autodocs'],
argTypes: {
- recommandListData: {
+ recommendListData: {
control: 'object',
},
},
-} satisfies Meta;
+} satisfies Meta;
export default meta;
type Story = StoryObj;
export const AdBanner: Story = {
args: {
- recommandListData: [
+ recommendListData: [
{
id: 1,
link: '/',
- src: '/assets/recommandItem1.png',
+ src: '/assets/recommendItem1.png',
alt: '그림',
productName: '카샴푸',
},
{
id: 2,
link: '/',
- src: '/assets/recommandItem2.png',
+ src: '/assets/recommendItem2.png',
alt: '그림',
productName: '휠 클리너',
},
{
id: 3,
link: '/',
- src: '/assets/recommandItem3.png',
+ src: '/assets/recommendItem3.png',
alt: '그림',
productName: '타올',
@@ -46,7 +46,7 @@ export const AdBanner: Story = {
{
id: 4,
link: '/',
- src: '/assets/recommandItem4.png',
+ src: '/assets/recommendItem4.png',
alt: '그림',
productName: '먼지털이개',
@@ -54,7 +54,7 @@ export const AdBanner: Story = {
{
id: 5,
link: '/',
- src: '/assets/recommandItem4.png',
+ src: '/assets/recommendItem4.png',
alt: '그림',
productName: '먼지털이개',
@@ -62,7 +62,7 @@ export const AdBanner: Story = {
{
id: 6,
link: '/',
- src: '/assets/recommandItem4.png',
+ src: '/assets/recommendItem4.png',
alt: '그림',
productName: '먼지털이개',
diff --git a/src/components/shared/carousel/RecommendList.tsx b/src/components/shared/carousel/RecommendList.tsx
new file mode 100644
index 00000000..24dc3ac1
--- /dev/null
+++ b/src/components/shared/carousel/RecommendList.tsx
@@ -0,0 +1,47 @@
+'use client';
+
+import Slider from 'react-slick';
+
+import Image from 'next/image';
+import Link from 'next/link';
+
+import Flex from '@shared/flex/Flex';
+import Text from '@shared/text/Text';
+
+import { IRecommendList } from './types/carousel.type';
+
+const settings = {
+ dots: false,
+ infinite: true,
+ speed: 2000,
+ slidesToShow: 4,
+ slidesToScroll: 1,
+ autoplay: true,
+ adaptiveHeight: true,
+ autoplaySpeed: 5000,
+ arrows: false,
+};
+
+function RecommendList({ recommendListData }: { recommendListData: IRecommendList[] }) {
+ return (
+
+ {recommendListData?.map((slide) => {
+ return (
+
+
+
+
+ {slide.productName}
+
+ );
+ })}
+
+ );
+}
+
+export default RecommendList;
diff --git a/src/components/shared/carousel/types/carousel.type.ts b/src/components/shared/carousel/types/carousel.type.ts
index f1ccef2d..68c0fb5c 100644
--- a/src/components/shared/carousel/types/carousel.type.ts
+++ b/src/components/shared/carousel/types/carousel.type.ts
@@ -1,10 +1,10 @@
-export interface IBannerdata {
+export interface IBannerData {
id: number
link: string
src: string
alt: string
}
-export interface IRecommandList extends IBannerdata {
+export interface IRecommendList extends IBannerData {
productName: string
}
diff --git a/src/components/shared/radio/Radio.module.scss b/src/components/shared/radio/Radio.module.scss
index 0a5a8838..94a35a03 100644
--- a/src/components/shared/radio/Radio.module.scss
+++ b/src/components/shared/radio/Radio.module.scss
@@ -64,3 +64,11 @@
background-color: var(--primary);
color: var(--white);
}
+
+.label.product {
+ height: 36px;
+}
+
+.input[type="radio"]:checked + .label.product {
+ border-bottom: 1px solid var(--black);
+}
diff --git a/src/components/shared/radio/Radio.tsx b/src/components/shared/radio/Radio.tsx
index 44a916cf..57ef2b05 100644
--- a/src/components/shared/radio/Radio.tsx
+++ b/src/components/shared/radio/Radio.tsx
@@ -5,7 +5,7 @@ import classNames from 'classnames/bind';
import styles from './Radio.module.scss';
interface RadioProps extends InputHTMLAttributes {
- type: 'gender' | 'ageGroup' | 'additionalInfo' | 'filter'
+ type: 'gender' | 'ageGroup' | 'additionalInfo' | 'filter' | 'product'
label: string
value: string | number
}
diff --git a/src/contexts/AccordionContext.tsx b/src/contexts/AccordionContext.tsx
index 6faac792..89aed14a 100644
--- a/src/contexts/AccordionContext.tsx
+++ b/src/contexts/AccordionContext.tsx
@@ -1,13 +1,13 @@
import { createContext, useContext } from 'react';
interface AccordionContextValue {
- activeItem: string
- changeSelectedItem: (item: string) => void
+ activeItems: string[]
+ setActiveItem: (item: string) => void
}
const AccordionContext = createContext({
- activeItem: '',
- changeSelectedItem: () => { },
+ activeItems: [],
+ setActiveItem: () => { },
});
export function useAccordion() {
diff --git a/yarn.lock b/yarn.lock
index 983af32c..4220a2c5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1808,6 +1808,11 @@
schema-utils "^3.0.0"
source-map "^0.7.3"
+"@polka/url@^1.0.0-next.24":
+ version "1.0.0-next.24"
+ resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.24.tgz#58601079e11784d20f82d0585865bb42305c4df3"
+ integrity sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ==
+
"@radix-ui/number@1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@radix-ui/number/-/number-1.0.1.tgz#644161a3557f46ed38a042acf4a770e826021674"
@@ -3557,6 +3562,13 @@
dependencies:
"@types/react" "*"
+"@types/react-slick@^0.23.13":
+ version "0.23.13"
+ resolved "https://registry.yarnpkg.com/@types/react-slick/-/react-slick-0.23.13.tgz#037434e73a58063047b121e08565f7185d811f36"
+ integrity sha512-bNZfDhe/L8t5OQzIyhrRhBr/61pfBcWaYJoq6UDqFtv5LMwfg4NsVDD2J8N01JqdAdxLjOt66OZEp6PX+dGs/A==
+ dependencies:
+ "@types/react" "*"
+
"@types/react@*", "@types/react@>=16", "@types/react@^18":
version "18.2.45"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.45.tgz#253f4fac288e7e751ab3dc542000fb687422c15c"
@@ -3846,6 +3858,19 @@
dependencies:
tinyspy "^2.2.0"
+"@vitest/ui@^1.2.1":
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/@vitest/ui/-/ui-1.2.1.tgz#dbf5eafd90aa52a2cd08bdbcdd81244ca1aeb9e0"
+ integrity sha512-5kyEDpH18TB13Keutk5VScWG+LUDfPJOL2Yd1hqX+jv6+V74tp4ZYcmTgx//WDngiZA5PvX3qCHQ5KrhGzPbLg==
+ dependencies:
+ "@vitest/utils" "1.2.1"
+ fast-glob "^3.3.2"
+ fflate "^0.8.1"
+ flatted "^3.2.9"
+ pathe "^1.1.1"
+ picocolors "^1.0.0"
+ sirv "^2.0.4"
+
"@vitest/utils@0.34.7", "@vitest/utils@^0.34.6":
version "0.34.7"
resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-0.34.7.tgz#46d0d27cd0f6ca1894257d4e141c5c48d7f50295"
@@ -3865,6 +3890,16 @@
loupe "^2.3.7"
pretty-format "^29.7.0"
+"@vitest/utils@1.2.1":
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-1.2.1.tgz#ad798cb13ec9e9e97b13be65d135e9e8e3c586aa"
+ integrity sha512-bsH6WVZYe/J2v3+81M5LDU8kW76xWObKIURpPrOXm2pjBniBu2MERI/XP60GpS4PHU3jyK50LUutOwrx4CyHUg==
+ dependencies:
+ diff-sequences "^29.6.3"
+ estree-walker "^3.0.3"
+ loupe "^2.3.7"
+ pretty-format "^29.7.0"
+
"@webassemblyjs/ast@1.11.6", "@webassemblyjs/ast@^1.11.5":
version "1.11.6"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.6.tgz#db046555d3c413f8966ca50a95176a0e2c642e24"
@@ -4877,7 +4912,7 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
inherits "^2.0.1"
safe-buffer "^5.0.1"
-classnames@^2.5.1:
+classnames@^2.2.5, classnames@^2.5.1:
version "2.5.1"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b"
integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==
@@ -5710,6 +5745,11 @@ enhanced-resolve@^5.12.0, enhanced-resolve@^5.15.0, enhanced-resolve@^5.7.0:
graceful-fs "^4.2.4"
tapable "^2.2.0"
+enquire.js@^2.1.6:
+ version "2.1.6"
+ resolved "https://registry.yarnpkg.com/enquire.js/-/enquire.js-2.1.6.tgz#3e8780c9b8b835084c3f60e166dbc3c2a3c89814"
+ integrity sha512-/KujNpO+PT63F7Hlpu4h3pE3TokKRHN26JYmQpPyjkRD/N57R7bPDNojMXdi7uveAKjYB7yQnartCxZnFWr0Xw==
+
entities@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
@@ -6423,6 +6463,11 @@ fetch-retry@^5.0.2:
resolved "https://registry.yarnpkg.com/fetch-retry/-/fetch-retry-5.0.6.tgz#17d0bc90423405b7a88b74355bf364acd2a7fa56"
integrity sha512-3yurQZ2hD9VISAhJJP9bpYFNQrHHBXE2JxxjY5aLEcDi46RmAzJE2OC9FAde0yis5ElW0jTTzs0zfg/Cca4XqQ==
+fflate@^0.8.1:
+ version "0.8.1"
+ resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.1.tgz#1ed92270674d2ad3c73f077cd0acf26486dae6c9"
+ integrity sha512-/exOvEuc+/iaUm105QIiOt4LpBdMTWsXxqR0HDF35vx3fmaKzw7354gTilCh5rkzEt8WYyG//ku3h3nRmd7CHQ==
+
figures@^3.0.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af"
@@ -7848,6 +7893,13 @@ json-stable-stringify-without-jsonify@^1.0.1:
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
+json2mq@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/json2mq/-/json2mq-0.2.0.tgz#b637bd3ba9eabe122c83e9720483aeb10d2c904a"
+ integrity sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==
+ dependencies:
+ string-convert "^0.2.0"
+
json5@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593"
@@ -8351,6 +8403,11 @@ mri@^1.2.0:
resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b"
integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==
+mrmime@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-2.0.0.tgz#151082a6e06e59a9a39b46b3e14d5cfe92b3abb4"
+ integrity sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==
+
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
@@ -9503,6 +9560,17 @@ react-remove-scroll@2.5.5:
use-callback-ref "^1.3.0"
use-sidecar "^1.1.2"
+react-slick@^0.29.0:
+ version "0.29.0"
+ resolved "https://registry.yarnpkg.com/react-slick/-/react-slick-0.29.0.tgz#0bed5ea42bf75a23d40c0259b828ed27627b51bb"
+ integrity sha512-TGdOKE+ZkJHHeC4aaoH85m8RnFyWqdqRfAGkhd6dirmATXMZWAxOpTLmw2Ll/jPTQ3eEG7ercFr/sbzdeYCJXA==
+ dependencies:
+ classnames "^2.2.5"
+ enquire.js "^2.1.6"
+ json2mq "^0.2.0"
+ lodash.debounce "^4.0.8"
+ resize-observer-polyfill "^1.5.0"
+
react-style-singleton@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.2.1.tgz#f99e420492b2d8f34d38308ff660b60d0b1205b4"
@@ -9755,6 +9823,11 @@ reselect@^5.0.1:
resolved "https://registry.yarnpkg.com/reselect/-/reselect-5.0.1.tgz#587cdaaeb4e0e8927cff80ebe2bbef05f74b1648"
integrity sha512-D72j2ubjgHpvuCiORWkOUxndHJrxDaSolheiz5CO+roz8ka97/4msh2E8F5qay4GawR5vzBt5MkbDHT+Rdy/Wg==
+resize-observer-polyfill@^1.5.0:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
+ integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==
+
resolve-from@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
@@ -10153,6 +10226,15 @@ simple-update-notifier@^2.0.0:
dependencies:
semver "^7.5.3"
+sirv@^2.0.4:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/sirv/-/sirv-2.0.4.tgz#5dd9a725c578e34e449f332703eb2a74e46a29b0"
+ integrity sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==
+ dependencies:
+ "@polka/url" "^1.0.0-next.24"
+ mrmime "^2.0.0"
+ totalist "^3.0.0"
+
sisteransi@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
@@ -10172,6 +10254,11 @@ slice-ansi@^4.0.0:
astral-regex "^2.0.0"
is-fullwidth-code-point "^3.0.0"
+slick-carousel@^1.8.1:
+ version "1.8.1"
+ resolved "https://registry.yarnpkg.com/slick-carousel/-/slick-carousel-1.8.1.tgz#a4bfb29014887bb66ce528b90bd0cda262cc8f8d"
+ integrity sha512-XB9Ftrf2EEKfzoQXt3Nitrt/IPbT+f1fgqBdoxO3W/+JYvtEOW6EgxnWfr9GH6nmULv7Y2tPmEX3koxThVmebA==
+
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.1, source-map-js@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
@@ -10327,6 +10414,11 @@ strict-event-emitter@^0.5.0, strict-event-emitter@^0.5.1:
resolved "https://registry.yarnpkg.com/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz#1602ece81c51574ca39c6815e09f1a3e8550bd93"
integrity sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==
+string-convert@^0.2.0:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/string-convert/-/string-convert-0.2.1.tgz#6982cc3049fbb4cd85f8b24568b9d9bf39eeff97"
+ integrity sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==
+
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
@@ -10615,11 +10707,6 @@ swc-loader@^0.2.3:
resolved "https://registry.yarnpkg.com/swc-loader/-/swc-loader-0.2.3.tgz#6792f1c2e4c9ae9bf9b933b3e010210e270c186d"
integrity sha512-D1p6XXURfSPleZZA/Lipb3A8pZ17fP4NObZvFCDjK/OKljroqDpPmsBdTraWhVBqUNpcWBQY1imWdoPScRlQ7A==
-swiper@^11:
- version "11.0.5"
- resolved "https://registry.yarnpkg.com/swiper/-/swiper-11.0.5.tgz#6ed1ad06e6906ba42fd4b93d4988f0626a49046e"
- integrity sha512-rhCwupqSyRnWrtNzWzemnBLMoyYuoDgGgspAm/8iBD3jCvAWycPLH4Z3TB0O5520DHLzMx94yUMH/B9Efpa48w==
-
symbol-tree@^3.2.4:
version "3.2.4"
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
@@ -10850,6 +10937,11 @@ toidentifier@1.0.1:
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
+totalist@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.1.tgz#ba3a3d600c915b1a97872348f79c127475f6acf8"
+ integrity sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==
+
tough-cookie@^4.1.3:
version "4.1.3"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.3.tgz#97b9adb0728b42280aa3d814b6b999b2ff0318bf"