From 7bc5748cb9d93905b43d0780870897f1ba59f33b Mon Sep 17 00:00:00 2001 From: pstachula Date: Thu, 11 Jul 2024 19:33:21 +0200 Subject: [PATCH] feat: add docs and fix bug for cancelTarget --- README.md | 84 ++++++++++++++++--- package.json | 11 +++ src/App.tsx | 11 +-- .../components/Carousel.tsx | 55 ++++++------ .../components/Dot.tsx | 50 +++++++---- .../components/DotsGroup.tsx | 49 +++++++---- .../hooks/useCarouselReducer.ts | 2 +- 7 files changed, 182 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index 7719675..557180e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Simple Headless Carousel React + +## Featues - Extremely small package size: 2kB gzipped - 0 external dependencies - Full typescript support @@ -8,19 +10,16 @@ - Lazy image loading - Responsive support -### How to use: +## How to use: ``` - + - - - - + - + @@ -29,16 +28,81 @@ ``` +## Props documentation: + +### `` +| Prop | Type | Default | Required | +| ---------|------|-----------|---------------| +| total | number | 0 | Yes | +| autoPlayDelay | number | false | No | +| slidesVisible | number | 1 | No | +| step | number | 1 | No | +| threshold | number | 0.25 | No | +| infinite | boolean | false | No | +| disableTouch | boolean | false | No | +| lazy | boolean | true | No | +| autoPlay | boolean | false | No | + +### `` +| Prop | Type | Default | Required | +| ---------|------|-----------|---------------| +| children | ReactNode | | Yes | +| wrapperClassName | string | | No | +| carouselClassName | string | | No | + +### `` +| Prop | Type | Default | Required | +| ---------|------|-----------|---------------| +| children | ReactNode | | Yes | +| className | string | | No | + +### `` +| Prop | Type | Default | Required | +| ---------|------|-----------|---------------| +| className | string | | No | +| colorActive | string | | No | +| colorInactive | string | | No | +| index | number | | Yes | +| disabled | boolean | | No | +| onClick | Function | | No | + +### `` +| Prop | Type | Default | Required | +| ---------|------|-----------|---------------| +| className | string | | No | +| dotClassName | string | | No | +| colorActive | string | | No | +| colorInactive | string | | No | +| onClick | Function | | No | + +### `` +| Prop | Type | Default | Required | +| ---------|------|-----------|---------------| +| className | string | | No | +| title | string | | No | +| onClick | Function | | No | + +### `` +| Prop | Type | Default | Required | +| ---------|------|-----------|---------------| +| className | string | | No | +| title | string | | No | +| onClick | Function | | No | + +### `` +| Prop | Type | Default | Required | +| ---------|------|-----------|---------------| +| className | string | | No | ### TODO: - [x] Add beta implementation - [ ] Add extra option props -- [ ] Add stable version +- [x] Add stable version - [x] Setup CI - [x] DX improvments prettier/eslint -- [ ] Add docs +- [x] Add docs - [ ] Add unit tests - [ ] Add examples - [x] Publish on NPM -- [ ] Add counter element +- [x] Add counter element diff --git a/package.json b/package.json index 3e0c6d6..e1e2707 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,17 @@ "version": "0.0.11", "sideEffects": false, "description": "React simple headless carousel", + "keywords": [ + "react", + "reactjs", + "react-carousel", + "slider", + "tailwind", + "carousel", + "headless", + "simple", + "component" + ], "type": "module", "scripts": { "dev": "vite", diff --git a/src/App.tsx b/src/App.tsx index 0162848..1e02afc 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,8 +8,8 @@ import { Counter } from './lib/simple-headless-carousel/components/Counter'; function App() { return ( -
-
+
+
-
- -
+
- Couner: + Counter:
-
diff --git a/src/lib/simple-headless-carousel/components/Carousel.tsx b/src/lib/simple-headless-carousel/components/Carousel.tsx index 98bb86f..878ee69 100644 --- a/src/lib/simple-headless-carousel/components/Carousel.tsx +++ b/src/lib/simple-headless-carousel/components/Carousel.tsx @@ -5,8 +5,8 @@ import { useEffect, useCallback, useRef, - type ReactNode, useMemo, + type ReactNode, } from 'react'; import { CarouselContext } from '../context/CarouselContext'; import { getSlideClientX } from '../services/getSliderClientX'; @@ -63,7 +63,7 @@ export const Carousel = memo( const setTranslateX = useCallback( (x: number) => { animationRef.current = requestAnimationFrame(() => { - const percentX = (x * fullSize) / width / total; + const percentX = (x * fullSize) / width / total / slidesVisible; imgRef.current?.style.setProperty( 'transform', @@ -71,12 +71,12 @@ export const Carousel = memo( ); }); }, - [width, total], + [width, total, slidesVisible], ); const onMoveStart = useCallback((event: SlideEvent) => { if (cancelWrongTarget(event)) return; - // Important due to blocking onMouseMove + // Important due to blocking onMouseMoev event.preventDefault(); setIsMoving(false); @@ -89,7 +89,7 @@ export const Carousel = memo( const onMove = useCallback( (event: SlideEvent) => { - if (!imgRef.current || isMoving || cancelWrongTarget(event)) return; + if (!imgRef.current || isMoving) return; const startPosX = movePayload.current.startX; const clientX = getSlideClientX(event); @@ -109,37 +109,30 @@ export const Carousel = memo( [currentIndex, width, isMoving, setTranslateX], ); - const onMoveEnd = useCallback( - (event: SlideEvent) => { - if (cancelWrongTarget(event)) return; + const onMoveEnd = useCallback(() => { + if (animationRef?.current) { + cancelAnimationFrame(animationRef.current); + } - if (animationRef?.current) { - cancelAnimationFrame(animationRef.current); - } + let finalIndex = currentIndex; - let finalIndex = currentIndex; + if (movePayload.current.clientX !== 0) { + const steps = Math.ceil(Math.abs(movePayload.current.clientX) / width); + const newIndex = movePayload.current.moveRight + ? currentIndex - steps + : currentIndex + steps; - if (movePayload.current.clientX !== 0) { - const steps = Math.ceil( - Math.abs(movePayload.current.clientX) / width, - ); - const newIndex = movePayload.current.moveRight - ? currentIndex - steps - : currentIndex + steps; - - if (infinite && (newIndex < 0 || newIndex >= total)) { - finalIndex = newIndex > 0 ? 0 : total - 1; - } else { - finalIndex = newIndex; - } + if (infinite && (newIndex < 0 || newIndex >= total)) { + finalIndex = newIndex > 0 ? 0 : total - 1; + } else { + finalIndex = newIndex; } + } - setIsMoving(true); - setTranslateX(-width * finalIndex); - setCurrentIndex(finalIndex); - }, - [setCurrentIndex, setTranslateX, currentIndex, infinite, total, width], - ); + setIsMoving(true); + setTranslateX(-width * finalIndex); + setCurrentIndex(finalIndex); + }, [setCurrentIndex, setTranslateX, currentIndex, infinite, total, width]); const eventsMap = useMemo( (): EventMap => ({ diff --git a/src/lib/simple-headless-carousel/components/Dot.tsx b/src/lib/simple-headless-carousel/components/Dot.tsx index 4cb1b11..0471f13 100644 --- a/src/lib/simple-headless-carousel/components/Dot.tsx +++ b/src/lib/simple-headless-carousel/components/Dot.tsx @@ -5,6 +5,8 @@ import { clsx } from '../services/clsx'; type DotProps = { index: number; className?: string; + colorActive?: string; + colorInactive?: string; disabled?: boolean; onClick?: (event: React.MouseEvent) => void; }; @@ -14,25 +16,39 @@ type DotProps = { * * @param {number} index - The index of the dot. * @param {string} className - The class name for the dot. + * @param {string} colorActive - The class name for the active dot. + * @param {string} colorInactive - The class name for the inactive dot. * @param {boolean} disabled - Whether the dot is disabled. * @param {Function} onClick - The callback function to be called when the button is clicked. */ -export const Dot = memo(({ index, disabled, onClick, className }: DotProps) => { - const { dispatch, state } = useContext(CarouselContext); - const color = state.currentIndex === index ? 'bg-black' : 'bg-white'; +export const Dot = memo( + ({ + index, + disabled, + onClick, + colorActive, + colorInactive, + className, + }: DotProps) => { + const { dispatch, state } = useContext(CarouselContext); + const color = + state.currentIndex === index + ? colorActive || 'bg-black' + : colorInactive || 'bg-white'; - const handleClick = (event: React.MouseEvent) => { - dispatch({ action: 'setCurrentIndex', value: index }); - onClick?.(event); - }; + const handleClick = (event: React.MouseEvent) => { + dispatch({ action: 'setCurrentIndex', value: index }); + onClick?.(event); + }; - return ( -