diff --git a/README.md b/README.md index 441cc36..b21a00e 100644 --- a/README.md +++ b/README.md @@ -91,12 +91,25 @@ you can set `startIndex` and `minMove`. `minMove` : Minimum movement required for the index to shift. -### useDragCarouselIndex +#### parameters + +`dataLength`, `startIndex`, `minMove` + +`startIndex` : specifies the start index. + +`minMove`: determines how many slides you have to slide over. + +#### return values -This hook should be called within the carousel provider provided by the useDragIndexCarousel component. -carousel provider renders children elements. It already has the `display: flex` property included. +useDragCarousel returns `CarouselWrapper`, `next`, `prev`, `index`, `ref`, `isEnd`, `isStart` -you can get current index in carousel children. +`CarouselWrapper`: renders children elements. It already contains `display:flex` property. + +`ref`: you need to assign a ref to the Carousel Wrapper. + +`next`: increase index + +`prev`: decrease index ### useCarousel diff --git a/package.json b/package.json index bc23723..d6b9894 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@rapiders/react-hooks", - "version": "1.0.1", + "version": "1.1.1", "description": "react hooks for fast development", "main": "dist/esm/index.js", "types": "dist/esm/index.d.ts", diff --git a/src/index.ts b/src/index.ts index bb2fe78..9a442bf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,6 @@ import useInput from './useInput/useInput'; import useAnimation from './useAnimation/useAnimation'; import useFocusAnimation from './useFocusAnimation/useFocusAnimation'; -import { useDragCarouselIndex } from './useDragIndexCarousel/useDragIndexCarousel'; import useDragIndexCarousel from './useDragIndexCarousel/useDragIndexCarousel'; import useCarousel from './useCarousel/useCarousel'; import useScrollRatio from './useScrollRatio'; @@ -10,7 +9,6 @@ export { useInput, useAnimation, useFocusAnimation, - useDragCarouselIndex, useDragIndexCarousel, useCarousel, useScrollRatio, diff --git a/src/stories/useDragIndexCarousel/DragCarousel.tsx b/src/stories/useDragIndexCarousel/DragCarousel.tsx index 7ccdacc..3ec4eba 100644 --- a/src/stories/useDragIndexCarousel/DragCarousel.tsx +++ b/src/stories/useDragIndexCarousel/DragCarousel.tsx @@ -8,9 +8,10 @@ export default function DragCarousel() { 'https://image.xportsnews.com/contents/images/upload/article/2023/0825/mb_1692925582785123.jpg', 'https://photo.newsen.com/news_photo/2022/08/19/202208190935355510_1.jpg', ]; - const DragCarousel = useDragIndexCarousel(); + const { CarouselWrapper, ref } = useDragIndexCarousel(images.length); return ( - ))} - + ); } diff --git a/src/useDragIndexCarousel/useDragIndexCarousel.test.tsx b/src/useDragIndexCarousel/useDragIndexCarousel.test.tsx index 73ad7c2..84df8f2 100644 --- a/src/useDragIndexCarousel/useDragIndexCarousel.test.tsx +++ b/src/useDragIndexCarousel/useDragIndexCarousel.test.tsx @@ -1,12 +1,26 @@ import { renderHook, act } from '@testing-library/react'; +import useDragIndexCarousel from './useDragIndexCarousel'; import { _useDragIndexCarousel } from './useDragIndexCarousel'; -describe('_useDragIndexCarousel', () => { +describe('useDragIndexCarousel', () => { it('초기 상태 테스트 index로 시작할 수 있다.', () => { const { result } = renderHook(() => _useDragIndexCarousel(3)); expect(result.current.index).toBe(0); - expect(result.current.style.transform).toBe('translateX(0px)'); + }); + + it('index를 next, prev로 조작할 수 있다.', () => { + const { result } = renderHook(() => _useDragIndexCarousel(2)); + + expect(result.current.index).toBe(0); + act(() => result.current.next()); + expect(result.current.index).toBe(1); + act(() => result.current.prev()); + expect(result.current.index).toBe(0); + expect(result.current.isStart).toBe(true); + act(() => result.current.next()); + act(() => result.current.next()); + expect(result.current.isEnd).toBe(true); }); it('모바일 터치이벤트 테스트: 절대값으로 minMove보다 많이, 오른쪽으로 슬라이드하면 index를 증가시킬 수 있다.', () => { diff --git a/src/useDragIndexCarousel/useDragIndexCarousel.tsx b/src/useDragIndexCarousel/useDragIndexCarousel.tsx index b05f0d5..a33b1af 100644 --- a/src/useDragIndexCarousel/useDragIndexCarousel.tsx +++ b/src/useDragIndexCarousel/useDragIndexCarousel.tsx @@ -1,16 +1,12 @@ -import React, { CSSProperties, ReactElement, cloneElement } from 'react'; +import React, { + CSSProperties, + ForwardedRef, + ReactElement, + cloneElement, + forwardRef, + useEffect, +} from 'react'; import { useRef, useState, Children, ReactNode } from 'react'; -import { createContext, useContext } from 'react'; - -export const CarouselIndex = createContext(null); - -export const useDragCarouselIndex = () => { - const result = useContext(CarouselIndex); - if (result) return result; - throw new Error( - 'Check the provider: You have to use CarouselIndex in IndexCarouselProvider children' - ); -}; export function _useDragIndexCarousel( pageLimit: number, @@ -62,104 +58,105 @@ export function _useDragIndexCarousel( setTouchStartX(0); }; - const style = { - display: 'flex', - transform: `translateX(${-index * getSliderWidth() + transX}px)`, - transitionDuration: '300ms', - transitionTimingFunction: 'ease-out', + const getNext = (index: number) => { + if (index < pageLimit) return index + 1; + return index; + }; + + const getPrev = (index: number) => { + if (index > 0) return index - 1; + return index; }; + const next = () => setIndex((prev) => getNext(prev)); + const prev = () => setIndex((prev) => getPrev(prev)); + + useEffect(() => { + if (ref.current) { + ref.current.style.display = 'flex'; + ref.current.style.transform = `translateX(${-index * getSliderWidth() + transX}px)`; + ref.current.style.transitionDuration = '300ms'; + ref.current.style.transitionTimingFunction = 'ease-out'; + } + }, [transX]); + return { - style, ref, + isEnd: index === pageLimit, + isStart: index === 0, handleTouchStart, handleTouchMove, handleMoveEnd, handleScrollStart, handleScrollMove, + next, + prev, index, }; } -function DragIndexCarousel({ - startIndex, - minMove, - style, - className, - children, -}: { - startIndex: number; - minMove?: number; - style?: CSSProperties; - className?: string; - children: ReactNode; -}) { - const pageLimit = Children.count(children) - 1; +const CarouselWrapper = forwardRef( + ( + { + children, + style, + className, + }: { children: ReactNode; style?: React.CSSProperties; className?: string }, + ref: ForwardedRef + ) => ( +
+
+ {Children.map(children, (child: ReactElement) => + cloneElement(child, { + style: { ...child.props.style, flexShrink: 0 }, + }) + )} +
+
+ ) +); + +export default function useDragIndexCarousel( + dataLength: number, + startIndex = 0, + minMove = 60 +) { const { index, ref, - style: dragStyle, handleTouchStart, handleTouchMove, handleMoveEnd, handleScrollStart, handleScrollMove, - } = _useDragIndexCarousel(pageLimit, minMove, startIndex); + next, + prev, + isEnd, + isStart, + } = _useDragIndexCarousel(dataLength - 1, minMove, startIndex); - return ( -
-
- - {children} - -
-
- ); -} - -export default function useDragIndexCarousel(startIndex = 0, minMove = 60) { - const component = ({ - children, - className, - style, - }: { - children: ReactNode; - style?: CSSProperties; - className?: string; - }) => { - return ( - - {Children.map(children, (child: ReactElement) => - cloneElement(child, { - style: { ...child.props.style, flexShrink: 0 }, - draggable: false, - }) - )} - - ); - }; + useEffect(() => { + if (ref.current) { + ref.current.addEventListener('touchstart', handleTouchStart as any); + ref.current.addEventListener('touchmove', handleTouchMove as any); + ref.current.addEventListener('touchend', handleMoveEnd as any); + ref.current.addEventListener('mousedown', handleScrollStart as any); + ref.current.addEventListener('mouseleave', handleMoveEnd as any); + ref.current.addEventListener('mousemove', handleScrollMove as any); + ref.current.addEventListener('mouseup', handleMoveEnd as any); + } + return () => { + if (ref.current) { + ref.current.removeEventListener('touchstart', handleTouchStart as any); + ref.current.removeEventListener('touchmove', handleTouchMove as any); + ref.current.removeEventListener('touchend', handleMoveEnd as any); + ref.current.removeEventListener('mousedown', handleScrollStart as any); + ref.current.removeEventListener('mouseleave', handleMoveEnd as any); + ref.current.removeEventListener('mousemove', handleScrollMove as any); + ref.current.removeEventListener('mouseup', handleMoveEnd as any); + } + }; + }); - return component; + return { CarouselWrapper, index, ref, next, prev, isStart, isEnd }; }