commit
e5e13134c7
57 changed files with 25577 additions and 0 deletions
@ -0,0 +1,70 @@ |
||||
version: 2.1 |
||||
|
||||
defaults: &defaults |
||||
docker: |
||||
- image: circleci/node:10 |
||||
|
||||
jobs: |
||||
test: |
||||
<<: *defaults |
||||
steps: |
||||
- checkout |
||||
- restore_cache: |
||||
keys: |
||||
- v1-dependencies-{{ checksum "package.json" }} |
||||
- v1-dependencies- |
||||
- run: npm install |
||||
- run: |
||||
name: Run tests |
||||
command: npm test |
||||
- save_cache: |
||||
paths: |
||||
- node_modules |
||||
key: v1-dependencies-{{ checksum "package.json" }} |
||||
- run: npm build |
||||
- persist_to_workspace: |
||||
root: . |
||||
paths: |
||||
- README.md |
||||
- CHANGELOG.md |
||||
- LICENSE |
||||
- package.json |
||||
- package-lock.json |
||||
- .npmignore |
||||
- dist |
||||
deploy: |
||||
<<: *defaults |
||||
steps: |
||||
- attach_workspace: |
||||
at: . |
||||
- run: |
||||
name: List Workspace |
||||
command: ls |
||||
- run: |
||||
name: Authenticate with registry |
||||
command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > .npmrc |
||||
- run: |
||||
name: Publish package |
||||
command: npm publish |
||||
|
||||
workflows: |
||||
version: 2 |
||||
test-deploy: |
||||
jobs: |
||||
- test: |
||||
filters: |
||||
tags: |
||||
only: /^v.*/ |
||||
- hold: |
||||
type: approval |
||||
requires: |
||||
- test |
||||
filters: |
||||
branches: |
||||
only: master |
||||
- deploy: |
||||
requires: |
||||
- hold |
||||
filters: |
||||
branches: |
||||
only: master |
@ -0,0 +1,2 @@ |
||||
*/node_modules |
||||
*.log |
@ -0,0 +1,10 @@ |
||||
# stop .editorconfig files search on current file. |
||||
root = true |
||||
|
||||
# Unix-style newlines with a newline ending every file |
||||
[*] |
||||
charset = utf-8 |
||||
end_of_line = lf |
||||
insert_final_newline = true |
||||
indent_style = tab |
||||
trim_trailing_whitespace = true |
@ -0,0 +1,15 @@ |
||||
.DS_Store |
||||
|
||||
node_modules |
||||
|
||||
lib/core/metadata.js |
||||
lib/core/MetadataBlog.js |
||||
|
||||
website/translated_docs |
||||
website/build/ |
||||
website/yarn.lock |
||||
website/node_modules |
||||
website/i18n/* |
||||
|
||||
dist/ |
||||
coverage |
@ -0,0 +1,6 @@ |
||||
.DS_Store |
||||
website |
||||
docs |
||||
.circleci |
||||
test |
||||
coverage |
@ -0,0 +1,6 @@ |
||||
{ |
||||
"semi": true, |
||||
"trailingComma": "all", |
||||
"singleQuote": true, |
||||
"printWidth": 90 |
||||
} |
@ -0,0 +1,10 @@ |
||||
FROM node:lts |
||||
|
||||
WORKDIR /app/website |
||||
|
||||
EXPOSE 3000 35729 |
||||
COPY ./docs /app/docs |
||||
COPY ./website /app/website |
||||
RUN yarn install |
||||
|
||||
CMD ["yarn", "start"] |
@ -0,0 +1,21 @@ |
||||
MIT License |
||||
|
||||
Copyright (c) 2019 Trendyol Open Source |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
of this software and associated documentation files (the "Software"), to deal |
||||
in the Software without restriction, including without limitation the rights |
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
copies of the Software, and to permit persons to whom the Software is |
||||
furnished to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in all |
||||
copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
SOFTWARE. |
@ -0,0 +1,3 @@ |
||||
# Carousel |
||||
|
||||
<img src="docs/carousel.png" height="300px"> |
@ -0,0 +1,7 @@ |
||||
import React from 'react'; |
||||
|
||||
export const carouselItemNodes = (len: number) => { |
||||
return new Array(len).fill(0).map((_, i) => { |
||||
return <div style={{ height: 600 }}>{i + 1}</div>; |
||||
}); |
||||
}; |
@ -0,0 +1,15 @@ |
||||
import React from 'react'; |
||||
import { render, cleanup, fireEvent } from '@testing-library/react'; |
||||
import { Arrow } from '../src/components/arrow'; |
||||
|
||||
describe('<Arrow />', () => { |
||||
afterEach(cleanup); |
||||
|
||||
it('should call onClick prop when click event occurs', async () => { |
||||
const onClick = jest.fn(); |
||||
const { getByRole } = render(<Arrow onClick={onClick} />); |
||||
|
||||
fireEvent.click(getByRole('button')); |
||||
expect(onClick).toHaveBeenCalled(); |
||||
}); |
||||
}); |
@ -0,0 +1,256 @@ |
||||
import React, { MouseEvent } from 'react'; |
||||
import { render, cleanup, fireEvent } from '@testing-library/react'; |
||||
import { Carousel } from '../src/components/carousel'; |
||||
import { defaultProps } from '../src/components/carousel/defaultProps'; |
||||
import { carouselItemNodes } from './__fixtures__/nodes'; |
||||
import * as helpers from '../src/helpers'; |
||||
|
||||
describe('<Carousel />', () => { |
||||
let mockGetPageX: jest.SpyInstance< |
||||
number, |
||||
[React.TouchEvent<Element> | React.MouseEvent<Element, globalThis.MouseEvent>] |
||||
>; |
||||
|
||||
afterEach(() => { |
||||
mockGetPageX.mockRestore(); |
||||
jest.clearAllTimers(); |
||||
jest.resetAllMocks(); |
||||
cleanup(); |
||||
}); |
||||
|
||||
beforeEach(() => { |
||||
Element.prototype.getBoundingClientRect = jest.fn(() => { |
||||
return { |
||||
width: 900, |
||||
height: 600, |
||||
top: 0, |
||||
left: 0, |
||||
bottom: 0, |
||||
right: 0, |
||||
x: 0, |
||||
y: 0, |
||||
toJSON: jest.fn(), |
||||
}; |
||||
}); |
||||
jest.useFakeTimers(); |
||||
mockGetPageX = jest |
||||
.spyOn(helpers, 'getPageX') |
||||
.mockImplementation((_: MouseEvent) => 600); |
||||
}); |
||||
|
||||
it('should render right layout', async () => { |
||||
const { getByTestId } = render( |
||||
<Carousel {...defaultProps} infinite={false} children={carouselItemNodes(6)} />, |
||||
); |
||||
const carousel = getByTestId('carousel'); |
||||
|
||||
expect(carousel.firstChild); |
||||
expect(carousel.firstChild!.firstChild).toBeTruthy(); |
||||
expect(carousel.firstChild!.firstChild!.firstChild).toBeTruthy(); |
||||
expect(carousel.firstChild!.firstChild!.firstChild).toBeTruthy(); |
||||
}); |
||||
|
||||
it('should transform when pressing arrow keys', async () => { |
||||
const { getByTestId } = render( |
||||
<Carousel {...defaultProps} children={carouselItemNodes(6)} useArrowKeys={true} />, |
||||
); |
||||
const carousel = getByTestId('carousel'); |
||||
|
||||
fireEvent.click(carousel); |
||||
fireEvent.keyDown(carousel, { keyCode: 39 }); |
||||
jest.runAllTimers(); |
||||
|
||||
expect(setTimeout).toHaveBeenCalledTimes(1); |
||||
expect(setTimeout).toHaveBeenLastCalledWith( |
||||
expect.any(Function), |
||||
defaultProps.transition * 1000, |
||||
); |
||||
}); |
||||
|
||||
it("shouldn't listen keyboard event if useArrowKeys option false", async () => { |
||||
const { getByTestId } = render( |
||||
<Carousel {...defaultProps} children={carouselItemNodes(6)} useArrowKeys={false} />, |
||||
); |
||||
const carousel = getByTestId('carousel'); |
||||
|
||||
fireEvent.click(carousel); |
||||
fireEvent.keyDown(carousel, { keyCode: 39 }); |
||||
|
||||
expect(setTimeout).toHaveBeenCalledTimes(0); |
||||
}); |
||||
|
||||
it("shouldn't slide to left if carousel not infinite and shows first item", async () => { |
||||
const { getByTestId } = render( |
||||
<Carousel |
||||
{...defaultProps} |
||||
infinite={false} |
||||
useArrowKeys={true} |
||||
children={carouselItemNodes(4)} |
||||
/>, |
||||
); |
||||
const carousel = getByTestId('carousel'); |
||||
|
||||
fireEvent.click(carousel); |
||||
fireEvent.keyDown(carousel, { keyCode: 37 }); |
||||
|
||||
expect(setTimeout).toHaveBeenCalledTimes(0); |
||||
}); |
||||
|
||||
it("shouldn't do anything if press a key that different from arrow keys", async () => { |
||||
const { getByTestId } = render( |
||||
<Carousel |
||||
{...defaultProps} |
||||
infinite={false} |
||||
useArrowKeys={true} |
||||
children={carouselItemNodes(4)} |
||||
/>, |
||||
); |
||||
const carousel = getByTestId('carousel'); |
||||
|
||||
fireEvent.click(carousel); |
||||
fireEvent.keyDown(carousel, { keyCode: 317 }); |
||||
|
||||
expect(setTimeout).toHaveBeenCalledTimes(0); |
||||
}); |
||||
|
||||
it('should slide to left when click left button', async () => { |
||||
const { getByTestId } = render( |
||||
<Carousel {...defaultProps} infinite={true} children={carouselItemNodes(4)} />, |
||||
); |
||||
const carousel = getByTestId('carousel'); |
||||
const button = carousel.querySelector('button'); |
||||
|
||||
expect(button).not.toBeNull(); |
||||
|
||||
fireEvent.click(button!); |
||||
jest.runAllTimers(); |
||||
|
||||
expect(setTimeout).toHaveBeenCalledTimes(1); |
||||
expect(setTimeout).toHaveBeenLastCalledWith( |
||||
expect.any(Function), |
||||
defaultProps.transition * 1000, |
||||
); |
||||
}); |
||||
|
||||
it('should slide to right when click right button', async () => { |
||||
const { getByTestId } = render( |
||||
<Carousel {...defaultProps} infinite={true} children={carouselItemNodes(4)} />, |
||||
); |
||||
const carousel = getByTestId('carousel'); |
||||
const button = carousel.querySelectorAll('button')[1]; |
||||
|
||||
expect(button).not.toBeNull(); |
||||
|
||||
fireEvent.click(button!); |
||||
jest.runAllTimers(); |
||||
|
||||
expect(setTimeout).toHaveBeenCalledTimes(1); |
||||
expect(setTimeout).toHaveBeenLastCalledWith( |
||||
expect.any(Function), |
||||
defaultProps.transition * 1000, |
||||
); |
||||
}); |
||||
|
||||
it('should slide back when swiping is done', async () => { |
||||
const { getByTestId } = render( |
||||
<Carousel |
||||
{...defaultProps} |
||||
infinite={true} |
||||
swiping={true} |
||||
children={carouselItemNodes(12)} |
||||
/>, |
||||
); |
||||
const trackList = getByTestId('trackList'); |
||||
|
||||
mockGetPageX = jest |
||||
.spyOn(helpers, 'getPageX') |
||||
.mockImplementation((_: MouseEvent) => 600); |
||||
|
||||
fireEvent.mouseDown(trackList, { pageX: 600 }); |
||||
|
||||
mockGetPageX = jest |
||||
.spyOn(helpers, 'getPageX') |
||||
.mockImplementation((_: MouseEvent) => 390); |
||||
|
||||
fireEvent.mouseMove(trackList, { pageX: 390 }); |
||||
fireEvent.mouseUp(trackList, { pageX: 390 }); |
||||
jest.runAllTimers(); |
||||
|
||||
expect(setTimeout).toHaveBeenCalledTimes(1); |
||||
expect(setTimeout).toHaveBeenLastCalledWith( |
||||
expect.any(Function), |
||||
defaultProps.transition * 1000, |
||||
); |
||||
}); |
||||
|
||||
it('should slide back when swiping is done and drag size less than item size', async () => { |
||||
const { getByTestId } = render( |
||||
<Carousel |
||||
{...defaultProps} |
||||
infinite={true} |
||||
swiping={true} |
||||
show={5} |
||||
children={carouselItemNodes(10)} |
||||
/>, |
||||
); |
||||
const trackList = getByTestId('trackList'); |
||||
|
||||
mockGetPageX = jest |
||||
.spyOn(helpers, 'getPageX') |
||||
.mockImplementation((_: MouseEvent) => 600); |
||||
|
||||
fireEvent.mouseDown(trackList, { pageX: 600 }); |
||||
|
||||
mockGetPageX = jest |
||||
.spyOn(helpers, 'getPageX') |
||||
.mockImplementation((_: MouseEvent) => 190); |
||||
|
||||
fireEvent.mouseMove(trackList, { pageX: 190 }); |
||||
fireEvent.mouseUp(trackList, { pageX: 190 }); |
||||
jest.runAllTimers(); |
||||
|
||||
expect(setTimeout).toHaveBeenCalledTimes(1); |
||||
expect(setTimeout).toHaveBeenLastCalledWith( |
||||
expect.any(Function), |
||||
defaultProps.transition * 1000, |
||||
); |
||||
}); |
||||
|
||||
it("shouldn't rotate items if carousel is not infinite", async () => { |
||||
const { getByTestId } = render( |
||||
<Carousel {...defaultProps} infinite={false} children={carouselItemNodes(10)} />, |
||||
); |
||||
const carousel = getByTestId('carousel'); |
||||
const button = carousel.querySelector('button'); |
||||
|
||||
expect(button).not.toBeNull(); |
||||
|
||||
fireEvent.click(button!); |
||||
jest.runAllTimers(); |
||||
|
||||
expect(setTimeout).toHaveBeenCalledTimes(1); |
||||
expect(setTimeout).toHaveBeenLastCalledWith( |
||||
expect.any(Function), |
||||
defaultProps.transition * 1000, |
||||
); |
||||
}); |
||||
|
||||
it('should rotate items if carousel is infinite', async () => { |
||||
const { getByTestId } = render( |
||||
<Carousel {...defaultProps} infinite={true} children={carouselItemNodes(10)} />, |
||||
); |
||||
const carousel = getByTestId('carousel'); |
||||
const button = carousel.querySelector('button'); |
||||
|
||||
expect(button).not.toBeNull(); |
||||
|
||||
fireEvent.click(button!); |
||||
jest.runAllTimers(); |
||||
|
||||
expect(setTimeout).toHaveBeenCalledTimes(1); |
||||
expect(setTimeout).toHaveBeenLastCalledWith( |
||||
expect.any(Function), |
||||
defaultProps.transition * 1000, |
||||
); |
||||
}); |
||||
}); |
@ -0,0 +1,70 @@ |
||||
import React, { MouseEvent, TouchEvent } from 'react'; |
||||
import * as helpers from '../src/helpers'; |
||||
import { carouselItemNodes } from './__fixtures__/nodes'; |
||||
import { SlideDirection } from '../src/types/carousel'; |
||||
|
||||
describe('helpers', () => { |
||||
it('should return to head of circular items list', async () => { |
||||
const items = carouselItemNodes(2); |
||||
const circular = new helpers.Circular(items, 0); |
||||
circular.next(); |
||||
circular.next(); |
||||
|
||||
expect(circular.current()).toEqual(items[0]); |
||||
}); |
||||
|
||||
it('should add items to right and left of the array', async () => { |
||||
const items = carouselItemNodes(2); |
||||
const showingItems = carouselItemNodes(2); |
||||
const result = helpers.rotateItems( |
||||
items, |
||||
showingItems, |
||||
0, |
||||
1, |
||||
1, |
||||
SlideDirection.Right, |
||||
); |
||||
|
||||
expect(result.length).toEqual(4); |
||||
}); |
||||
|
||||
it('should get indicator of the items array', async () => { |
||||
const result = helpers.getCurrent(0, 2, 4, SlideDirection.Right); |
||||
|
||||
expect(result).toEqual(2); |
||||
}); |
||||
|
||||
it('should get indicator of the items array in circular manner', async () => { |
||||
const result = helpers.getCurrent(0, 5, 4, SlideDirection.Right); |
||||
|
||||
expect(result).toEqual(1); |
||||
}); |
||||
|
||||
it('should return 0 if event is not mouseEvent or touchEvent', async () => { |
||||
const mouseEvent = { nativeEvent: { pageX: 10 } }; |
||||
const result = helpers.getPageX(mouseEvent as React.MouseEvent); |
||||
|
||||
expect(result).toEqual(0); |
||||
}); |
||||
|
||||
it('should return pageX if event is mouseEvent', async () => { |
||||
const nativeEvent = new MouseEvent('mousedown'); |
||||
const pageX = 10; |
||||
Object.defineProperty(nativeEvent, 'pageX', { value: pageX }); |
||||
const event = { nativeEvent }; |
||||
const result = helpers.getPageX(event as MouseEvent); |
||||
|
||||
expect(result).toEqual(pageX); |
||||
}); |
||||
|
||||
it('should return pageX if event is touchEvent', async () => { |
||||
const nativeEvent = new TouchEvent('mousedown'); |
||||
const pageX = 10; |
||||
const changedTouches = [{ pageX }]; |
||||
Object.defineProperty(nativeEvent, 'changedTouches', { value: changedTouches }); |
||||
const event = { nativeEvent }; |
||||
const result = helpers.getPageX(event as TouchEvent); |
||||
|
||||
expect(result).toEqual(pageX); |
||||
}); |
||||
}); |
@ -0,0 +1,7 @@ |
||||
import { Carousel } from '../src'; |
||||
|
||||
describe('index.ts', () => { |
||||
it('should export Carousel component', async () => { |
||||
expect(Carousel).toBeTruthy(); |
||||
}); |
||||
}); |
@ -0,0 +1,165 @@ |
||||
import React, { MouseEvent } from 'react'; |
||||
import { render, cleanup, fireEvent } from '@testing-library/react'; |
||||
import { ItemProvider, ItemProviderProps } from '../src/components/item'; |
||||
import { defaultProps } from '../src/components/carousel/defaultProps'; |
||||
import * as helpers from '../src/helpers'; |
||||
import { carouselItemNodes } from './__fixtures__/nodes'; |
||||
|
||||
describe('<ItemProvider />', () => { |
||||
const defaultItemProviderProps: ItemProviderProps = { |
||||
...defaultProps, |
||||
items: carouselItemNodes(6), |
||||
widthCallBack: jest.fn(), |
||||
dragCallback: jest.fn(), |
||||
slideCallback: jest.fn(), |
||||
transition: 0, |
||||
transform: 0, |
||||
}; |
||||
let mockGetPageX: jest.SpyInstance< |
||||
number, |
||||
[React.TouchEvent<Element> | React.MouseEvent<Element, globalThis.MouseEvent>] |
||||
>; |
||||
|
||||
afterEach(() => { |
||||
mockGetPageX.mockRestore(); |
||||
jest.resetAllMocks(); |
||||
cleanup(); |
||||
}); |
||||
|
||||
beforeEach(() => { |
||||
Element.prototype.getBoundingClientRect = jest.fn(() => { |
||||
return { |
||||
width: 900, |
||||
height: 600, |
||||
top: 0, |
||||
left: 0, |
||||
bottom: 0, |
||||
right: 0, |
||||
x: 0, |
||||
y: 0, |
||||
toJSON: jest.fn(), |
||||
}; |
||||
}); |
||||
mockGetPageX = jest |
||||
.spyOn(helpers, 'getPageX') |
||||
.mockImplementation((_: MouseEvent) => 600); |
||||
}); |
||||
|
||||
it('should call dragCallback when user swipes less than item width', async () => { |
||||
const { getByTestId } = render( |
||||
<ItemProvider {...defaultItemProviderProps} show={3} swiping={true} />, |
||||
); |
||||
const trackList = getByTestId('trackList'); |
||||
mockGetPageX = jest |
||||
.spyOn(helpers, 'getPageX') |
||||
.mockImplementation((_: MouseEvent) => 600); |
||||
fireEvent.mouseDown(trackList, { pageX: 600 }); |
||||
|
||||
mockGetPageX = jest |
||||
.spyOn(helpers, 'getPageX') |
||||
.mockImplementation((_: MouseEvent) => 390); |
||||
fireEvent.mouseMove(trackList, { pageX: 390 }); |
||||
|
||||
fireEvent.mouseUp(trackList, { pageX: 390 }); |
||||
|
||||
expect(defaultItemProviderProps.dragCallback).toHaveBeenCalledTimes(1); |
||||
}); |
||||
|
||||
it('should call slideCallback when user swipes bigger than item width', async () => { |
||||
const { getByTestId } = render( |
||||
<ItemProvider {...defaultItemProviderProps} show={3} swiping={true} />, |
||||
); |
||||
const trackList = getByTestId('trackList'); |
||||
mockGetPageX = jest |
||||
.spyOn(helpers, 'getPageX') |
||||
.mockImplementation((_: MouseEvent) => 600); |
||||
fireEvent.mouseDown(trackList, { pageX: 600 }); |
||||
|
||||
mockGetPageX = jest |
||||
.spyOn(helpers, 'getPageX') |
||||
.mockImplementation((_: MouseEvent) => 290); |
||||
fireEvent.mouseMove(trackList, { pageX: 290 }); |
||||
|
||||
fireEvent.mouseUp(trackList, { pageX: 290 }); |
||||
|
||||
expect(defaultItemProviderProps.slideCallback).toHaveBeenCalledTimes(1); |
||||
}); |
||||
|
||||
it('should slide to left if drag value bigger than zero', async () => { |
||||
const { getByTestId } = render( |
||||
<ItemProvider {...defaultItemProviderProps} show={3} swiping={true} />, |
||||
); |
||||
const trackList = getByTestId('trackList'); |
||||
mockGetPageX = jest |
||||
.spyOn(helpers, 'getPageX') |
||||
.mockImplementation((_: MouseEvent) => 600); |
||||
fireEvent.mouseDown(trackList, { pageX: 600 }); |
||||
|
||||
mockGetPageX = jest |
||||
.spyOn(helpers, 'getPageX') |
||||
.mockImplementation((_: MouseEvent) => 910); |
||||
fireEvent.mouseMove(trackList, { pageX: 910 }); |
||||
|
||||
fireEvent.mouseUp(trackList, { pageX: 910 }); |
||||
|
||||
expect(defaultItemProviderProps.slideCallback).toHaveBeenCalledTimes(1); |
||||
}); |
||||
|
||||
it("should't call neither drag or slide callbacks when not swiping", async () => { |
||||
const { getByTestId } = render( |
||||
<ItemProvider {...defaultItemProviderProps} show={3} swiping={true} />, |
||||
); |
||||
const trackList = getByTestId('trackList'); |
||||
|
||||
mockGetPageX = jest |
||||
.spyOn(helpers, 'getPageX') |
||||
.mockImplementation((_: MouseEvent) => 290); |
||||
fireEvent.mouseMove(trackList, { pageX: 290 }); |
||||
|
||||
expect(defaultItemProviderProps.slideCallback).toHaveBeenCalledTimes(0); |
||||
expect(defaultItemProviderProps.dragCallback).toHaveBeenCalledTimes(0); |
||||
}); |
||||
|
||||
it("should't call neither drag or slide callbacks when mouse leave and not swiping", async () => { |
||||
const { getByTestId } = render( |
||||
<ItemProvider {...defaultItemProviderProps} show={3} swiping={true} />, |
||||
); |
||||
const trackList = getByTestId('trackList'); |
||||
|
||||
mockGetPageX = jest |
||||
.spyOn(helpers, 'getPageX') |
||||
.mockImplementation((_: MouseEvent) => 290); |
||||
fireEvent.mouseUp(trackList, { pageX: 290 }); |
||||
|
||||
expect(defaultItemProviderProps.slideCallback).toHaveBeenCalledTimes(0); |
||||
expect(defaultItemProviderProps.dragCallback).toHaveBeenCalledTimes(0); |
||||
}); |
||||
|
||||
it("should't listen mouse or touch event when swipiwing option false", async () => { |
||||
const { getByTestId } = render( |
||||
<ItemProvider {...defaultItemProviderProps} swiping={false} />, |
||||
); |
||||
const trackList = getByTestId('trackList'); |
||||
mockGetPageX = jest |
||||
.spyOn(helpers, 'getPageX') |
||||
.mockImplementation((_: MouseEvent) => 600); |
||||
fireEvent.mouseDown(trackList, { pageX: 600 }); |
||||
|
||||
mockGetPageX = jest |
||||
.spyOn(helpers, 'getPageX') |
||||
.mockImplementation((_: MouseEvent) => 390); |
||||
fireEvent.mouseMove(trackList, { pageX: 390 }); |
||||
|
||||
fireEvent.mouseUp(trackList, { pageX: 390 }); |
||||
|
||||
expect(defaultItemProviderProps.dragCallback).toHaveBeenCalledTimes(0); |
||||
expect(defaultItemProviderProps.slideCallback).toHaveBeenCalledTimes(0); |
||||
}); |
||||
|
||||
it("shouldn't call useWindowWidthCahnge hook when responsive option false", async () => { |
||||
render(<ItemProvider {...defaultItemProviderProps} responsive={true} />); |
||||
|
||||
Object.defineProperty(window, 'innerWidth', { value: 5 }); |
||||
fireEvent(window, new Event('resize')); |
||||
}); |
||||
}); |
@ -0,0 +1,18 @@ |
||||
version: '3' |
||||
|
||||
services: |
||||
docusaurus: |
||||
build: . |
||||
ports: |
||||
- 3000:3000 |
||||
- 35729:35729 |
||||
volumes: |
||||
- ./docs:/app/docs |
||||
- ./website/blog:/app/website/blog |
||||
- ./website/core:/app/website/core |
||||
- ./website/i18n:/app/website/i18n |
||||
- ./website/pages:/app/website/pages |
||||
- ./website/static:/app/website/static |
||||
- ./website/sidebars.json:/app/website/sidebars.json |
||||
- ./website/siteConfig.js:/app/website/siteConfig.js |
||||
working_dir: /app/website |
@ -0,0 +1,21 @@ |
||||
module.exports = { |
||||
coverageDirectory: 'coverage', |
||||
collectCoverage: true, |
||||
collectCoverageFrom: ['src/**/*.{ts,tsx}'], |
||||
coverageThreshold: { |
||||
global: { |
||||
branches: 100, |
||||
functions: 100, |
||||
lines: 100, |
||||
statements: 100, |
||||
}, |
||||
}, |
||||
testPathIgnorePatterns: ['<rootDir>/__tests__/__fixtures__/'], |
||||
transform: { |
||||
'^.+\\.tsx?$': 'ts-jest', |
||||
'^.+\\.css$': 'jest-transform-css', |
||||
}, |
||||
setupFilesAfterEnv: ['@testing-library/jest-dom/extend-expect'], |
||||
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$', |
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], |
||||
}; |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,84 @@ |
||||
{ |
||||
"name": "@trendyol/react-carousel", |
||||
"version": "1.0.0", |
||||
"description": "Lightweight carousel component for react", |
||||
"main": "dist/cjs/index.js", |
||||
"module": "dist/es/index.js", |
||||
"jsnext:main": "dist/es/index.js", |
||||
"types": "dist/types/index.d.ts", |
||||
"homepage": "https://trendyol.github.io/react-carousel", |
||||
"repository": "github:Trendyol/carousel", |
||||
"bugs": "https://github.com/Trendyol/react-carousel/issues", |
||||
"scripts": { |
||||
"build": "rollup -c --environment BUILD:production", |
||||
"dev": "rollup -c -w --environment BUILD:development", |
||||
"publish": "npm publish", |
||||
"fmt": "prettier --write 'src/**/*.{ts,tsx,css}' *.{js,json,md} && npm run lint:fix", |
||||
"lint": "tslint -t verbose -c tslint.json 'src/**/*.{ts,tsx}'", |
||||
"lint:fix": "tslint -t verbose -c tslint.json --fix 'src/**/*.{ts,tsx}'", |
||||
"lint:staged": "pretty-quick --staged && lint-staged", |
||||
"test": "jest", |
||||
"security": "npm audit" |
||||
}, |
||||
"lint-staged": { |
||||
"*.{ts,tsx}": "npm run fmt" |
||||
}, |
||||
"husky": { |
||||
"hooks": { |
||||
"pre-commit": "npm lint:staged", |
||||
"pre-push": "npm test" |
||||
} |
||||
}, |
||||
"keywords": [ |
||||
"carousel", |
||||
"react", |
||||
"slider" |
||||
], |
||||
"authors": [ |
||||
"Hasan Genc <hasan.genc@trendyol.com> (https://github.com/hasangenc0)" |
||||
], |
||||
"license": "MIT", |
||||
"peerDependencies": { |
||||
"react": "^16.13.1" |
||||
}, |
||||
"devDependencies": { |
||||
"@rollup/plugin-replace": "^2.3.1", |
||||
"@testing-library/jest-dom": "^5.3.0", |
||||
"@testing-library/react": "^10.0.2", |
||||
"@types/jest": "^25.2.1", |
||||
"@types/react": "^16.9.26", |
||||
"autoprefixer": "^9.7.5", |
||||
"eslint": "^6.8.0", |
||||
"husky": "^4.2.3", |
||||
"jest": "^25.2.7", |
||||
"jest-transform-css": "^2.0.0", |
||||
"lint-staged": "^10.0.9", |
||||
"prettier": "^2.0.2", |
||||
"pretty-quick": "^2.0.1", |
||||
"react": "^16.13.1", |
||||
"react-dom": "^16.13.1", |
||||
"rollup": "^2.3.3", |
||||
"rollup-plugin-peer-deps-external": "^2.2.2", |
||||
"rollup-plugin-postcss-modules": "^2.0.1", |
||||
"rollup-plugin-terser": "^5.3.0", |
||||
"rollup-plugin-typescript2": "^0.27.0", |
||||
"ts-jest": "^25.3.1", |
||||
"tslint": "^6.1.0", |
||||
"tslint-plugin-prettier": "^2.3.0", |
||||
"tslint-react-hooks": "^2.2.2", |
||||
"typescript": "^3.8.3" |
||||
}, |
||||
"sideEffects": false, |
||||
"browserslist": { |
||||
"production": [ |
||||
">0.2%", |
||||
"not dead", |
||||
"not op_mini all" |
||||
], |
||||
"development": [ |
||||
"last 1 chrome version", |
||||
"last 1 firefox version", |
||||
"last 1 safari version" |
||||
] |
||||
} |
||||
} |
@ -0,0 +1,40 @@ |
||||
import peerDepsExternal from 'rollup-plugin-peer-deps-external'; |
||||
import postcss from 'rollup-plugin-postcss-modules'; |
||||
import pkg from './package.json'; |
||||
import typescript from 'rollup-plugin-typescript2'; |
||||
import autoprefixer from 'autoprefixer'; |
||||
import { terser } from 'rollup-plugin-terser'; |
||||
import replace from '@rollup/plugin-replace'; |
||||
|
||||
const isProduction = process.env.BUILD === 'production'; |
||||
|
||||
export default { |
||||
input: 'src/index.ts', |
||||
output: [ |
||||
{ |
||||
file: pkg.main, |
||||
format: 'cjs', |
||||
sourcemap: !isProduction, |
||||
}, |
||||
{ |
||||
file: pkg.module, |
||||
format: 'es', |
||||
sourcemap: !isProduction, |
||||
}, |
||||
], |
||||
plugins: [ |
||||
peerDepsExternal(), |
||||
postcss({ |
||||
extract: false, |
||||
modules: true, |
||||
plugins: [autoprefixer()], |
||||
writeDefinitions: true, |
||||
}), |
||||
typescript({ useTsconfigDeclarationDir: true }), |
||||
isProduction && replace({ 'data-testid': '' }), |
||||
isProduction && terser(), |
||||
], |
||||
external: { |
||||
react: 'react', |
||||
}, |
||||
}; |
@ -0,0 +1,9 @@ |
||||
import React, { FunctionComponent } from 'react'; |
||||
import styles from '../../styles/styles.module.css'; |
||||
|
||||
export const Arrow: FunctionComponent<ArrowProps> = (props: ArrowProps) => ( |
||||
<button className={styles.carouselArrow} onClick={props.onClick}></button> |
||||
); |
||||
export interface ArrowProps { |
||||
onClick: (...args: any) => any; |
||||
} |
@ -0,0 +1,15 @@ |
||||
import { CarouselProps } from '.'; |
||||
|
||||
export const defaultProps: Required<CarouselProps> = { |
||||
children: [], |
||||
show: 1, |
||||
slide: 1, |
||||
transition: 0.5, |
||||
swiping: false, |
||||
swipeOn: 1, |
||||
responsive: false, |
||||
infinite: true, |
||||
className: '', |
||||
useArrowKeys: false, |
||||
a11y: {}, |
||||
}; |
@ -0,0 +1,152 @@ |
||||
import React, { useState, FunctionComponent, KeyboardEvent } from 'react'; |
||||
import { Arrow } from '../arrow'; |
||||
import { ItemProvider } from '../item'; |
||||
import { |
||||
rotateItems, |
||||
getTransformAmount, |
||||
getCurrent, |
||||
initItems, |
||||
getShowArrow, |
||||
cleanItems, |
||||
} from '../../helpers'; |
||||
import { SlideDirection, Item, ArrowKeys } from '../../types/carousel'; |
||||
import { defaultProps } from './defaultProps'; |
||||
import styles from '../../styles/styles.module.css'; |
||||
|
||||
export const Carousel: FunctionComponent<CarouselProps> = (userProps: CarouselProps) => { |
||||
const props: Required<CarouselProps> = { ...defaultProps, ...userProps }; |
||||
const [items, setItems] = useState( |
||||
initItems(props.children, props.slide, props.infinite), |
||||
); |
||||
const [width, setWidth] = useState(0); |
||||
const [animation, setAnimation] = useState({ |
||||
transform: 0, |
||||
transition: 0, |
||||
isSliding: false, |
||||
}); |
||||
const [current, setCurrent] = useState(0); |
||||
const [showArrow, setShowArrow] = useState( |
||||
getShowArrow(props.children.length, props.show, props.infinite, current), |
||||
); |
||||
|
||||
const slide = (direction: SlideDirection, slide: number): void => { |
||||
if ( |
||||
animation.isSliding || |
||||
(direction === SlideDirection.Right && !showArrow.right) || |
||||
(direction === SlideDirection.Left && !showArrow.left) |
||||
) { |
||||
return; |
||||
} |
||||
|
||||
const next = getCurrent(current, slide, props.children.length, direction); |
||||
const rotated = props.infinite |
||||
? rotateItems(props.children, items, next, props.show, slide, direction) |
||||
: items; |
||||
if (props.infinite && direction === SlideDirection.Right) { |
||||
setItems(rotated); |
||||
} |
||||
setAnimation({ |
||||
transform: animation.transform + getTransformAmount(width, slide, direction), |
||||
transition: props.transition, |
||||
isSliding: true, |
||||
}); |
||||
setCurrent(next); |
||||
setShowArrow(getShowArrow(props.children.length, props.show, props.infinite, next)); |
||||
setTimeout(() => { |
||||
if (props.infinite) { |
||||
setItems(cleanItems(rotated, slide, direction)); |
||||
} |
||||
setAnimation({ |
||||
transform: props.infinite |
||||
? getTransformAmount(width, slide, SlideDirection.Right) |
||||
: animation.transform + getTransformAmount(width, slide, direction), |
||||
transition: 0, |
||||
isSliding: false, |
||||
}); |
||||
}, props.transition * 1_0_0_0); |
||||
}; |
||||
|
||||
const widthCallBack = (calculatedWidth: number, slide: number) => { |
||||
setWidth(calculatedWidth); |
||||
setAnimation({ |
||||
transform: props.infinite |
||||
? getTransformAmount(calculatedWidth, slide, SlideDirection.Right) |
||||
: 0, |
||||
transition: 0, |
||||
isSliding: false, |
||||
}); |
||||
}; |
||||
|
||||
const dragCallback = (translateX: number) => { |
||||
setAnimation({ |
||||
transform: translateX, |
||||
transition: props.transition, |
||||
isSliding: false, |
||||
}); |
||||
setTimeout( |
||||
() => setAnimation({ ...animation, transition: 0 }), |
||||
props.transition * 1_0_0_0, |
||||
); |
||||
}; |
||||
|
||||
const slideCallback = (direction: SlideDirection) => { |
||||
slide(direction, props.slide); |
||||
}; |
||||
|
||||
const handleOnKeyDown = (e: KeyboardEvent) => { |
||||
if (e.keyCode === ArrowKeys.Left) { |
||||
slide(SlideDirection.Left, props.slide); |
||||
} else if (e.keyCode === ArrowKeys.Right) { |
||||
slide(SlideDirection.Right, props.slide); |
||||
} |
||||
}; |
||||
|
||||
return ( |
||||
<div |
||||
{...props.a11y} |
||||
data-testid="carousel" |
||||
tabIndex={0} |
||||
{...(props.useArrowKeys ? { onKeyDown: handleOnKeyDown } : {})} |
||||
className={`${styles.carouselBase} ${props.className}`} |
||||
> |
||||
{showArrow.left && ( |
||||
<Arrow onClick={() => slide(SlideDirection.Left, props.slide)} /> |
||||
)} |
||||
<ItemProvider |
||||
{...props} |
||||
transition={animation.transition} |
||||
items={items} |
||||
transform={animation.transform} |
||||
slideCallback={slideCallback} |
||||
dragCallback={dragCallback} |
||||
widthCallBack={widthCallBack} |
||||
/> |
||||
{showArrow.right && ( |
||||
<Arrow onClick={() => slide(SlideDirection.Right, props.slide)} /> |
||||
)} |
||||
</div> |
||||
); |
||||
}; |
||||
|
||||
export interface CarouselProps { |
||||
children: Item[]; |
||||
show: number; |
||||
slide: number; |
||||
transition?: number; |
||||
swiping?: boolean; |
||||
swipeOn?: number; |
||||
responsive?: boolean; |
||||
infinite?: boolean; |
||||
className?: string; |
||||
useArrowKeys?: boolean; |
||||
a11y?: { [key: string]: string }; |
||||
} |
||||
|
||||
export interface CarouselState { |
||||
items: Item[]; |
||||
width: number; |
||||
transform: number; |
||||
transition: number; |
||||
isSliding: boolean; |
||||
current: number; |
||||
} |
@ -0,0 +1,133 @@ |
||||
import React, { |
||||
FunctionComponent, |
||||
useCallback, |
||||
useState, |
||||
MouseEvent, |
||||
TouchEvent, |
||||
} from 'react'; |
||||
import { Item, SlideDirection } from '../../types/carousel'; |
||||
import { getPageX } from '../../helpers'; |
||||
import { useWindowWidthChange } from '../../hooks'; |
||||
import styles from '../../styles/styles.module.css'; |
||||
|
||||
export const ItemProviderBase: FunctionComponent<ItemProviderProps> = ( |
||||
props: ItemProviderProps, |
||||
) => { |
||||
const [width, setWidth] = useState(200); |
||||
const ref = useCallback( |
||||
(node) => { |
||||
if (node !== null) { |
||||
const calculated = node.getBoundingClientRect().width / props.show; |
||||
setWidth(calculated); |
||||
props.widthCallBack(calculated, props.slide); |
||||
} |
||||
}, |
||||
[width], |
||||
); |
||||
|
||||
// tslint:disable-next-line: no-unused-expression
|
||||
props.responsive && |
||||
useWindowWidthChange((change: number) => { |
||||
setWidth(width - change); |
||||
}); |
||||
const [drag, setDrag] = useState({ |
||||
initial: props.transform, |
||||
start: 0, |
||||
isDown: false, |
||||
drag: 0, |
||||
finished: true, |
||||
pointers: true, |
||||
}); |
||||
const handleDragStart = (e: MouseEvent | TouchEvent) => { |
||||
e.persist(); |
||||
setDrag({ |
||||
...drag, |
||||
isDown: true, |
||||
start: getPageX(e), |
||||
initial: props.transform, |
||||
finished: false, |
||||
}); |
||||
}; |
||||
const handleDragFinish = (e: MouseEvent | TouchEvent) => { |
||||
e.persist(); |
||||
if (drag.finished) { |
||||
return; |
||||
} |
||||
if (Math.abs(drag.drag) < width * props.swipeOn) { |
||||
props.dragCallback(props.transform); |
||||
return setDrag({ |
||||
initial: props.transform, |
||||
start: 0, |
||||
isDown: false, |
||||
drag: 0, |
||||
finished: true, |
||||
pointers: true, |
||||
}); |
||||
} |
||||
|
||||
props.slideCallback(drag.drag > 0 ? SlideDirection.Right : SlideDirection.Left); |
||||
setDrag({ ...drag, drag: 0, isDown: false, finished: true, pointers: true }); |
||||
return; |
||||
}; |
||||
const handleDragMove = (e: MouseEvent | TouchEvent) => { |
||||
e.persist(); |
||||
if (!drag.isDown) { |
||||
return; |
||||
} |
||||
const pos = getPageX(e); |
||||
setDrag({ ...drag, drag: drag.start - pos, pointers: false }); |
||||
}; |
||||
const swipeProps = props.swiping |
||||
? { |
||||
onTouchCancel: handleDragFinish, |
||||
onTouchEnd: handleDragFinish, |
||||
onTouchMove: handleDragMove, |
||||
onTouchStart: handleDragStart, |
||||
onMouseDown: handleDragStart, |
||||
onMouseLeave: handleDragFinish, |
||||
onMouseUp: handleDragFinish, |
||||
onMouseMove: handleDragMove, |
||||
} |
||||
: {}; |
||||
|
||||
return ( |
||||
<div ref={ref} className={styles.itemProvider}> |
||||
<div |
||||
data-testid="trackList" |
||||
{...swipeProps} |
||||
className={styles.itemTracker} |
||||
style={{ |
||||
transform: `translateX(${props.transform - drag.drag}px)`, |
||||
transition: `transform ${props.transition}s ease 0s`, |
||||
width: width * props.items.length, |
||||
}} |
||||
> |
||||
{props.items.map((item, i) => ( |
||||
<div |
||||
key={i} |
||||
style={{ width, pointerEvents: drag.pointers ? 'all' : 'none' }} |
||||
className={styles.itemContainer} |
||||
> |
||||
{item} |
||||
</div> |
||||
))} |
||||
</div> |
||||
</div> |
||||
); |
||||
}; |
||||
|
||||
export const ItemProvider = React.memo(ItemProviderBase); |
||||
export interface ItemProviderProps { |
||||
items: Item[]; |
||||
show: number; |
||||
slide: number; |
||||
widthCallBack: (width: number, slide: number) => void; |
||||
dragCallback: (transform: number) => void; |
||||
slideCallback: (direction: SlideDirection) => void; |
||||
transition: number; |
||||
transform: number; |
||||
swiping: boolean; |
||||
swipeOn: number; |
||||
responsive: boolean; |
||||
infinite: boolean; |
||||
} |
@ -0,0 +1,135 @@ |
||||
import { MouseEvent, TouchEvent } from 'react'; |
||||
import { SlideDirection, Item } from '../types/carousel'; |
||||
|
||||
export class Circular<T> { |
||||
constructor(private arr: T[], private currentIndex: number) {} |
||||
|
||||
next(): T { |
||||
const i = this.currentIndex; |
||||
const arr = this.arr; |
||||
this.currentIndex = i < arr.length - 1 ? i + 1 : 0; |
||||
return this.current(); |
||||
} |
||||
|
||||
prev(): T { |
||||
const i = this.currentIndex; |
||||
const arr = this.arr; |
||||
this.currentIndex = i > 0 ? i - 1 : arr.length - 1; |
||||
return this.current(); |
||||
} |
||||
|
||||
current(): T { |
||||
return this.arr[this.currentIndex]; |
||||
} |
||||
} |
||||
|
||||
export const rotateItems = ( |
||||
items: any[], |
||||
showingItems: any[], |
||||
start: number, |
||||
show: number, |
||||
slide: number, |
||||
direction: SlideDirection, |
||||
): any[] => { |
||||
const circular = new Circular(items, start); |
||||
const newItems: any[] = Array.from(showingItems); |
||||
|
||||
switch (+direction) { |
||||
case SlideDirection.Left: |
||||
for (let i = slide; i >= 0; i--) { |
||||
if (slide - i < 0 || !newItems[i - slide]) { |
||||
newItems.unshift(circular.current()); |
||||
} |
||||
circular.prev(); |
||||
} |
||||
break; |
||||
case SlideDirection.Right: |
||||
for (let i = 0; i < show + slide; i++) { |
||||
if (!newItems[2 * slide + i]) { |
||||
newItems.push(circular.current()); |
||||
} |
||||
circular.next(); |
||||
} |
||||
break; |
||||
} |
||||
|
||||
return newItems; |
||||
}; |
||||
|
||||
export const getTransformAmount = ( |
||||
width: number, |
||||
slideCount: number, |
||||
direction: SlideDirection, |
||||
): number => { |
||||
return direction * width * slideCount; |
||||
}; |
||||
|
||||
export const getCurrent = ( |
||||
current: number, |
||||
slide: number, |
||||
length: number, |
||||
direction: SlideDirection, |
||||
) => { |
||||
const slideTo = current - direction * slide; |
||||
if (slideTo < 0) { |
||||
return length + slideTo; |
||||
} else if (length <= slideTo) { |
||||
return slideTo - length; |
||||
} |
||||
|
||||
return slideTo; |
||||
}; |
||||
|
||||
export const getShowArrow = ( |
||||
items: number, |
||||
show: number, |
||||
infinite: boolean, |
||||
current: number, |
||||
): { left: boolean; right: boolean } => { |
||||
const isItemsMore = items > show; |
||||
if (infinite) { |
||||
return { |
||||
left: isItemsMore, |
||||
right: isItemsMore, |
||||
}; |
||||
} |
||||
|
||||
return { |
||||
left: isItemsMore && current !== 0, |
||||
right: isItemsMore && current + show < items, |
||||
}; |
||||
}; |
||||
|
||||
export const cleanItems = ( |
||||
showingItems: any[], |
||||
slide: number, |
||||
direction: SlideDirection, |
||||
): any[] => { |
||||
if (direction === SlideDirection.Left) { |
||||
return showingItems.slice(0, -1 * slide); |
||||
} |
||||
return showingItems.slice(slide); |
||||
}; |
||||
|
||||
export const initItems = (items: Item[], slide: number, infinite: boolean): Item[] => { |
||||
if (!infinite) { |
||||
return items; |
||||
} |
||||
|
||||
const newArray = Array.from(items); |
||||
const circular = new Circular(items, 0); |
||||
for (let i = 0; i < slide; i++) { |
||||
newArray.unshift(circular.prev()); |
||||
} |
||||
|
||||
return newArray; |
||||
}; |
||||
|
||||
export function getPageX(e: TouchEvent | MouseEvent): number { |
||||
if (e.nativeEvent instanceof MouseEvent) { |
||||
return e.nativeEvent.pageX; |
||||
} else if (e.nativeEvent instanceof TouchEvent) { |
||||
return e.nativeEvent.changedTouches[0].pageX; |
||||
} |
||||
return 0; |
||||
} |
@ -0,0 +1,15 @@ |
||||
import { useLayoutEffect, useState } from 'react'; |
||||
|
||||
export const useWindowWidthChange = (callBack: (changed: number) => any) => { |
||||
const [windowWidth, setWindowWidth] = useState(window.innerWidth); |
||||
useLayoutEffect(() => { |
||||
const update = () => { |
||||
const changed = windowWidth - window.innerWidth; |
||||
setWindowWidth(window.innerWidth); |
||||
callBack(changed); |
||||
}; |
||||
window.addEventListener('resize', update); |
||||
return () => window.removeEventListener('resize', update); |
||||
}, []); |
||||
return; |
||||
}; |
@ -0,0 +1,3 @@ |
||||
import { Carousel } from './components/carousel'; |
||||
|
||||
export { Carousel }; |
@ -0,0 +1,26 @@ |
||||
.carousel-base { |
||||
width: 100%; |
||||
box-sizing: border-box; |
||||
display: flex; |
||||
outline: none; |
||||
} |
||||
|
||||
.item-provider { |
||||
overflow: hidden; |
||||
width: 100%; |
||||
cursor: pointer; |
||||
} |
||||
|
||||
.item-container img { |
||||
user-select: none; |
||||
-webkit-user-drag: none; |
||||
} |
||||
|
||||
.item-tracker { |
||||
height: 100%; |
||||
display: flex; |
||||
} |
||||
|
||||
.carousel-arrow { |
||||
z-index: 1; |
||||
} |
@ -0,0 +1,19 @@ |
||||
export const carouselBase: string; |
||||
export const itemProvider: string; |
||||
export const itemContainer: string; |
||||
export const itemTracker: string; |
||||
export const carouselArrow: string; |
||||
interface Namespace { |
||||
carouselBase: string; |
||||
'carousel-base': string; |
||||
itemProvider: string; |
||||
'item-provider': string; |
||||
itemContainer: string; |
||||
'item-container': string; |
||||
itemTracker: string; |
||||
'item-tracker': string; |
||||
carouselArrow: string; |
||||
'carousel-arrow': string; |
||||
} |
||||
declare const stylesModule: Namespace; |
||||
export default stylesModule; |
@ -0,0 +1,13 @@ |
||||
import { ReactElement } from 'react'; |
||||
|
||||
export enum SlideDirection { |
||||
Right = -1, |
||||
Left = 1, |
||||
} |
||||
|
||||
export const enum ArrowKeys { |
||||
Right = 39, |
||||
Left = 37, |
||||
} |
||||
|
||||
export type Item = ReactElement; |
@ -0,0 +1,26 @@ |
||||
{ |
||||
"compilerOptions": { |
||||
"outDir": "./dist", |
||||
"module": "esnext", |
||||
"target": "es6", |
||||
"lib": ["es6", "dom", "es2016", "es2017"], |
||||
"sourceMap": true, |
||||
"allowJs": false, |
||||
"jsx": "react", |
||||
"declaration": true, |
||||
"declarationDir": "./dist/types", |
||||
"moduleResolution": "node", |
||||
"forceConsistentCasingInFileNames": true, |
||||
"noImplicitReturns": true, |
||||
"noImplicitThis": true, |
||||
"noImplicitAny": true, |
||||
"strictNullChecks": true, |
||||
"suppressImplicitAnyIndexErrors": true, |
||||
"allowSyntheticDefaultImports": true, |
||||
"noUnusedLocals": true, |
||||
"noUnusedParameters": true, |
||||
"esModuleInterop": true |
||||
}, |
||||
"include": ["src"], |
||||
"exclude": ["node_modules", "dist"] |
||||
} |
@ -0,0 +1,24 @@ |
||||
{ |
||||
"defaultSeverity": "error", |
||||
"extends": ["tslint:recommended", "tslint-react-hooks"], |
||||
"jsRules": { |
||||
"trailing-comma": false |
||||
}, |
||||
"rules": { |
||||
"interface-name": [true, "never-prefix"], |
||||
"no-console": false, |
||||
"no-shadowed-variable": false, |
||||
"arrow-parens": false, |
||||
"trailing-comma": false, |
||||
"member-access": [true, "no-public"], |
||||
"callable-types": false, |
||||
"no-empty-interface": false, |
||||
"only-arrow-functions": false, |
||||
"variable-name": [true, "allow-leading-underscore", "allow-pascal-case"], |
||||
"object-literal-sort-keys": false |
||||
}, |
||||
"rulesDirectory": [], |
||||
"linterOptions": { |
||||
"exclude": ["./**/node_modules/**"] |
||||
} |
||||
} |
@ -0,0 +1,20 @@ |
||||
# Dependencies |
||||
/node_modules |
||||
|
||||
# Production |
||||
/build |
||||
|
||||
# Generated files |
||||
.docusaurus |
||||
.cache-loader |
||||
|
||||
# Misc |
||||
.DS_Store |
||||
.env.local |
||||
.env.development.local |
||||
.env.test.local |
||||
.env.production.local |
||||
|
||||
npm-debug.log* |
||||
yarn-debug.log* |
||||
yarn-error.log* |
@ -0,0 +1,33 @@ |
||||
# Website |
||||
|
||||
This website is built using [Docusaurus 2](https://v2.docusaurus.io/), a modern static website generator. |
||||
|
||||
### Installation |
||||
|
||||
``` |
||||
$ yarn |
||||
``` |
||||
|
||||
### Local Development |
||||
|
||||
``` |
||||
$ yarn start |
||||
``` |
||||
|
||||
This command starts a local development server and open up a browser window. Most changes are reflected live without having to restart the server. |
||||
|
||||
### Build |
||||
|
||||
``` |
||||
$ yarn build |
||||
``` |
||||
|
||||
This command generates static content into the `build` directory and can be served using any static contents hosting service. |
||||
|
||||
### Deployment |
||||
|
||||
``` |
||||
$ GIT_USER=<Your GitHub username> USE_SSH=true yarn deploy |
||||
``` |
||||
|
||||
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. |
@ -0,0 +1,11 @@ |
||||
--- |
||||
id: hola |
||||
title: Hola |
||||
author: Gao Wei |
||||
author_title: Docusaurus Core Team |
||||
author_url: https://github.com/wgao19 |
||||
author_image_url: https://avatars1.githubusercontent.com/u/2055384?v=4 |
||||
tags: [hola, docusaurus] |
||||
--- |
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet |
@ -0,0 +1,17 @@ |
||||
--- |
||||
id: hello-world |
||||
title: Hello |
||||
author: Endilie Yacop Sucipto |
||||
author_title: Maintainer of Docusaurus |
||||
author_url: https://github.com/endiliey |
||||
author_image_url: https://avatars1.githubusercontent.com/u/17883920?s=460&v=4 |
||||
tags: [hello, docusaurus] |
||||
--- |
||||
|
||||
Welcome to this blog. This blog is created with [**Docusaurus 2 alpha**](https://v2.docusaurus.io/). |
||||
|
||||
<!--truncate--> |
||||
|
||||
This is a test post. |
||||
|
||||
A whole bunch of other information. |
@ -0,0 +1,13 @@ |
||||
--- |
||||
id: welcome |
||||
title: Welcome |
||||
author: Yangshun Tay |
||||
author_title: Front End Engineer @ Facebook |
||||
author_url: https://github.com/yangshun |
||||
author_image_url: https://avatars0.githubusercontent.com/u/1315101?s=400&v=4 |
||||
tags: [facebook, hello, docusaurus] |
||||
--- |
||||
|
||||
Blog features are powered by the blog plugin. Simply add files to the `blog` directory. It supports tags as well! |
||||
|
||||
Delete the whole directory if you don't want the blog features. As simple as that! |
@ -0,0 +1,26 @@ |
||||
--- |
||||
id: carousel |
||||
title: Carousel |
||||
--- |
||||
|
||||
Creates carousel component. |
||||
|
||||
```jsx |
||||
<Carousel /> |
||||
``` |
||||
|
||||
### Props |
||||
|
||||
| Name | type | required | default | descripiton | |
||||
| ------------ | :-----: | -------: | ------: | --------------------------------------------------------------------------------: | |
||||
| children | Node[] | true | [] | Child items that will be wrapped by carousel | |
||||
| show | number | false | 1 | number of items to show at per slide | |
||||
| slide | number | false | 1 | number of how many items to slide | |
||||
| infinite | boolean | false | true | scrolling infinity | |
||||
| transition | number | false | 0.5 | same as css transition property's second value | |
||||
| swiping | boolean | false | false | enable swiping/dragging with mouse/touch events | |
||||
| swipeOn | number | false | 1 | percantage of item width that slides when user drag count exceeds | |
||||
| responsive | boolean | false | false | enables the feature that adjusts items width according to screen size dynamically | |
||||
| className | string | false | "" | same as react's className property | |
||||
| useArrowKeys | boolean | false | false | enables sliding when press arrow keys | |
||||
| a11y | Array | false | {} | accessibility attributes | |
@ -0,0 +1,33 @@ |
||||
--- |
||||
id: infinity |
||||
title: Infinite Carousel |
||||
--- |
||||
|
||||
Carousel is infinite at default |
||||
|
||||
import {Carousel} from '@trendyol/react-carousel'; |
||||
export const Highlight = ({children, color}) => ( <span style={{ |
||||
backgroundColor: color, |
||||
borderRadius: '2px', |
||||
color: '#fff', |
||||
padding: '90px 0', |
||||
display: 'block', |
||||
height: '200px', |
||||
margin: '16px 16px 16px 0', |
||||
}}> {children} </span> ); |
||||
|
||||
<Carousel className={'exampleCarousel1'} show={3.5} slide={2} transition={0.5}> |
||||
<Highlight color="#f27a1a">We love Trendyol orange</Highlight> |
||||
<a target="_blank" href="https://github.com/trendyol/"><Highlight color="#d53f8c">This is our github</Highlight></a> |
||||
<Highlight color="#16be48">We love Trendyol green</Highlight> |
||||
<a target="_blank" href="https://trendyol.com/"><Highlight color="#3f51b5">This is our website</Highlight></a> |
||||
</Carousel> |
||||
|
||||
```jsx |
||||
<Carousel show={3.5} slide={2} transition={0.5}> |
||||
<Highlight color="#f27a1a">We love Trendyol orange</Highlight> |
||||
<Highlight color="#d53f8c">This is our github</Highlight> |
||||
<Highlight color="#16be48">We love Trendyol green</Highlight> |
||||
<Highlight color="#3f51b5">This is our website</Highlight> |
||||
</Carousel> |
||||
``` |
@ -0,0 +1,21 @@ |
||||
--- |
||||
id: installation |
||||
title: Installation Guide |
||||
sidebar_label: Installation Guide |
||||
--- |
||||
|
||||
Carousel requires React 16.8 or greater. |
||||
|
||||
## Installation |
||||
|
||||
``` |
||||
npm i react react-dom @trendyol/react-carousel --save |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Importing |
||||
|
||||
```jsx |
||||
import { Carousel } from '@trendyol/react-carousel'; |
||||
``` |
@ -0,0 +1,23 @@ |
||||
--- |
||||
id: usage |
||||
title: Usage |
||||
--- |
||||
|
||||
Simple carousel that show one item per slide. |
||||
|
||||
```jsx |
||||
import React from 'react'; |
||||
import ReactDOM from 'react-dom'; |
||||
import { Carousel } from '@trendyol/react-carousel'; |
||||
import { Item } from './yourItem'; |
||||
|
||||
ReactDOM.render( |
||||
<Carousel> |
||||
<Item /> |
||||
<Item /> |
||||
<Item /> |
||||
<Item /> |
||||
</Carousel>, |
||||
document.getElementById('root'), |
||||
); |
||||
``` |
@ -0,0 +1,90 @@ |
||||
module.exports = { |
||||
title: 'Carousel', |
||||
tagline: 'Lightweight carousel component for React', |
||||
url: 'https://trendyol.github.io/react-carousel', |
||||
baseUrl: '/', |
||||
favicon: 'img/icon.png', |
||||
organizationName: 'Trendyol', |
||||
projectName: 'carousel', |
||||
themeConfig: { |
||||
navbar: { |
||||
title: 'React Carousel', |
||||
logo: { |
||||
alt: 'Carousel', |
||||
src: 'img/icon.png', |
||||
}, |
||||
links: [ |
||||
{ |
||||
to: 'docs/installation', |
||||
activeBasePath: 'docs', |
||||
label: 'Docs', |
||||
position: 'left', |
||||
}, |
||||
{ |
||||
href: 'https://github.com/trendyol/react-carousel', |
||||
label: 'GitHub', |
||||
position: 'right', |
||||
}, |
||||
], |
||||
}, |
||||
footer: { |
||||
style: 'dark', |
||||
links: [ |
||||
{ |
||||
title: 'Docs', |
||||
items: [ |
||||
{ |
||||
label: 'Installation', |
||||
to: 'docs/installation', |
||||
}, |
||||
{ |
||||
label: 'Usage', |
||||
to: 'docs/usage ', |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
title: 'Communtiy', |
||||
items: [ |
||||
{ |
||||
label: 'GitHub', |
||||
href: 'https://github.com/trendyol/', |
||||
}, |
||||
{ |
||||
label: 'Meetup', |
||||
href: 'https://www.meetup.com/trendyol/', |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
title: 'Social', |
||||
items: [ |
||||
{ |
||||
label: 'Medium', |
||||
href: 'https://medium.com/trendyol-tech', |
||||
}, |
||||
{ |
||||
label: 'Youtube', |
||||
href: 'https://www.youtube.com/channel/UCUBiayLMggBAsiYvGLzQJ5w/', |
||||
}, |
||||
], |
||||
}, |
||||
], |
||||
copyright: `Copyright © ${new Date().getFullYear()} Trendyol Open Source`, |
||||
}, |
||||
}, |
||||
presets: [ |
||||
[ |
||||
'@docusaurus/preset-classic', |
||||
{ |
||||
docs: { |
||||
sidebarPath: require.resolve('./sidebars.js'), |
||||
editUrl: 'https://github.com/trendyol/react-carousel/edit/master/website/', |
||||
}, |
||||
theme: { |
||||
customCss: require.resolve('./src/css/custom.css'), |
||||
}, |
||||
}, |
||||
], |
||||
], |
||||
}; |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,30 @@ |
||||
{ |
||||
"name": "website", |
||||
"version": "0.0.0", |
||||
"private": true, |
||||
"scripts": { |
||||
"start": "docusaurus start", |
||||
"build": "docusaurus build", |
||||
"swizzle": "docusaurus swizzle", |
||||
"deploy": "docusaurus deploy" |
||||
}, |
||||
"dependencies": { |
||||
"@docusaurus/core": "^2.0.0-alpha.48", |
||||
"@docusaurus/preset-classic": "^2.0.0-alpha.48", |
||||
"classnames": "^2.2.6", |
||||
"react": "^16.13.1", |
||||
"react-dom": "^16.13.1" |
||||
}, |
||||
"browserslist": { |
||||
"production": [ |
||||
">0.2%", |
||||
"not dead", |
||||
"not op_mini all" |
||||
], |
||||
"development": [ |
||||
"last 1 chrome version", |
||||
"last 1 firefox version", |
||||
"last 1 safari version" |
||||
] |
||||
} |
||||
} |
@ -0,0 +1,7 @@ |
||||
module.exports = { |
||||
someSidebar: { |
||||
Docusaurus: ['installation', 'usage'], |
||||
API: ['carousel'], |
||||
Examples: ['infinity', 'swipible'], |
||||
}, |
||||
}; |
@ -0,0 +1,50 @@ |
||||
/* stylelint-disable docusaurus/copyright-header */ |
||||
/** |
||||
* Any CSS included here will be global. The classic template |
||||
* bundles Infima by default. Infima is a CSS framework designed to |
||||
* work well for content-centric websites. |
||||
*/ |
||||
|
||||
/* You can override the default Infima variables here. */ |
||||
:root { |
||||
--ifm-color-primary: #25c2a0; |
||||
--ifm-color-primary-dark: rgb(33, 175, 144); |
||||
--ifm-color-primary-darker: rgb(31, 165, 136); |
||||
--ifm-color-primary-darkest: rgb(26, 136, 112); |
||||
--ifm-color-primary-light: rgb(70, 203, 174); |
||||
--ifm-color-primary-lighter: rgb(102, 212, 189); |
||||
--ifm-color-primary-lightest: rgb(146, 224, 208); |
||||
--ifm-code-font-size: 95%; |
||||
} |
||||
|
||||
.docusaurus-highlight-code-line { |
||||
background-color: rgb(72, 77, 91); |
||||
display: block; |
||||
margin: 0 calc(-1 * var(--ifm-pre-padding)); |
||||
padding: 0 var(--ifm-pre-padding); |
||||
} |
||||
|
||||
.exampleCarousel1 button:first-child { |
||||
background: url('https://cdn.dsmcdn.com/web/production/slick-arrow.svg') no-repeat |
||||
center; |
||||
transform: rotateZ(180deg); |
||||
outline: none; |
||||
border: none; |
||||
cursor: pointer; |
||||
position: relative; |
||||
left: -16px; |
||||
} |
||||
|
||||
.exampleCarousel1 button:last-child { |
||||
background: url('https://cdn.dsmcdn.com/web/production/slick-arrow.svg') no-repeat |
||||
center; |
||||
outline: none; |
||||
border: none; |
||||
cursor: pointer; |
||||
position: relative; |
||||
right: -16px; |
||||
} |
||||
|
||||
.exampleCarousel1 div:first-child { |
||||
text-align: center; |
||||
} |
@ -0,0 +1,126 @@ |
||||
import React from 'react'; |
||||
import classnames from 'classnames'; |
||||
import Layout from '@theme/Layout'; |
||||
import Link from '@docusaurus/Link'; |
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; |
||||
import useBaseUrl from '@docusaurus/useBaseUrl'; |
||||
import { Carousel } from '@trendyol/react-carousel'; |
||||
import styles from './styles.module.css'; |
||||
import { Redirect } from '@docusaurus/router'; |
||||
|
||||
const features = [ |
||||
{ |
||||
title: <>Easy to Use</>, |
||||
imageUrl: 'img/undraw_docusaurus_mountain.svg', |
||||
description: ( |
||||
<> |
||||
Docusaurus was designed from the ground up to be easily installed and used to get |
||||
your website up and running quickly. |
||||
</> |
||||
), |
||||
}, |
||||
{ |
||||
title: <>Focus on What Matters</>, |
||||
imageUrl: 'img/undraw_docusaurus_tree.svg', |
||||
description: ( |
||||
<> |
||||
Docusaurus lets you focus on your docs, and we'll do the chores. Go ahead and |
||||
move your docs into the <code>docs</code> directory. |
||||
</> |
||||
), |
||||
}, |
||||
{ |
||||
title: <>Powered by React</>, |
||||
imageUrl: 'img/undraw_docusaurus_react.svg', |
||||
description: ( |
||||
<> |
||||
Extend or customize your website layout by reusing React. Docusaurus can be |
||||
extended while reusing the same header and footer. |
||||
</> |
||||
), |
||||
}, |
||||
]; |
||||
const features2 = (num) => { |
||||
let i = 0; |
||||
return new Array(num).fill(0).map(() => { |
||||
i++; |
||||
return { |
||||
description: <>{i}</>, |
||||
}; |
||||
}); |
||||
}; |
||||
|
||||
function Feature({ imageUrl, title, description }) { |
||||
const imgUrl = useBaseUrl(imageUrl); |
||||
return ( |
||||
<div className={classnames('col col--4', styles.feature)}> |
||||
{imgUrl && ( |
||||
<div className="text--center"> |
||||
<img className={styles.featureImage} src={imgUrl} alt={title} /> |
||||
</div> |
||||
)} |
||||
<h3>{title}</h3> |
||||
<p>{description}</p> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
function Home() { |
||||
return <Redirect to="/docs/installation" />; |
||||
const context = useDocusaurusContext(); |
||||
const { siteConfig = {} } = context; |
||||
return ( |
||||
<Layout |
||||
title={`Hello from ${siteConfig.title}`} |
||||
description="Description will go into a meta tag in <head />" |
||||
> |
||||
<header className={classnames('hero hero--primary', styles.heroBanner)}> |
||||
<div className="container"> |
||||
<h1 className="hero__title">{siteConfig.title}</h1> |
||||
<p className="hero__subtitle">{siteConfig.tagline}</p> |
||||
<div className={styles.buttons}> |
||||
<Link |
||||
className={classnames( |
||||
'button button--outline button--secondary button--lg', |
||||
styles.getStarted, |
||||
)} |
||||
to={useBaseUrl('docs/doc1')} |
||||
> |
||||
Get Started |
||||
</Link> |
||||
</div> |
||||
</div> |
||||
</header> |
||||
<main> |
||||
<section className={styles.features}> |
||||
<div className="container"> |
||||
<div className="row"> |
||||
<Carousel show={5} slide={5} transition={0.5}> |
||||
{features2(15).map((props, idx) => ( |
||||
<div key={idx} style={{ marginRight: '20px' }}> |
||||
{props.description} |
||||
</div> |
||||
))} |
||||
</Carousel> |
||||
</div> |
||||
</div> |
||||
</section> |
||||
{features && features.length && ( |
||||
<section className={styles.features}> |
||||
<div className="container"> |
||||
<div className="row"> |
||||
<Carousel show={2} slide={1} transition={0.5}> |
||||
{features.map((props, idx) => ( |
||||
<Feature key={idx} {...props} /> |
||||
))} |
||||
</Carousel> |
||||
</div> |
||||
</div> |
||||
</section> |
||||
)} |
||||
</main> |
||||
</Layout> |
||||
); |
||||
} |
||||
|
||||
export default Home; |
@ -0,0 +1,36 @@ |
||||
/* stylelint-disable docusaurus/copyright-header */ |
||||
/** |
||||
* CSS files with the .module.css suffix will be treated as CSS modules |
||||
* and scoped locally. |
||||
*/ |
||||
|
||||
.heroBanner { |
||||
padding: 4rem 0; |
||||
text-align: center; |
||||
position: relative; |
||||
overflow: hidden; |
||||
} |
||||
|
||||
@media screen and (max-width: 966px) { |
||||
.heroBanner { |
||||
padding: 2rem; |
||||
} |
||||
} |
||||
|
||||
.buttons { |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: center; |
||||
} |
||||
|
||||
.features { |
||||
display: flex; |
||||
align-items: center; |
||||
padding: 2rem 0; |
||||
width: 100%; |
||||
} |
||||
|
||||
.featureImage { |
||||
height: 200px; |
||||
width: 200px; |
||||
} |
After Width: | Height: | Size: 766 B |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 6.3 KiB |
After Width: | Height: | Size: 31 KiB |
After Width: | Height: | Size: 35 KiB |
After Width: | Height: | Size: 12 KiB |
Loading…
Reference in new issue