Skip to content

Commit

Permalink
Merge pull request #166 from hang-log-design-system/feature/#165
Browse files Browse the repository at this point in the history
Carousel 컴포넌트 재구축
  • Loading branch information
dladncks1217 authored Jan 5, 2024
2 parents 07f7cf9 + 75abb9c commit fbce6a6
Show file tree
Hide file tree
Showing 7 changed files with 376 additions and 1 deletion.
71 changes: 71 additions & 0 deletions src/components/GeneralCarousel/Dots.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { css } from '@emotion/react';

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

interface DotsProps {
imageLength: number;
activeNumber: number;
moveImage: (imageNumber: number) => void;
}

const Dots = ({ imageLength, activeNumber, moveImage }: DotsProps) => {
const images = Array.from({ length: imageLength }, () => '');

return (
<div css={dotContainerStyling}>
{images.map((_, index) => {
if (activeNumber === index)
return (
<button
type="button"
key={crypto.randomUUID()}
css={dotStyle(true)}
onClick={() => moveImage(index)}
/>
);
return (
<button
type="button"
key={crypto.randomUUID()}
css={dotStyle(false)}
onClick={() => moveImage(index)}
/>
);
})}
</div>
);
};

export default Dots;

const dotContainerStyling = css({
position: 'absolute',
display: 'flex',
gap: Theme.spacer.spacing2,

left: '50%',
bottom: Theme.spacer.spacing3,

transform: 'translateX(-50%)',
transition: 'opacity .1s ease-in',

cursor: 'pointer',

'.image-carousel-container:hover &': {
opacity: 1,
},
});

const dotStyle = (isSelected: boolean) => {
return css({
width: '6px',
height: '6px',

backgroundColor: Theme.color.white,
borderRadius: '50%',
border: 'none',

opacity: isSelected ? 1 : 0.6,
cursor: 'pointer',
});
};
110 changes: 110 additions & 0 deletions src/components/GeneralCarousel/GeneralCarousel.style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { css } from '@emotion/react';

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

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

borderRadius: Theme.borderRadius.medium,

overflow: 'hidden',

'& *': {
userSelect: 'none',
},
});
};

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

overflow: 'hidden',
});

export const getItemWrapperStyling = (width: number, height: number) => {
return css({
minWidth: width,
width,
minHeight: height,
height,

'& > img': {
width,
height,

backgroundColor: Theme.color.gray200,

objectFit: 'cover',
},
});
};

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

opacity: showOnHover ? 0 : 1,

'div:hover &': {
opacity: 1,
},

'& > button': {
position: 'absolute',
top: '50%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
zIndex: Theme.zIndex.overlayTop,

width: '28px',
height: '28px',
border: 'none',
borderRadius: '50%',
outline: '0',

backgroundColor: Theme.color.white,
boxShadow: Theme.boxShadow.shadow8,

transform: 'translateY(-50%)',

cursor: 'pointer',

'& svg': {
width: '12px',
height: '12px',

'& path': {
strokeWidth: 2,
},
},
},
});

export const leftButtonStyling = css({
left: Theme.spacer.spacing2,
});

export const rightButtonStyling = css({
right: Theme.spacer.spacing2,
});

export const dotStyling = (isSelected: boolean) => {
return css({
width: '6px',
height: '6px',
borderRadius: '50%',

backgroundColor: Theme.color.white,

opacity: isSelected ? 1 : 0.6,
});
};
78 changes: 78 additions & 0 deletions src/components/GeneralCarousel/GeneralCarousel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import LeftIcon from '@assets/svg/left-icon.svg';
import RightIcon from '@assets/svg/right-icon.svg';

import useGeneralCarousel from '@hooks/useGeneralCarousel';

import Box from '@components/Box/Box';
import Dots from '@components/GeneralCarousel/Dots';
import {
getButtonContainerStyling,
getContainerStyling,
getItemWrapperStyling,
leftButtonStyling,
rightButtonStyling,
sliderWrapperStyling,
} from '@components/GeneralCarousel/GeneralCarousel.style';

