Skip to content

Commit

Permalink
refactor: Carousel 컴포넌트 Compound 패턴 적용
Browse files Browse the repository at this point in the history
  • Loading branch information
dladncks1217 committed Jan 10, 2024
1 parent d8e070a commit 03c0a35
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 137 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import { css } from '@emotion/react';

import { Theme } from '@styles/Theme';

export const getContainerStyling = (width: number, height: number) => {
export const containerStyling = (width: number, height: number) => {
return css({
position: 'relative',

width,
height,
minWidth: width,
Expand All @@ -20,16 +21,29 @@ export const getContainerStyling = (width: number, height: number) => {
});
};

export const sliderWrapperStyling = css({
display: 'flex',
width: '100%',
margin: 0,
padding: 0,
export const sliderWrapperStyling = (width: number, height: number) =>
css({
display: 'flex',
width: '100%',
margin: 0,
padding: 0,
height,

overflow: 'hidden',
});
overflow: 'hidden',
});

export const carouselItemStyling = (width: number, height: number) =>
css({
display: 'flex',

'& > *': {
objectFit: 'cover',
width,
height,
},
});

export const getItemWrapperStyling = (width: number, height: number) => {
export const itemWrapperStyling = (width: number, height: number) => {
return css({
minWidth: width,
width,
Expand All @@ -47,7 +61,7 @@ export const getItemWrapperStyling = (width: number, height: number) => {
});
};

export const getButtonContainerStyling = (showOnHover: boolean) =>
export const buttonContainerStyling = (showOnHover: boolean) =>
css({
transition: 'opacity .1s ease-in',

Expand Down
98 changes: 98 additions & 0 deletions src/components/GeneralCarousel/Carousel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import LeftIcon from '@assets/svg/left-icon.svg';
import RightIcon from '@assets/svg/right-icon.svg';
import { createContext, useMemo } from 'react';
import type { PropsWithChildren } from 'react';

import useCarousel from '@hooks/useCarousel';

import Box from '@components/Box/Box';

import {
buttonContainerStyling,
containerStyling,
leftButtonStyling,
rightButtonStyling,
sliderWrapperStyling,
} from './Carousel.style';
import CarouselItem from './CarouselItem';
import Dots from './Dots';

export interface CarouselProps extends PropsWithChildren {
width: number;
height: number;
length: number;
showNavigationOnHover?: boolean;
showArrows?: boolean;
showDots?: boolean;
children?: JSX.Element | JSX.Element[];
}

export const CarouselContext = createContext<{
viewIndex: number;
width: number;
height: number;
itemRef: React.MutableRefObject<HTMLDivElement | null>;
} | null>(null);

const Carousel = ({
width,
height,
length,
showNavigationOnHover = true,
showArrows = true,
showDots = true,
children,
}: CarouselProps) => {
const { viewIndex, itemRef, carouselBoxRef, handleMoveImage, handleClickLeft, handleClickRight } =
useCarousel(length);

const context = useMemo(
() => ({
width,
height,
viewIndex,
itemRef,
carouselBoxRef,
handleMoveImage,
handleClickLeft,
handleClickRight,
}),
[
width,
height,
viewIndex,
itemRef,
carouselBoxRef,
handleMoveImage,
handleClickLeft,
handleClickRight,
]
);

return (
<CarouselContext.Provider value={context}>
<div css={containerStyling(width, height)} ref={carouselBoxRef}>
{showArrows && length !== 1 && (
<div css={buttonContainerStyling(showNavigationOnHover)}>
<button type="button" css={leftButtonStyling} onClick={handleClickLeft}>
<LeftIcon />
</button>
<button type="button" css={rightButtonStyling} onClick={handleClickRight}>
<RightIcon />
</button>
</div>
)}

{showDots && (
<Dots imageLength={length} activeNumber={viewIndex} moveImage={handleMoveImage} />
)}

<Box css={sliderWrapperStyling(width, height)}>{children}</Box>
</div>
</CarouselContext.Provider>
);
};

Carousel.Item = CarouselItem;

export default Carousel;
33 changes: 33 additions & 0 deletions src/components/GeneralCarousel/CarouselItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { useContext, useEffect, useRef } from 'react';
import type { PropsWithChildren } from 'react';

import { CarouselContext } from '@components/GeneralCarousel/Carousel';
import { carouselItemStyling } from '@components/GeneralCarousel/Carousel.style';

export interface CarouselItemProps extends PropsWithChildren {
index: number;
}

const CarouselItem = ({ index, children }: CarouselItemProps) => {
const ref = useRef<HTMLDivElement | null>(null);
const context = useContext(CarouselContext);

if (!context) throw Error('Carousel.Item is only available within Carousel.');

const { width, height, viewIndex, itemRef } = context;

useEffect(() => {
if (ref.current) {
if (index === viewIndex) itemRef.current = ref.current;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [viewIndex]);

return (
<div ref={ref} css={carouselItemStyling(width, height)}>
{children}
</div>
);
};

export default CarouselItem;
84 changes: 0 additions & 84 deletions src/components/GeneralCarousel/GeneralCarousel.tsx

This file was deleted.

15 changes: 11 additions & 4 deletions src/hooks/useGeneralCarousel.ts → src/hooks/useCarousel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useRef, useState } from 'react';
import type { MouseEvent } from 'react';
import { flushSync } from 'react-dom';

const useGeneralCarousel = (items: React.FC<React.SVGProps<SVGSVGElement>>[] | string[]) => {
const useCarousel = (itemLength: number) => {
const [viewIndex, setViewIndex] = useState(0);
const carouselBoxRef = useRef<HTMLDivElement | null>(null);
const itemRef = useRef<HTMLDivElement | null>(null);
Expand Down Expand Up @@ -41,7 +41,7 @@ const useGeneralCarousel = (items: React.FC<React.SVGProps<SVGSVGElement>>[] | s
e.stopPropagation();
if (itemRef.current) {
flushSync(() => {
if (viewIndex === items.length - 1) setViewIndex(viewIndex);
if (viewIndex === itemLength - 1) setViewIndex(viewIndex);
else setViewIndex(viewIndex + 1);
});

Expand All @@ -53,7 +53,14 @@ const useGeneralCarousel = (items: React.FC<React.SVGProps<SVGSVGElement>>[] | s
}
};

return { viewIndex, itemRef, carouselBoxRef, handleMoveImage, handleClickLeft, handleClickRight };
return {
viewIndex,
itemRef,
carouselBoxRef,
handleMoveImage,
handleClickLeft,
handleClickRight,
};
};

export default useGeneralCarousel;
export default useCarousel;
2 changes: 1 addition & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import DateRangePicker from '@components/DateRangePicker/DateRangePicker';
import Divider from '@components/Divider/Divider';
import Flex from '@components/Flex/Flex';
import FloatingButton from '@components/FloatingButton/FloatingButton';
import GeneralCarousel from '@components/GeneralCarousel/GeneralCarousel';
import GeneralCarousel from '@components/GeneralCarousel/Carousel';
import Heading from '@components/Heading/Heading';
import ImageCarousel from '@components/ImageCarousel/ImageCarousel';
import ImageUploadInput from '@components/ImageUploadInput/ImageUploadInput';
Expand Down
Loading

0 comments on commit 03c0a35

Please sign in to comment.