export interface useGeneralCarouselProps {
width: number;
height: number;
items: React.FC<React.SVGProps<SVGSVGElement>>[] | string[];
showArrows?: boolean;
showDots?: boolean;
}

const GeneralCarousel = ({
width,
height,
items,
showArrows = true,
showDots = true,
}: useGeneralCarouselProps) => {
const { viewIndex, itemRef, carouselBoxRef, handleMoveImage, handleClickLeft, handleClickRight } =
useGeneralCarousel(items);

return (
<div css={getContainerStyling(width, height)} ref={carouselBoxRef}>
{showDots && (
<Dots imageLength={items.length} activeNumber={viewIndex} moveImage={handleMoveImage} />
)}
{showArrows && items.length !== 1 && (
<div css={getButtonContainerStyling(true)}>
<button type="button" css={leftButtonStyling} onClick={handleClickLeft}>
<LeftIcon />
</button>
<button type="button" css={rightButtonStyling} onClick={handleClickRight}>
<RightIcon />
</button>
</div>
)}
<Box css={sliderWrapperStyling}>
{items.map((Item, index) => {
if (typeof Item === 'string') {
return (
<div
ref={index === viewIndex ? itemRef : null}
key={crypto.randomUUID()}
css={getItemWrapperStyling(width, height)}
>
<img draggable={false} src={Item} alt="이미지" />
</div>
);
}
return (
<div
ref={index === viewIndex ? itemRef : null}
key={crypto.randomUUID()}
css={getItemWrapperStyling(width, height)}
>
<Item />
</div>
);
})}
</Box>
</div>
);
};

export default GeneralCarousel;
3 changes: 2 additions & 1 deletion src/components/SVGCarouselModal/SVGCarouselModal.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
/* eslint-disable jsx-a11y/no-static-element-interactions */
import { useImageCarousel } from '@/hooks/useImageCarousel';
import LeftIcon from '@assets/svg/left-icon.svg';
import RightIcon from '@assets/svg/right-icon.svg';

import { useImageCarousel } from '@hooks/useImageCarousel';

import Box from '@components/Box/Box';
import Button from '@components/Button/Button';
import Flex from '@components/Flex/Flex';
Expand Down
56 changes: 56 additions & 0 deletions src/hooks/useGeneralCarousel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { useRef, useState } from 'react';
import { flushSync } from 'react-dom';

const useGeneralCarousel = (items: React.FC<React.SVGProps<SVGSVGElement>>[] | string[]) => {
const [viewIndex, setViewIndex] = useState(0);
const carouselBoxRef = useRef<HTMLDivElement | null>(null);
const itemRef = useRef<HTMLDivElement | null>(null);

const handleMoveImage = (imageNumber: number) => {
if (itemRef.current) {
flushSync(() => {
setViewIndex(imageNumber);
});

itemRef.current.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'center',
});
}
};

const handleClickLeft = () => {
if (itemRef.current) {
flushSync(() => {
if (viewIndex === 0) setViewIndex(0);
else setViewIndex(viewIndex - 1);
});

itemRef.current.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'center',
});
}
};

const handleClickRight = () => {
if (itemRef.current) {
flushSync(() => {
if (viewIndex === items.length - 1) setViewIndex(viewIndex);
else setViewIndex(viewIndex + 1);
});

itemRef.current.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'center',
});
}
};

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

export default useGeneralCarousel;
2 changes: 2 additions & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +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 Heading from '@components/Heading/Heading';
import ImageCarousel from '@components/ImageCarousel/ImageCarousel';
import ImageUploadInput from '@components/ImageUploadInput/ImageUploadInput';
Expand Down Expand Up @@ -88,5 +89,6 @@ export {
ToggleGroup,
SVGCarousel,
SVGCarouselModal,
GeneralCarousel,
Theme,
};
Loading

0 comments on commit fbce6a6

Please sign in to comment.