├── .babelrc ├── .changeset └── config.json ├── .eslintrc ├── .github ├── ISSUE_TEMPLATE.md ├── actions │ └── setup │ │ └── action.yml ├── pull_request_template.md └── workflows │ ├── chromatic.yml │ ├── ci.yml │ └── release.yml ├── .gitignore ├── .idea └── .gitignore ├── .npmrc ├── CONTRIBUTING.md ├── LICENSE ├── Nuka-Hero.png ├── README.md ├── docs ├── api-v7.mdx ├── api │ ├── accessibility.mdx │ ├── autoplay.mdx │ ├── callbacks.mdx │ ├── index.mdx │ ├── initial-page.mdx │ ├── methods.mdx │ ├── navigation.mdx │ ├── scroll.mdx │ ├── styling.mdx │ └── swiping.mdx ├── introduction.mdx ├── v7-upgrade-guide.mdx └── v8-upgrade-guide.mdx ├── package.json ├── packages └── nuka │ ├── .babelrc │ ├── .eslintignore │ ├── .npmignore │ ├── .storybook │ ├── main.js │ └── preview.js │ ├── CHANGELOG.md │ ├── __mocks__ │ └── styleMock.js │ ├── globals.d.ts │ ├── jest-setup.ts │ ├── jest.config.js │ ├── package.json │ ├── src │ ├── Carousel │ │ ├── Carousel.css │ │ ├── Carousel.stories.tsx │ │ ├── Carousel.tsx │ │ ├── CarouselStories.css │ │ ├── ExampleSlide.tsx │ │ ├── NavButtons.tsx │ │ └── PageIndicators.tsx │ ├── hooks │ │ ├── use-carousel.tsx │ │ ├── use-hover.tsx │ │ ├── use-interval.tsx │ │ ├── use-keyboard.tsx │ │ ├── use-measurement.test.tsx │ │ ├── use-measurement.tsx │ │ ├── use-paging.test.tsx │ │ ├── use-paging.tsx │ │ ├── use-reduced-motion.tsx │ │ └── use-resize-observer.tsx │ ├── index.tsx │ ├── types.ts │ └── utils │ │ ├── array.test.ts │ │ ├── array.ts │ │ ├── browser.ts │ │ ├── css.ts │ │ ├── index.ts │ │ └── mouse.ts │ ├── tsconfig.json │ └── tsup.config.ts ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── prettier.config.js ├── tsconfig.json ├── vercel.json └── website ├── .gitignore ├── babel.config.js ├── docusaurus.config.js ├── package.json ├── sidebars.js ├── src ├── assets │ ├── fonts │ │ └── inter │ │ │ ├── InterBold.woff2 │ │ │ ├── InterMedium.woff2 │ │ │ └── InterRegular.woff2 │ └── images │ │ ├── hero-pattern.png │ │ ├── native-support.png │ │ ├── responsive.png │ │ └── style.png ├── components │ ├── basic-button-demo.tsx │ ├── buttons.tsx │ ├── carousel-demo.tsx │ ├── custom-arrows-demo.tsx │ ├── custom-dots-demo.tsx │ ├── full-feature-demo.tsx │ └── landing │ │ ├── landing-banner.tsx │ │ ├── landing-divider.tsx │ │ ├── landing-featured-projects.tsx │ │ ├── landing-features.tsx │ │ ├── landing-hero.tsx │ │ ├── landing-logos.tsx │ │ └── nf-link-button.tsx ├── css │ └── custom.css └── pages │ └── index.tsx ├── static ├── .nojekyll ├── img │ ├── av_cover.jpg │ ├── favicon.ico │ ├── nearform-icon-white.svg │ ├── nearform-icon.svg │ ├── nearform-logo-white.svg │ ├── nearform-logo.svg │ ├── pexels-01.jpg │ ├── pexels-02.jpg │ ├── pexels-03.jpg │ ├── product-1.jpg │ ├── product-2.jpg │ ├── product-3.jpg │ ├── product-4.jpg │ └── product-5.jpg └── open-graph.png ├── tailwind.config.js └── tsconfig.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-typescript", "@babel/preset-react"], 3 | "plugins": [ 4 | "@babel/plugin-transform-object-assign", 5 | "@babel/plugin-transform-object-rest-spread" 6 | ], 7 | "env": { 8 | "esm": { 9 | "presets": [ 10 | ["@babel/preset-env", { "modules": false }], 11 | "@babel/preset-react" 12 | ] 13 | }, 14 | "test": { 15 | "plugins": ["@babel/plugin-transform-runtime"] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.0.0/schema.json", 3 | "changelog": [ 4 | "@svitejs/changesets-changelog-github-compact", 5 | { 6 | "repo": "FormidableLabs/nuka-carousel" 7 | } 8 | ], 9 | "access": "public", 10 | "baseBranch": "main", 11 | "ignore": ["website"] 12 | } 13 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended", 4 | "prettier", 5 | "plugin:@typescript-eslint/recommended", 6 | "plugin:prettier/recommended", 7 | "plugin:react/recommended" 8 | ], 9 | "env": { 10 | "node": true, 11 | "browser": true, 12 | "jest": true 13 | }, 14 | "settings": { 15 | "react": { 16 | "version": "detect" 17 | } 18 | }, 19 | "globals": { 20 | "page": true 21 | }, 22 | "ignorePatterns": ["packages/nuka/dist"], 23 | "parser": "@typescript-eslint/parser", 24 | "parserOptions": { 25 | "ecmaVersion": 12, 26 | "ecmaFeatures": { 27 | "jsx": true 28 | } 29 | }, 30 | "plugins": ["react", "react-hooks", "prettier", "@typescript-eslint"], 31 | "rules": { 32 | "max-params": 0, 33 | "max-statements": 0, 34 | "no-invalid-this": 0, 35 | "no-magic-numbers": 0, 36 | "no-unused-expressions": 0, 37 | "prettier/prettier": "error", 38 | "react/no-multi-comp": 0, 39 | "react/prefer-es6-class": 0, 40 | "react/prop-types": 0, 41 | "react/sort-prop-types": 0, 42 | "react/no-string-refs": 0, 43 | "react/no-did-update-set-state": 0, 44 | "react-hooks/rules-of-hooks": "error", 45 | "react-hooks/exhaustive-deps": "error", 46 | "@typescript-eslint/no-unused-vars": ["error"], 47 | "@typescript-eslint/no-var-requires": 0, 48 | "react/react-in-jsx-scope": "off" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Bugs and Questions 2 | 3 | ### Prerequisites 4 | 5 | 6 | 7 | - [ ] I've searched open [issues](https://github.com/FormidableLabs/nuka-carousel/issues) to make sure I'm not opening a duplicate issue 8 | - [ ] I have read through the docs before asking a question 9 | - [ ] I am using the latest version of nuka-carousel 10 | 11 | ### Describe Your Environment 12 | 13 | - What version of nuka-carousel are you using? 14 | - What version of React are you using? 15 | - What browser are you using? 16 | 17 | ### Describe the Problem 18 | 19 | 27 | 28 | #### Expected behavior: 29 | 30 | 31 | 32 | #### Actual behavior 33 | 34 | 35 | 36 | ### Additional Information 37 | 38 | 39 | -------------------------------------------------------------------------------- /.github/actions/setup/action.yml: -------------------------------------------------------------------------------- 1 | name: Setup 2 | description: Setup Build Step 3 | inputs: 4 | node-version: 5 | required: true 6 | default: '18.x' 7 | 8 | runs: 9 | using: "composite" 10 | steps: 11 | - uses: pnpm/action-setup@v3 12 | with: 13 | version: 7 14 | 15 | - name: Use Node.js 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: ${{ inputs.node-version }} 19 | cache: 'pnpm' 20 | 21 | - name: Install dependencies 22 | shell: bash 23 | run: pnpm install 24 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### Description 2 | 3 | 4 | 5 | 6 | 7 | Fixes # (issue) 8 | 9 | #### Type of Change 10 | 11 | 12 | 13 | - [ ] Bug fix (non-breaking change which fixes an issue) 14 | - [ ] New feature (non-breaking change which adds functionality) 15 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 16 | - [ ] This change requires a documentation update 17 | 18 | ### How Has This Been Tested? 19 | 20 | 21 | 22 | 23 | 24 | ### Checklist 25 | 26 | 27 | 28 | - [ ] My code follows the style guidelines of this project (I have run `pnpm run lint`) 29 | - [ ] I have added tests that prove my fix is effective or that my feature works 30 | - [ ] New and existing unit tests pass locally with my changes (I have run `pnpm run test:ci-with-server`/`pnpm run test`) 31 | - [ ] I have performed a self-review of my own code 32 | - [ ] I have commented my code, particularly in hard-to-understand areas 33 | - [ ] I have made corresponding changes to the documentation 34 | - [ ] I have recorded any user-facing fixes or changes with `pnpm changeset`. 35 | - [ ] My changes generate no new warnings 36 | - [ ] Any dependent changes have been merged and published in downstream modules 37 | -------------------------------------------------------------------------------- /.github/workflows/chromatic.yml: -------------------------------------------------------------------------------- 1 | name: Chromatic 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | paths: 11 | - 'packages/**' 12 | 13 | jobs: 14 | chromatic: 15 | name: Storybook Publish 16 | runs-on: ubuntu-latest 17 | steps: 18 | # requires all branches and tags to be fetched for chromatic 19 | - uses: actions/checkout@v4 20 | with: 21 | fetch-depth: 0 22 | 23 | - uses: ./.github/actions/setup 24 | 25 | - name: Build 26 | run: pnpm build 27 | 28 | - name: Publish to Chromatic 29 | uses: chromaui/action@v1 30 | with: 31 | token: ${{ secrets.GITHUB_TOKEN }} 32 | projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} 33 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | build: 13 | name: Check and build codebase 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: ./.github/actions/setup 18 | 19 | - name: Check Code 20 | run: pnpm check:ci 21 | 22 | - name: Build 23 | run: pnpm build 24 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Nuka Release Workflow 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: write 13 | id-token: write 14 | issues: write 15 | repository-projects: write 16 | deployments: write 17 | packages: write 18 | pull-requests: write 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | - uses: ./.github/actions/setup 23 | 24 | - name: Build packages 25 | run: pnpm run build 26 | 27 | - name: PR or Publish 28 | id: changesets 29 | uses: changesets/action@v1 30 | with: 31 | version: pnpm run version 32 | publish: pnpm changeset publish 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### SublimeText ### 2 | *.sublime-workspace 3 | .idea 4 | 5 | ### OSX ### 6 | .DS_Store 7 | .AppleDouble 8 | .LSOverride 9 | Icon 10 | 11 | # Thumbnails 12 | ._* 13 | 14 | # Files that might appear on external disk 15 | .Spotlight-V100 16 | .Trashes 17 | 18 | ### Windows ### 19 | # Windows image file caches 20 | Thumbs.db 21 | ehthumbs.db 22 | 23 | # Folder config file 24 | Desktop.ini 25 | 26 | # Recycle Bin used on file shares 27 | $RECYCLE.BIN/ 28 | 29 | # App specific 30 | 31 | node_modules/ 32 | bower_components/ 33 | .tmp 34 | es 35 | lib 36 | dist 37 | types 38 | yarn-error.log* 39 | package-lock.json 40 | coverage 41 | 42 | cypress/screenshots 43 | cypress/videos 44 | /packages/nuka/cypress/screenshots 45 | /packages/nuka/cypress/videos 46 | /packages/nuka/storybook-static 47 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | strict-peer-dependencies=false 2 | prefer-workspace-packages=true 3 | auto-install-peers=false -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Install 4 | 5 | ```sh 6 | $ pnpm install 7 | ``` 8 | 9 | ## Build source 10 | 11 | Build the published Babel sources: 12 | 13 | ```sh 14 | # One time build 15 | $ pnpm run build 16 | ``` 17 | 18 | ## Running the demos 19 | 20 | Run the NextJS example on `localhost:3000` 21 | 22 | ```sh 23 | $ pnpm run start:nextjs 24 | ``` 25 | 26 | To make changes to the Nuka Carousel library and have those changes reflect in the NextJS demo app also run 27 | 28 | ```sh 29 | $ pnpm run build:watch 30 | ``` 31 | 32 | Run Storybook on `localhost:6006` 33 | 34 | ```sh 35 | $ pnpm run start:storybook 36 | ``` 37 | 38 | ## Tests 39 | 40 | Basics: 41 | 42 | ```sh 43 | # Everything 44 | $ pnpm run check 45 | 46 | # ... which really runs 47 | $ pnpm run lint 48 | $ pnpm run test 49 | ``` 50 | 51 | And E2E tests (you _must_ be on `node@8+`): 52 | 53 | ```sh 54 | $ pnpm run test 55 | ``` 56 | 57 | ## Release 58 | 59 | We use [changesets](https://github.com/changesets/changesets) to create package versions and publish them. 60 | 61 | ### Using changsets 62 | 63 | Our official release path is to use automation to perform the actual publishing of our packages. The steps are to: 64 | 65 | 1. A human developer adds a changeset. Ideally this is as a part of a PR that will have a version impact on a package. 66 | 2. On merge of a PR our automation system opens a "Version Packages" PR. 67 | 3. On merging the "Version Packages" PR, the automation system publishes the packages. 68 | 69 | Here are more details: 70 | 71 | ### Add a changeset 72 | 73 | When you would like to add a changeset (which creates a file indicating the type of change), in your branch/PR issue this command: 74 | 75 | ```sh 76 | $ pnpm run changeset 77 | ``` 78 | 79 | to produce an interactive menu. Navigate the packages with arrow keys and hit `` to select 1+ packages. Hit `` when done. Select semver versions for packages and add appropriate messages. From there, you'll be prompted to enter a summary of the change. Some tips for this summary: 80 | 81 | 1. Aim for a single line, 1+ sentences as appropriate. 82 | 2. Include issue links in GH format (e.g. `#123`). 83 | 3. You don't need to reference the current pull request or whatnot, as that will be added later automatically. 84 | 85 | After this, you'll see a new uncommitted file in `.changesets` like: 86 | 87 | ```sh 88 | $ git status 89 | # .... 90 | Untracked files: 91 | (use "git add ..." to include in what will be committed) 92 | .changeset/flimsy-pandas-marry.md 93 | ``` 94 | 95 | Review the file, make any necessary adjustments, and commit it to source. When we eventually do a package release, the changeset notes and version will be incorporated! 96 | 97 | ### Creating versions 98 | 99 | On a merge of a feature PR, the changesets GitHub action will open a new PR titled `"Version Packages"`. This PR is automatically kept up to date with additional PRs with changesets. So, if you're not ready to publish yet, just keep merging feature PRs and then merge the version packages PR later. 100 | 101 | ### Publishing packages 102 | 103 | On the merge of a version packages PR, the changesets GitHub action will publish the packages to npm. 104 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2024 Formidable Labs, Inc. 4 | 5 | Copyright (c) 2018 Roman Charugin, Alex Smith, Matt Sungwook, and potentially 6 | other DefinitelyTyped contributors. 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy of 9 | this software and associated documentation files (the "Software"), to deal in 10 | the Software without restriction, including without limitation the rights to 11 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 12 | the Software, and to permit persons to whom the Software is furnished to do so, 13 | subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 20 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 21 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 22 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 23 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /Nuka-Hero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/nuka-carousel/e839a97883f06cd29d3a537b51e5ffb67216649c/Nuka-Hero.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Nuka Carousel](https://raw.githubusercontent.com/FormidableLabs/nuka-carousel/main/Nuka-Hero.png)](https://commerce.nearform.com/open-source/nuka-carousel) 2 | 3 | [![Maintenance Status][maintenance-image]](#maintenance-status) 4 | 5 | Small, fast and accessibility-first React carousel library with easily customizable UI and behavior to fit your brand and site 6 | 7 | ![Nuka Carousel Animated Example](https://i.imgur.com/UwP5gle.gif) 8 | 9 | 10 | ### Install 11 | 12 | To add `nuka-carousel` to your project run the following command in your project folder. 13 | 14 | ```bash 15 | $ yarn add nuka-carousel 16 | ``` 17 | 18 | Come learn more and see a live demo at our [docs site](https://commerce.nearform.com/open-source/nuka-carousel)! 19 | 20 | ## Support 21 | 22 | Have a question about nuka-carousel? Submit an issue in this repository using the 23 | ["Question" template](https://github.com/FormidableLabs/nuka-carousel/issues/new?template=question.md). 24 | 25 | Notice something inaccurate or confusing? Feel free to [open an issue](https://github.com/FormidableLabs/nuka-carousel/issues/new/choose) or [make a pull request](https://github.com/FormidableLabs/nuka-carousel/pulls) to help improve the documentation for everyone! 26 | 27 | The source for our docs site lives in this repo in the [`docs`](https://github.com/FormidableLabs/nuka-carousel/blob/main/docs) folder. 28 | 29 | 30 | ### Contributing 31 | 32 | See the [Contribution Docs](CONTRIBUTING.md). 33 | 34 | ### Maintenance Status 35 | 36 | **Active:** Nearform is actively working on this project, and we expect to continue for work for the foreseeable future. Bug reports, feature requests and pull requests are welcome. 37 | 38 | [maintenance-image]: https://img.shields.io/badge/maintenance-active-green.svg?color=brightgreen&style=flat 39 | -------------------------------------------------------------------------------- /docs/api-v7.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 5 3 | --- 4 | 5 | # API (v7) 6 | 7 | ## Carousel Props 8 | 9 | Props for the `` component with their types and default values. 10 | 11 | ### `adaptiveHeight` 12 | A `boolean` to set the carousel to adapt its height to the visible slides. Default value: `false`. 13 | 14 | --- 15 | 16 | ### `adaptiveHeightAnimation` 17 | A `boolean` to animate height changes when `adaptiveHeight` is enabled. Default value: `false`. 18 | 19 | --- 20 | 21 | ### `afterSlide` 22 | A function `(index: number) => void` to be called after a slide is changed with a slide index parameter. 23 | 24 | --- 25 | 26 | ### `autoplay` 27 | A `boolean` to set the carousel to automatically play through the slides. Default value: `false`. 28 | 29 | --- 30 | 31 | ### `autoplayInterval` 32 | A `number` for the `autoplay` iteration in milliseconds. Default value: `3000`. 33 | 34 | --- 35 | 36 | ### `beforeSlide` 37 | A function `(index: number) => void` to be called before a slide is changed with a slide index parameter. 38 | 39 | --- 40 | 41 | ### `carouselId` 42 | 43 | A `string` prop that gives the carousel frame an id attribute which can be uniquely targeted by controls. This prevents collisions if there are multiple instances of Nuka in a single tree. The default value is generated by React’s `useId` hook. 44 | 45 | --- 46 | 47 | ### `cellAlign` 48 | An enum `'left' | 'center' | 'right'` for when displaying more than one slide, sets which position to anchor the current slide. Default value: `'left'`. 49 | 50 | --- 51 | 52 | ### `cellSpacing` 53 | A `number` representing pixels for the spacing between slides. Default value: `0`. 54 | 55 | --- 56 | 57 | ### `className` 58 | A `string` for the CSS selectors to be applied to the slider frame. 59 | 60 | --- 61 | 62 | ### `defaultControlsConfig` 63 | A configuration object to apply custom classes and styles to the default controls. [Learn more](#default-controls-configuration) about the interface. 64 | 65 | --- 66 | 67 | ### `disableAnimation` 68 | A `boolean` to disable the animation of the carousel. Default value: `false`. 69 | 70 | --- 71 | 72 | ### `disableEdgeSwiping` 73 | A `boolean` to disable swiping past the edge for the first and last slides. Default value: `false`. 74 | 75 | --- 76 | 77 | ### `dragging` 78 | A `boolean` to enable dragging of slides to navigate using a pointing device. Default value: `true`. 79 | 80 | --- 81 | 82 | ### `dragThreshold` 83 | A `number` representing the percentage (from `0` to `1`) of a slide that the user needs to drag before a slide change is triggered. Default value: `0.5`. 84 | 85 | --- 86 | 87 | ### `easing` 88 | An easing function `(normalizedTime: number) => number` for the navigation animations. [Learn more](#easing-functions) about how to use the easing function. The default function is a cubic ease out. 89 | 90 | --- 91 | 92 | ### `edgeEasing` 93 | An easing function `(normalizedTime: number) => number` for when the navigation exceeds the edge. [Learn more](#easing-functions) about how to use the easing function. The default function is a cubic ease out. 94 | 95 | --- 96 | 97 | ### `enableKeyboardControls` 98 | A `boolean` to enable keyboard controls when the carousel has focus. If the carousel does not have focus, keyboard controls will be ignored. Default value: `false`. 99 | 100 | ___ 101 | 102 | ### `frameAriaLabel` 103 | A `string` for the aria-label of the frame container of the carousel. This is useful when you have more than one carousel on the page. The default value is now `Slider`, **this is changed** from v6 and earlier. 104 | 105 | --- 106 | 107 | ### `keyCodeConfig` 108 | A configuration object for the key codes to override the default keyboard keys configured when `enableKeyboardControls` is `true`. [Learn more](#key-code-configuration) about the interface. 109 | 110 | --- 111 | 112 | ### `landmark` 113 | A `boolean` that determines whether role should be a region landmark or have role group. Role and label have been moved from the slider frame to the parent carousel element so controls are contained within the label and role. Landmark regions should be intentional, so the default value is set to `false`. 114 | 115 | --- 116 | 117 | ### `onDragStart` 118 | A function `(e: React.TouchEvent | React.MouseEvent) => void;` to capture event at the start of swiping and dragging slides. 119 | 120 | --- 121 | 122 | ### `onDrag` 123 | A function `(e: React.TouchEvent | React.MouseEvent) => void;` to capture the dragging and swiping events on slides. 124 | 125 | --- 126 | 127 | ### `onDragEnd` 128 | A function `(e: React.TouchEvent | React.MouseEvent) => void;` to capture event at the end of swiping and dragging slides. 129 | 130 | --- 131 | 132 | ### `onUserNavigation` 133 | A function `(e: React.TouchEvent | React.MouseEvent | React.KeyboardEvent) => void;` to capture user-triggered when navigation occurs: dragging and swiping, clicking one of the controls (custom controls not included), or using a keyboard shortcut. 134 | 135 | --- 136 | 137 | ### `pauseOnHover` 138 | A `boolean` to autoplay when mouse hovered over the carousel. Default value is `true`. 139 | 140 | ___ 141 | 142 | ### `onUserNavigation` 143 | A function `(props: Pick) => string` to render the message in the ARIA live region that is announcing the current slide on slide change. 144 | 145 | ___ 146 | 147 | ### `ref` 148 | A React ref object `MutableRefObject` for carousel element. 149 | 150 | --- 151 | 152 | ### `renderTopLeftControls` 153 | A function to render custom controls for the top left area of the carousel. [Learn more](#control-render-functions) about function and the parameters it accepts. 154 | 155 | --- 156 | 157 | ### `renderTopCenterControls` 158 | A function to render custom controls for the top center area of the carousel. [Learn more](#control-render-functions) about function and the parameters it accepts. 159 | 160 | --- 161 | 162 | ### `renderTopRightControls` 163 | A function to render custom controls for the top right area of the carousel. [Learn more](#control-render-functions) about function and the parameters it accepts. 164 | 165 | --- 166 | 167 | ### `renderCenterLeftControls` 168 | A function to render custom controls for the center left area of the carousel. [Learn more](#control-render-functions) about function and the parameters it accepts. 169 | 170 | --- 171 | 172 | ### `renderCenterCenterControls` 173 | A function to render custom controls for the center middle area of the carousel. [Learn more](#control-render-functions) about function and the parameters it accepts. 174 | 175 | --- 176 | 177 | ### `renderCenterRightControls` 178 | A function to render custom controls for the center right area of the carousel. [Learn more](#control-render-functions) about function and the parameters it accepts. 179 | 180 | --- 181 | 182 | ### `renderBottomLeftControls` 183 | A function to render custom controls for the bottom left area of the carousel. [Learn more](#control-render-functions) about function and the parameters it accepts. 184 | 185 | --- 186 | 187 | ### `renderBottomCenterControls` 188 | A function to render custom controls for the bottom center area of the carousel. [Learn more](#control-render-functions) about function and the parameters it accepts. 189 | 190 | --- 191 | 192 | ### `renderBottomRightControls` 193 | A function to render custom controls for the bottom right area of the carousel. [Learn more](#control-render-functions) about function and the parameters it accepts. 194 | 195 | --- 196 | 197 | ### `scrollMode` 198 | An enum `'page' | 'remainder'` for showing whitespace when you scroll to the end of a carousel with `wrapAround` set to `false`. Setting to `'remainder'` will not show the whitespace. Default value: `'page'`. 199 | 200 | --- 201 | 202 | ### `slideIndex` 203 | A `number` to set the index of the slide to be shown. This value is unset by default. 204 | 205 | --- 206 | 207 | ### `slidesToScroll` 208 | A `number` to set the amount of slides to scroll at once. This prop is ignored when `animation` is set to `fade`. Default value is `1`. 209 | 210 | --- 211 | 212 | ### `slidesToShow` 213 | A `number` to set the amount of slides to show at once. This prop is will be cast as an integer when `animation` is set to `fade` since fractional slides are not supported with that animation type. Default value is `1`. 214 | 215 | --- 216 | 217 | ### `speed` 218 | A `number` to set the animation duration and transition speed in milliseconds. Default value is `500`. 219 | 220 | --- 221 | 222 | ### `style` 223 | A `CSSProperties` object to set additional inline styles for the carousel frame. 224 | 225 | --- 226 | 227 | ### `swiping` 228 | A `boolean` to enable touch swipe and dragging for navigation. Default value is `true`. 229 | 230 | --- 231 | 232 | ### `tabbed` 233 | 234 | A `boolean` which enables the `tabpannel` aria role for the carousel to conform to WCAG tabbed carousel requirements. Default value is `true`. 235 | 236 | --- 237 | 238 | ### `withoutControls` 239 | A `boolean` to remove and hide all controls. Default value is `false`. 240 | 241 | --- 242 | 243 | ### `wrapAround` 244 | A `boolean` to enable wrap around mode where the carousel can infinitely navigate forwards and backwards. Default value is `false`. 245 | 246 | --- 247 | 248 | ### `zoomScale` 249 | A `number` to set the scale of zoom when the animation type is set to `zoom`. The number should range from `0` to `1`. Default value is `0.85`. 250 | 251 | --- 252 | 253 | ## Default Controls Configuration 254 | A configuration object to apply custom classes and styles to the default container, navigation buttons, and progress dot controls. This configuration object also lets you add additional event handlers to each of the navigation item's click handlers. 255 | ```tsx 256 | interface DefaultControlsConfig { 257 | containerClassName?: string 258 | nextButtonClassName?: string 259 | nextButtonStyle?: CSSProperties 260 | nextButtonText?: React.ReactNode 261 | pagingDotsClassName?: string 262 | pagingDotsContainerClassName?: string 263 | pagingDotsStyle?: CSSProperties 264 | prevButtonClassName?: string 265 | prevButtonStyle?: CSSProperties 266 | prevButtonText?: React.ReactNode 267 | prevButtonOnClick?(event: React.MouseEvent): void 268 | nextButtonOnClick?(event: React.MouseEvent): void 269 | pagingDotsOnClick?(event: React.MouseEvent): void 270 | 271 | } 272 | ``` 273 | 274 | ## Control Render Functions 275 | The control render functions are render props that let you provide a custom UI that overrides the default UI for each of the control areas. The signature for the render functions is: `(props: ControlProps) => JSX.Element`. 276 | 277 | ```tsx 278 | interface ControlProps { 279 | currentSlide: number; 280 | slideCount: number; 281 | pagingDotsIndices: number[]; 282 | nextDisabled: boolean; 283 | previousDisabled: boolean; 284 | nextSlide(): void; 285 | previousSlide(): void; 286 | goToSlide(targetIndex: number): void; 287 | } 288 | ``` 289 | 290 | ### `currentSlide` 291 | A `number` set to the current slide index. 292 | 293 | --- 294 | 295 | ### `slideCount` 296 | A `number` set to the number of slides. 297 | 298 | --- 299 | 300 | ### `pagingDotsIndices` 301 | An array `number[]` set the indexes of the active slides. 302 | 303 | --- 304 | 305 | ### `nextDisabled` 306 | A `boolean` indicating if a custom next slide control be disabled 307 | 308 | --- 309 | 310 | ### `previousDisabled` 311 | A `boolean` indicating if a custom next slide control be disabled 312 | 313 | --- 314 | 315 | 316 | ### `nextSlide` 317 | A function to invoke to navigate to the next slide. 318 | 319 | --- 320 | 321 | ### `previousSlide` 322 | A function to invoke to navigate to the previous slide. 323 | 324 | --- 325 | 326 | ### `goToSlide` 327 | A function to invoke with a `number` parameter to navigate to a specific slide. 328 | 329 | --- 330 | 331 | ## Key Code Configuration 332 | A configuration object for the key codes to override the default keyboard keys configured when `enableKeyboardControls` is `true`. Multiple custom key codes can be provided for each action. 333 | ```tsx 334 | interface KeyCodeConfig { 335 | firstSlide?: number[]; 336 | lastSlide?: number[]; 337 | nextSlide?: number[]; 338 | pause?: number[]; 339 | previousSlide?: number[]; 340 | } 341 | ``` 342 | 343 | ## Easing Functions 344 | 345 | `(normalizedTime: number) => number` 346 | 347 | A function accepting a normalized time between `0` and `1`, inclusive, and returning an eased time, which equals `0` at `normalizedTime == 0` and equals `1` at `normalizedTime == 1`. You can plug in your own custom easing function (e.g., `(t) => t` for a linear transition), or import functions from a different library, like [`d3-ease`](https://github.com/d3/d3-ease). 348 | ```tsx 349 | import { easeCircleOut, easeElasticOut } from 'd3-ease'; 350 | 351 | // ... 352 | 353 | 354 | {/* Carousel Content */} 355 | 356 | ``` 357 | 358 | Please note that using a function for `easing` with "In" in it (ease**In**Out, easeElastic**In**, etc.) will make swiping transitions feel a bit clunky, as the velocity at the end of the swipe will suddenly drop to follow the slow startup speed of the "In" easing function. In general, when using custom easing functions, try out both swiping and clicking on the navigation buttons to see how the transitions feel. 359 | -------------------------------------------------------------------------------- /docs/api/accessibility.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 6 3 | --- 4 | 5 | import { Carousel } from 'nuka-carousel'; 6 | 7 | # Accessibility 8 | 9 | | Prop Name | Type | Default Value | 10 | | :----------------- | :------ | :------------ | 11 | | `keyboard` | boolean | `true` | 12 | | `title` | string | `undefined` | 13 | 14 | ### Keyboard 15 | 16 | By default the carousel can be navigated using the keyboard. The `keyboard` prop can be used to disable the keyboard navigation. 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | #### Code 25 | 26 | ```tsx 27 | 28 | 29 | 30 | 31 | 32 | ``` 33 | 34 | ### Title 35 | 36 | The carousel has a `title` prop that can be used to provide a title for the carousel. The value of the title will be rendered as a hidden `h3` tag for the carousel targeted with the `aria-labeledby` attribute. This will help screen readers to announce the title of the carousel when it is focused. 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | #### Code 45 | 46 | ```tsx 47 | 48 | 49 | 50 | 51 | 52 | ``` 53 | -------------------------------------------------------------------------------- /docs/api/autoplay.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | import { Carousel } from 'nuka-carousel'; 6 | 7 | # Autoplay 8 | 9 | The carousel can advance on its own with a specified interval measured in milliseconds. An `autoplayInterval` without the `autoplay` prop being true will not do anything. 10 | For accessibility, the carousel will pause when the user is interacting with it. 11 | 12 | | Prop Name | Type | Default Value | 13 | | :----------------- | :------ | :------------ | 14 | | `autoplay` | boolean | `false` | 15 | | `autoplayInterval` | number | `3000` | 16 | 17 | #### Example 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | #### Code 26 | 27 | ```tsx 28 | 29 | 30 | 31 | 32 | 33 | ``` 34 | 35 | ### Navigation Arrows 36 | 37 | | Prop Name | Type | Default Value | 38 | | :----------------- | :------ | :------------ | 39 | | `autoplay` | boolean | `false` | 40 | | `showArrows` | boolean \| `always` \| `hover` | `false` | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | #### Code 49 | 50 | ```tsx 51 | 52 | 53 | 54 | 55 | 56 | ``` -------------------------------------------------------------------------------- /docs/api/callbacks.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 90 3 | --- 4 | 5 | import Tabs from '@theme/Tabs'; 6 | import TabItem from '@theme/TabItem'; 7 | import CodeBlock from '@theme/CodeBlock'; 8 | 9 | # Callbacks 10 | 11 | ## Before/After Slide 12 | 13 | Functions that are invoked when the progression methods (goBack()/goForward()) are called or when carousel changes its scroll position. 14 | 15 | ### Details 16 | 17 | | Prop Name | Type | Default Value | 18 | | :------------ | :--------------------------------------------------------- | :------------ | 19 | | `beforeSlide` | (currentSlideIndex: number, endSlideIndex: number) => void | `undefined` | 20 | | `afterSlide` | (endSlideIndex: number) => void | `undefined` | 21 | 22 | - `beforeSlide` - Runs a given function before scrolling when a progression method is called. It will also run right before the carousel registers that it has been scrolled on if manually scrolled. 23 | - `afterSlide` - Runs a given function after scrolling when a progression method is called or after manually scrolling. 24 | 25 | ### Example 26 | 27 | ```tsx 28 | myCustomBeforeFunction()}>{/* Cards */} 29 | ``` 30 | 31 | ```tsx 32 | myCustomAfterFunction()}>{/* Cards */} 33 | ``` 34 | -------------------------------------------------------------------------------- /docs/api/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | import Tabs from '@theme/Tabs'; 6 | import TabItem from '@theme/TabItem'; 7 | import { Carousel } from 'nuka-carousel'; 8 | 9 | # Usage 10 | 11 | Nuka v8 and above are completely rewritten with new props and might not be completely backwards compatable with v7. 12 | 13 | ## Installation 14 | 15 | In the directory containing package.json, run your package manager's install command: 16 | 17 | 18 | 19 | 20 | ```bash 21 | npm install nuka-carousel 22 | ``` 23 | 24 | 25 | 26 | 27 | ```bash 28 | yarn add nuka-carousel 29 | ``` 30 | 31 | 32 | 33 | 34 | ```bash 35 | pnpm add nuka-carousel 36 | ``` 37 | 38 | 39 | 40 | 41 | :::info 42 | 43 | Nuka Carousel has a peer dependency on React 18. 44 | 45 | ::: 46 | 47 | ## Basic Usage 48 | 49 | ```jsx 50 | import { Carousel } from 'nuka-carousel'; 51 | 52 | const App = () => { 53 | return ( 54 | 55 |
Slide 1
56 |
Slide 2
57 |
Slide 3
58 |
59 | ); 60 | }; 61 | ``` 62 | 63 | Feel free to mix React components and HTML elements as children. Nuka Carousel will handle them all. 64 | 65 | ```jsx 66 | 67 |
Slide 1
68 | 69 | 70 |
71 | ``` 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | :::caution 80 | 81 | ### Nuka Carousel uses a flex container to hold its contents. 82 | 83 | In order for Nuka to measure your slides, they must have a width that can be calculated. 84 | 85 | ::: 86 | 87 | ### Images 88 | 89 | If you're using images, Nuka will correctly calculate the width and height of the image after it has loaded. 90 | 91 | #### Default 92 | 93 | ```jsx 94 | 95 | 96 | 97 | 98 | 99 | ``` 100 | 101 | #### Recommended 102 | 103 | However, it's recommended to set the width and height of the image in the HTML to prevent layout shifts. This is best practice for all images on the web and some frameworks such as `Next/image` require it. 104 | 105 | ```jsx 106 | 107 | 108 | 109 | 110 | 111 | ``` 112 | 113 | ### HTML Block Elements 114 | 115 | When using HTML block elements, such as `div`, you must set the min width in the HTML. 116 | 117 | :::info 118 | 119 | Most of the examples use Tailwind classes for styling 120 | 121 | ::: 122 | 123 | ```jsx 124 | .demo-slide { 125 | min-width: 300px; 126 | min-height: 100px; 127 | } 128 | 129 | 130 |
131 |
132 |
133 | 134 | ``` 135 | 136 | ### Custom React components 137 | 138 | Nuka supports all custom React components. Just make sure to set the width in the component. 139 | 140 | ```jsx 141 | function CarouselImage() { 142 | return ( 143 |
144 | image 145 |
Title
146 |
147 | ); 148 | } 149 | 150 | 151 | 152 | 153 | 154 | ; 155 | ``` 156 | -------------------------------------------------------------------------------- /docs/api/initial-page.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | import { Carousel } from 'nuka-carousel'; 6 | 7 | # Initial Page 8 | 9 | The carousel can start on any index within bounds of its length. Anything out of bounds will default back to `0` for its index. This list is `0` indexed. 10 | 11 | | Prop Name | Type | Default Value | 12 | | :------------ | :----- | :------------ | 13 | | `initialPage` | number | `0` | 14 | 15 | ### Example 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | #### Code 24 | 25 | ```tsx 26 | 27 | 28 | 29 | 30 | 31 | ``` 32 | -------------------------------------------------------------------------------- /docs/api/methods.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 80 3 | --- 4 | 5 | import { BasicButtonDemo } from '../../website/src/components/basic-button-demo'; 6 | import Tabs from '@theme/Tabs'; 7 | import TabItem from '@theme/TabItem'; 8 | 9 | # Methods 10 | 11 | Nuka Carousel has a few exposed methods that allow the user to control certain parts of the carousel manually. These methods are accessed via a ref attached to the carousel. 12 | 13 | ```tsx title="MyComponent.tsx" 14 | import { useRef } from 'react'; 15 | import Carousel, { SlideHandle } from 'nuka-carousel'; 16 | 17 | const MyComponent = () => { 18 | const ref = useRef(null); 19 | 20 | return ( 21 |
22 | ... 23 |
24 | ); 25 | }; 26 | ``` 27 | 28 | --- 29 | 30 | ## Progression 31 | 32 | The carousel can be advanced forward and backwards based on the `scrollDistance` defined from the props. 33 | 34 | ### `goForward()` 35 | 36 | Advances the carousel forward by the given `scrollDistance`. 37 | 38 | ### `goBack()` 39 | 40 | Advances the carousel backward by the given `scrollDistance`. 41 | 42 | ### `goToPage()` 43 | 44 | Advances the carousel to the specified page. 45 | 46 | ### Usage/Examples 47 | 48 | 49 | 50 | #### Code 51 | 52 | ```tsx title="MyComponent.tsx" 53 | import { useRef } from 'react'; 54 | import Carousel, { SlideHandle } from 'nuka-carousel'; 55 | 56 | const MyComponent = () => { 57 | const ref = useRef(null); 58 | 59 | return ( 60 |
61 | ... 62 | 63 | 64 | 65 |
66 | ); 67 | }; 68 | ``` 69 | -------------------------------------------------------------------------------- /docs/api/navigation.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | import { Carousel } from 'nuka-carousel'; 6 | 7 | # Navigation 8 | 9 | The carousel supports several navigation options, including arrows and dots. 10 | You can also control whether the carousel should wrap around when the first or last slide is active. 11 | 12 | | Prop Name | Type | Default Value | 13 | | :----------------- | :------- | :------------ | 14 | | `showArrows` | boolean \| `always` \| `hover` | `false` | 15 | | `showDots` | boolean | `false` | 16 | | `wrapMode` | `nowrap` \| `wrap` | `nowrap` | 17 | 18 | ### Navigation Arrows 19 | 20 | When set to `true` or `always`, the arrows will always be visible. When set to `hover`, the arrows will be visible only when the mouse is over the carousel. 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | #### Code 29 | 30 | ```tsx 31 | 32 | 33 | 34 | 35 | 36 | ``` 37 | 38 | ### Navigation Arrows (hover) 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | #### Code 47 | 48 | ```tsx 49 | 50 | 51 | 52 | 53 | 54 | ``` 55 | 56 | ### Wrapping 57 | 58 | If you set `wrapMode` to `wrap`, the carousel will wrap around when it reaches the end of the slide set. This means that if you are on the last slide and you click next, it will go to the first slide. 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | #### Code 67 | 68 | ```tsx 69 | 70 | 71 | 72 | 73 | 74 | ``` 75 | 76 | ### Navigation Dots 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | #### Code 85 | 86 | ```tsx 87 | 88 | 89 | 90 | 91 | 92 | ``` 93 | -------------------------------------------------------------------------------- /docs/api/scroll.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | import { Carousel } from 'nuka-carousel'; 6 | import Tabs from '@theme/Tabs'; 7 | import TabItem from '@theme/TabItem'; 8 | 9 | # Scroll 10 | 11 | How far the carousel should move when its goForward() and goBack() methods are called. 12 | 13 | | Prop Name | Type | Default Value | 14 | | :--------------- | :------------------------------ | :------------ | 15 | | `scrollDistance` | "screen" \| "slide" \| `number` | `screen` | 16 | 17 | ### Screen (default) 18 | 19 | Scroll by the width of the "screen" or the container the carousel is in. 20 | 21 | 22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | 41 | 42 | #### Code 43 | 44 | ```tsx 45 | 46 | 47 | 48 | 49 | 50 | ``` 51 | 52 | ### Slide 53 | 54 | Scroll by the width of each slide. Slides can be different widths and the carousel will scroll adjust the scroll distance internally when the container size is changed. 55 | 56 | 57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | 76 | 77 | #### Code 78 | 79 | ```tsx 80 | 81 | 82 | 83 | 84 | 85 | ``` 86 | 87 | ### Fixed Distance (number) 88 | 89 | Scroll by a fixed distance measured in px. This example scrolls by 100px. 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | #### Code 98 | 99 | ```tsx 100 | 101 | 102 | 103 | 104 | 105 | ``` 106 | -------------------------------------------------------------------------------- /docs/api/styling.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 5 3 | --- 4 | 5 | import { Carousel } from 'nuka-carousel'; 6 | import Tabs from '@theme/Tabs'; 7 | import TabItem from '@theme/TabItem'; 8 | import CodeBlock from '@theme/CodeBlock'; 9 | 10 | import { CustomDots } from '../../website/src/components/custom-dots-demo'; 11 | import { CustomArrows } from '../../website/src/components/custom-arrows-demo'; 12 | 13 | # Styling 14 | 15 | Apply css classes to the carousel container. This is the property you would use if you want to constrain the width or height of the carousel. 16 | 17 | | Prop Name | Type | Default Value | 18 | | :---------- | :----- | :------------ | 19 | | `className` | string | `undefined` | 20 | 21 | #### Example 22 | 23 | 28 | 29 | 30 | 31 | 32 | 33 | #### Code 34 | 35 | ```tsx 36 | 41 | 42 | 43 | 44 | 45 | ``` 46 | 47 | ## Navigation Dots 48 | 49 | You can supply a custom React component to render the navigation dots by using the Carousel hooks. 50 | 51 | | Prop Name | Type | Default Value | 52 | | :------------------------------ | :----- | :------------ | 53 | | `dots` | `ReactNode` | `undefined` | 54 | 55 | #### Example 56 | 57 | }> 58 | 59 | 60 | 61 | 62 | 63 | #### Code 64 | 65 | ```tsx 66 | import { useCarousel } from 'nuka-carousel'; 67 | 68 | export const CustomDots = () => { 69 | const { totalPages, currentPage, goToPage } = useCarousel(); 70 | 71 | const className = (index: number) => { 72 | let value = 73 | 'w-3 h-3 p-0 rounded-full bg-gray-200 border-none cursor-pointer hover:bg-green-200'; 74 | if (currentPage === index) { 75 | value += ' bg-green-500 hover:bg-green-500'; 76 | } 77 | return value; 78 | }; 79 | 80 | return ( 81 |
82 | {[...Array(totalPages)].map((_, index) => ( 83 |
90 | ); 91 | }; 92 | 93 | }> 94 | 95 | 96 | 97 | 98 | ``` 99 | 100 | ## Navigation Arrows 101 | 102 | You can supply a custom React component to render the navigation arrows by using the Carousel hooks. 103 | 104 | | Prop Name | Type | Default Value | 105 | | :------------------------------ | :----- | :------------ | 106 | | `arrows` | `ReactNode` | `undefined` | 107 | 108 | #### Example 109 | 110 | }> 111 | 112 | 113 | 114 | 115 | 116 | #### Code 117 | 118 | ```tsx 119 | import { useCarousel } from 'nuka-carousel'; 120 | 121 | function cls(...classes) { 122 | return classes.filter(Boolean).join(' '); 123 | } 124 | 125 | export function CustomArrows() { 126 | const { currentPage, totalPages, wrapMode, goBack, goForward } = 127 | useCarousel(); 128 | 129 | const allowWrap = wrapMode === 'wrap'; 130 | const enablePrevNavButton = allowWrap || currentPage > 0; 131 | const enableNextNavButton = allowWrap || currentPage < totalPages - 1; 132 | 133 | const prevNavClassName = cls( 134 | 'inline-block px-4 py-2 bg-pink-800 cursor-pointer invisible', 135 | enablePrevNavButton && '!visible', 136 | ); 137 | 138 | const nextNavClassName = cls( 139 | 'inline-block px-4 py-2 bg-pink-800 cursor-pointer invisible', 140 | enableNextNavButton && '!visible', 141 | ); 142 | 143 | return ( 144 |
145 |
146 | PREV 147 |
148 |
149 | NEXT 150 |
151 |
152 | ); 153 | } 154 | 155 | }> 156 | 157 | 158 | 159 | 160 | ``` 161 | -------------------------------------------------------------------------------- /docs/api/swiping.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | import { Carousel } from 'nuka-carousel'; 6 | import Tabs from '@theme/Tabs'; 7 | import TabItem from '@theme/TabItem'; 8 | 9 | # Swiping 10 | 11 | By default the carousel will allow you to drag the carousel to the next slide. You can disable this by setting the `swiping` prop to `false`. This is only enabled on mobile devices. 12 | 13 | | Prop Name | Type | Default Value | 14 | | :--------- | :-------- | :------------ | 15 | | `minSwipeDistance` | `number` | 50 | 16 | | `swiping` | `boolean` | `true` | 17 | 18 | ### Enabled with `scrollDistance="slide"` 19 | 20 | 21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | 32 | 33 | #### Code 34 | 35 | ```tsx 36 | 37 | 38 | 39 | 40 | 41 | ``` 42 | 43 | ### Enabled with `scrollDistance="screen"` 44 | 45 | 46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | 57 | 58 | #### Code 59 | 60 | ```tsx 61 | 62 | 63 | 64 | 65 | 66 | ``` 67 | 68 | ### Disabled 69 | 70 | 71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 | 82 | 83 | #### Code 84 | 85 | ```tsx 86 | 87 | 88 | 89 | 90 | 91 | ``` 92 | -------------------------------------------------------------------------------- /docs/introduction.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | title: Introduction 4 | slug: / 5 | --- 6 | 7 | import { FullFeatureDemo } from '../website/src/components/full-feature-demo'; 8 | 9 | Small, fast, and accessibility-first React carousel library with easily customizable UI and behavior to fit your brand and site. 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/v7-upgrade-guide.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 7 3 | title: 'v7 Upgrade Guide' 4 | --- 5 | 6 | Nuka Carousel 7 is a major release that introduces changes to make the carousel better align with the WCAG Aria Authoring Practices Guide. This guide will help you upgrade from v6 to v7. 7 | 8 | ## New props 9 | 10 | - [`tabbed`](api#tabbed) determines whether roles should follow the WCAG tabbed carousel requirements. 11 | - [`landmark`](api#landmark) prop determines whether role should be a region landmark or have role group. 12 | - [`carouselId`](api#carouselid) prop gives the carousel frame an id attribute which can be uniquely targeted by controls. 13 | 14 | ## Changes in functionality 15 | 16 | - The `frameAriaLabel` default value has been changed to 'slider' 17 | - Default pagination dots have been given the role `tab` and aria-controls will target their slide. 18 | - Pagination dots parent has been given the role `tablist`. As tabs need to be the immediate child of `tablist`, and since `tablist` already has list semantics, `ul` and `li` have been removed. 19 | - When the `tabbed` prop is `true`, slides will have the role `tabpanel` indicating they are the target of the tab, otherwise they will have the role `group` and a description of slide. 20 | - When the `tabbed` prop is `false`, default pagination dots will not be rendered. 21 | - Default next and previous buttons now target the slider frame with aria-controls. 22 | 23 | ## Changes in dev tooling 24 | 25 | - This release adds `jest-axe` and their corresponding as dev dependencies to ensure Nuka’s test suite verifies accessibility. 26 | -------------------------------------------------------------------------------- /docs/v8-upgrade-guide.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | import Link from '@docusaurus/Link'; 6 | 7 | # v8 Upgrade Guide 8 | 9 | Nuka v8 was rewritten with simplicity and native support in mind. Many props have been removed or renamed to make the API easier to understand and use. This guide will help you upgrade your v7 carousel to v8. 10 | 11 | --- 12 | 13 | ## Changed Props 14 | 15 | - `afterSlide` - API signature changed from v7. See the callbacks for more information. 16 | - `beforeSlide` - API signature changed from v7. See the callbacks for more information. 17 | - `carouselId` - Use the `id` prop instead. 18 | - `disableAnimation` - See the navigation docs for the `autoPlay` property. 19 | - `dragging` - Enabled by default, see the swiping docs for the `swiping` property. 20 | - `enableKeyboardControls` - Enabled by default, see the accessibility docs for the `keyboard` property. 21 | - `frameAriaLabel` - Replaced by the `title` prop. See the accessibility docs. 22 | - `slidesToScroll` - Renamed to scrollDistance. See the scrolling docs for more information. 23 | 24 | --- 25 | 26 | ## Removed Props 27 | 28 | The following props were removed becuase they are no longer valid or replaced by built in functionality. If there is any functionality lost, we may consider adding some of these back in the future. 29 | 30 | - `adaptiveHeight` - The carousel will adapt automatically to the height of the items in the carousel without the need of this prop. 31 | - `adaptiveHeightAnimation` - The carousel will adapt automatically to the height of the items in the carousel without the need of this prop. 32 | - `cellAlign` - Controlled through CSS. See the style guide. 33 | - `cellSpacing` - Controlled through CSS. See the style guide. 34 | - `defaultControlsConfig`- Replaced by various properties like `showArrows`, `showDots`, `arrows`, `dots`. 35 | - `disableEdgeSwiping` 36 | - `dragThreshold` - this defaults to the OS/browser settings. 37 | - `easing` 38 | - `edgeEasing` 39 | - `keyCodeConfig` 40 | - `landmark` 41 | - `onDragStart` 42 | - `onDrag` 43 | - `onDragEnd` 44 | - `onUserNavigation` 45 | - `pauseOnHover` - Enabled by default. See the autoplay docs. 46 | - `renderTop{direction}Controls` 47 | - `scrollMode` - Defaults to `remainder`. 48 | - `slideIndex` - Use initialPage to start on a certain page, use goToPage to change indices on command. 49 | - `slidesToShow` - Now based on media queries and how large the slides are. 50 | - `speed` - Controlled by native browser settings. 51 | - `style` - See the style guide. 52 | - `tabbed` - Enabled by default, see the accessibility docs 53 | - `withoutControls`- Replaced by various properties like `showArrows`, `showDots`, `arrows`, `dots`. 54 | - `zoomScale` 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuka-carousel-monorepo", 3 | "scripts": { 4 | "build": "pnpm run --filter nuka-carousel build", 5 | "build:watch": "pnpm run --filter nuka-carousel build:watch", 6 | "build:storybook": "pnpm run --filter nuka-carousel storybook:build", 7 | "build-storybook": "pnpm run --filter nuka-carousel storybook:build", 8 | "build:website": "pnpm run --filter website build", 9 | "lint": "pnpm run --parallel lint", 10 | "lint:fix": "pnpm run --parallel lint --fix", 11 | "start:storybook": "pnpm run --filter nuka-carousel storybook", 12 | "start:website": "pnpm run --filter website start", 13 | "test": "pnpm run --filter nuka-carousel test", 14 | "test:storybook": "pnpm run --filter nuka-carousel test:storybook", 15 | "check": "pnpm run lint && pnpm run test", 16 | "check:ci": "pnpm run check", 17 | "clean": "pnpm run -r clean", 18 | "changeset": "changeset", 19 | "version": "pnpm changeset version && pnpm install --no-frozen-lockfile" 20 | }, 21 | "devDependencies": { 22 | "@babel/cli": "^7.24.1", 23 | "@babel/core": "^7.24.3", 24 | "@babel/eslint-parser": "^7.24.1", 25 | "@babel/plugin-transform-object-assign": "^7.24.1", 26 | "@babel/plugin-transform-object-rest-spread": "^7.24.1", 27 | "@babel/plugin-transform-runtime": "^7.24.3", 28 | "@babel/preset-env": "^7.24.3", 29 | "@babel/preset-react": "^7.24.1", 30 | "@babel/preset-typescript": "^7.24.1", 31 | "@changesets/cli": "^2.23.1", 32 | "@storybook/test-runner": "^0.16.0", 33 | "@svitejs/changesets-changelog-github-compact": "^0.1.1", 34 | "@types/jest": "^27.0.2", 35 | "@typescript-eslint/eslint-plugin": "^7.3.1", 36 | "@typescript-eslint/parser": "^7.3.1", 37 | "eslint": "^8.57.0", 38 | "eslint-config-prettier": "^9.1.0", 39 | "eslint-plugin-prettier": "^5.1.3", 40 | "eslint-plugin-react": "^7.34.1", 41 | "eslint-plugin-react-hooks": "^4.6.0", 42 | "prettier": "^3.2.5", 43 | "react": "^18.0.0", 44 | "react-dom": "^18.0.0", 45 | "shx": "^0.3.4", 46 | "typescript": "^5.4.2" 47 | }, 48 | "engines": { 49 | "node": ">=18.0.0" 50 | } 51 | } -------------------------------------------------------------------------------- /packages/nuka/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.babelrc" 3 | } 4 | -------------------------------------------------------------------------------- /packages/nuka/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | es 4 | types 5 | -------------------------------------------------------------------------------- /packages/nuka/.npmignore: -------------------------------------------------------------------------------- 1 | /* 2 | !lib/**/* 3 | !es/**/* 4 | -------------------------------------------------------------------------------- /packages/nuka/.storybook/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'], 3 | 4 | addons: [ 5 | '@storybook/addon-links', 6 | '@storybook/addon-essentials', 7 | '@storybook/addon-interactions', 8 | ], 9 | 10 | framework: { 11 | name: '@storybook/react-webpack5', 12 | options: {}, 13 | }, 14 | 15 | docs: { 16 | autodocs: true, 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /packages/nuka/.storybook/preview.js: -------------------------------------------------------------------------------- 1 | export const parameters = { 2 | actions: { argTypesRegex: '^on[A-Z].*' }, 3 | controls: { 4 | matchers: { 5 | color: /(background|color)$/i, 6 | date: /Date$/, 7 | }, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /packages/nuka/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Nuka Changelog 2 | 3 | ## 8.2.0 4 | 5 | ### Minor Changes 6 | 7 | - Support initial index for slides ([`4908a05c9dc655362d2df5e0fcb0f558d9f57dc9`](https://github.com/FormidableLabs/nuka-carousel/commit/4908a05c9dc655362d2df5e0fcb0f558d9f57dc9)) 8 | 9 | ## 8.1.1 10 | 11 | ### Patch Changes 12 | 13 | - Improve the stability of swiping ([#1077](https://github.com/FormidableLabs/nuka-carousel/pull/1077)) 14 | 15 | ## 8.1.0 16 | 17 | ### Minor Changes 18 | 19 | - feat: provide page info on beforeSlide and afterSlide callbacks ([#1070](https://github.com/FormidableLabs/nuka-carousel/pull/1070)) 20 | 21 | ### Patch Changes 22 | 23 | - fix carousel missing last page if it is less than half of the container width in scrollDistance screen ([#1064](https://github.com/FormidableLabs/nuka-carousel/pull/1064)) 24 | 25 | ## 8.0.1 26 | 27 | ### Patch Changes 28 | 29 | - Fix screen measurement when scrollWidth is a float ([#1055](https://github.com/FormidableLabs/nuka-carousel/pull/1055)) 30 | 31 | * Prevent divide by zero errors in measurements ([#1058](https://github.com/FormidableLabs/nuka-carousel/pull/1058)) 32 | 33 | ## 8.0.0 34 | 35 | ### Major Changes 36 | 37 | - Nuka v8 Release ([#1047](https://github.com/FormidableLabs/nuka-carousel/pull/1047)) 38 | 39 | ## 7.0.0 40 | 41 | ### Major Changes 42 | 43 | - Adjusts roles and aria attributes. Adds new "carouselId", "tabbed", and "landmark" props. ([#1036](https://github.com/FormidableLabs/nuka-carousel/pull/1036)) 44 | 45 | ## 6.0.3 46 | 47 | ### Patch Changes 48 | 49 | - Fix peer dependency version for React in package. ([#1017](https://github.com/FormidableLabs/nuka-carousel/pull/1017)) 50 | 51 | ## 6.0.2 52 | 53 | ### Patch Changes 54 | 55 | - Add package provenance ([#1015](https://github.com/FormidableLabs/nuka-carousel/pull/1015)) 56 | 57 | ## 6.0.1 58 | 59 | ### Patch Changes 60 | 61 | - Fix output of types. ([#1010](https://github.com/FormidableLabs/nuka-carousel/pull/1010)) 62 | 63 | ## 6.0.0 64 | 65 | ### Major Changes 66 | 67 | - replace innerRef prop with a native-to-React ref prop to allow the nullablity of the ref ([#1006](https://github.com/FormidableLabs/nuka-carousel/pull/1006)) 68 | new docs page with demo 69 | 70 | ## 5.5.1 71 | 72 | ### Patch Changes 73 | 74 | - Fix to only include inert property when slide is not in view ([#1000](https://github.com/FormidableLabs/nuka-carousel/pull/1000)] @allypalanzi 75 | 76 | ## 5.5.0 77 | 78 | ### Minor Changes 79 | 80 | - Implement fixed width slides with intersection observer ([#985](https://github.com/FormidableLabs/nuka-carousel/pull/985)) 81 | - Fix cellAlignment issue 82 | - Enforce `scrollMode` `remainder` if `slidesToScroll` `auto` is used 83 | 84 | ## 5.4.1 85 | 86 | ### Patch Changes 87 | 88 | - slider-frame is no longer a focus target if enableKeyboardControls is false ([#984](https://github.com/FormidableLabs/nuka-carousel/pull/984)) 89 | 90 | * fix the Alignment type so library users don't need to import an enum for a simple value ([#974](https://github.com/FormidableLabs/nuka-carousel/pull/974)) 91 | 92 | ## 5.4.0 93 | 94 | ### Minor Changes 95 | 96 | - add onUserNavigation prop to listen for user-triggered navigation ([#970](https://github.com/FormidableLabs/nuka-carousel/pull/970)) 97 | 98 | * allow for any element or component in the nextButtonText and prevButtonText props, instead of just strings ([#970](https://github.com/FormidableLabs/nuka-carousel/pull/970)) 99 | 100 | - pass nextDisabled, previousDisabled, and pagingDotsIndices to render\*Controls callbacks to aid in the creation of custom controls ([#966](https://github.com/FormidableLabs/nuka-carousel/pull/966)) 101 | 102 | * adds the ability to use custom easing functions for the animations via `easing` and `edgeEasing` ([#959](https://github.com/FormidableLabs/nuka-carousel/pull/959)) 103 | 104 | - add props to enable users to hook into interactions with the default carousel controls ([#970](https://github.com/FormidableLabs/nuka-carousel/pull/970)) 105 | 106 | ### Patch Changes 107 | 108 | - animation=fade now behaves as it did pre-5.3.0, scrolling all visible slides at once ([#959](https://github.com/FormidableLabs/nuka-carousel/pull/959)) 109 | 110 | * make the control dots change in size when dimensions changed via CSS ([#964](https://github.com/FormidableLabs/nuka-carousel/pull/964)) 111 | 112 | - Applies draggable=false only to children of sliderList, not entire document ([#972](https://github.com/FormidableLabs/nuka-carousel/pull/972)) 113 | 114 | ## 5.3.0 115 | 116 | ### Minor Changes 117 | 118 | - makes it possible to swipe over multiple slides at once rather than being limited to one ([#956](https://github.com/FormidableLabs/nuka-carousel/pull/956)) 119 | 120 | ### Patch Changes 121 | 122 | - Fix autoplay timing so the interval is not extended occasionally when wrapMode=true ([#954](https://github.com/FormidableLabs/nuka-carousel/pull/954)) 123 | 124 | * make the prev/next buttons jump to appropriate indices with cellAlign=center|right ([#952](https://github.com/FormidableLabs/nuka-carousel/pull/952)) 125 | 126 | - keyboard-triggered "firstSlide" or "lastSlide" actions are now animated, and now take cellAlign into account ([#958](https://github.com/FormidableLabs/nuka-carousel/pull/958)) 127 | 128 | * make autoplay continue to the last slide when cellAlign != left and slidesToShow > 1 ([#952](https://github.com/FormidableLabs/nuka-carousel/pull/952)) 129 | 130 | - children of Carousel that are falsy will not be rendered as slides ([#953](https://github.com/FormidableLabs/nuka-carousel/pull/953)) 131 | 132 | * fix missing slide-visible classes when slideIndex has fractional digits ([#947](https://github.com/FormidableLabs/nuka-carousel/pull/947)) 133 | 134 | - correct button disabling when cellAlign=center|right ([#952](https://github.com/FormidableLabs/nuka-carousel/pull/952)) 135 | 136 | * show a dot corresponding to the last slide when cellAlign=center|right ([#952](https://github.com/FormidableLabs/nuka-carousel/pull/952)) 137 | 138 | - fixes number of dots in default controls and eliminates janky animation when changing slides rapidly ([#945](https://github.com/FormidableLabs/nuka-carousel/pull/945)) 139 | 140 | * setting "dragging" to false will no longer disable carousel swiping on mobile ("swiping" does that) ([#956](https://github.com/FormidableLabs/nuka-carousel/pull/956)) 141 | 142 | - keyboard interactions when focused on the carousel are now kept from bubbling up and triggering other listeners ([#958](https://github.com/FormidableLabs/nuka-carousel/pull/958)) 143 | 144 | * fix initial index when autoplayReverse=true and cellAlign is not left ([#952](https://github.com/FormidableLabs/nuka-carousel/pull/952)) 145 | 146 | ## 4.8.3 (2021-11-19) 147 | 148 | - [#810](https://github.com/FormidableLabs/nuka-carousel/pull/810) fix: Last image is not rendered with 3 slides 149 | 150 | ## 4.8.2 (2021-10-29) 151 | 152 | - [#808](https://github.com/FormidableLabs/nuka-carousel/pull/808) update webpack deps 153 | - [#807](https://github.com/FormidableLabs/nuka-carousel/pull/807) Fix: improve pinch zoom behavior on mobile 154 | 155 | ## 4.8.1 (2021-10-22) 156 | 157 | - [#804](https://github.com/FormidableLabs/nuka-carousel/pull/804) Fix: flip inequality check in initializeCarouselHeight 158 | - [#805](https://github.com/FormidableLabs/nuka-carousel/pull/805) Fix: prevent initializeCarouselHeight loop to fix browser crash 159 | 160 | ## 4.8.0 (2021-09-21) 161 | 162 | - [#802](https://github.com/FormidableLabs/nuka-carousel/pull/802) Minor A11y improvements 163 | - [#801](https://github.com/FormidableLabs/nuka-carousel/pull/801) Fix lint errors 164 | - [#800](https://github.com/FormidableLabs/nuka-carousel/pull/800) Dep bumps 165 | - [#799](https://github.com/FormidableLabs/nuka-carousel/pull/799) Dep bumps 166 | - [#796](https://github.com/FormidableLabs/nuka-carousel/pull/796) Perf: Use more modern screen-reader only styles 167 | - [#794](https://github.com/FormidableLabs/nuka-carousel/pull/794) Dep bumps 168 | 169 | ## 4.7.9 (2021-07-13) 170 | 171 | - [#791](https://github.com/FormidableLabs/nuka-carousel/pull/791) Fix: bind blockEvent method to `this` 172 | - [#785](https://github.com/FormidableLabs/nuka-carousel/pull/785) Fix: update react-move 173 | - [#781](https://github.com/FormidableLabs/nuka-carousel/pull/781) Fix: Improve SVG dot A11y 174 | - [#779](https://github.com/FormidableLabs/nuka-carousel/pull/779) Dep bumps 175 | - [#777](https://github.com/FormidableLabs/nuka-carousel/pull/777) 176 | - [#776](https://github.com/FormidableLabs/nuka-carousel/pull/776) 177 | 178 | ## 4.7.8 (2021-05-10) 179 | 180 | - [#766](https://github.com/FormidableLabs/nuka-carousel/pull/766) Dep bumps 181 | - [#767](https://github.com/FormidableLabs/nuka-carousel/pull/767) 182 | - [#770](https://github.com/FormidableLabs/nuka-carousel/pull/770) 183 | - [#771](https://github.com/FormidableLabs/nuka-carousel/pull/771) 184 | - [#772](https://github.com/FormidableLabs/nuka-carousel/pull/772) 185 | - [#774](https://github.com/FormidableLabs/nuka-carousel/pull/774) Allow React 17 Peer Dep 186 | - [#775](https://github.com/FormidableLabs/nuka-carousel/pull/775) Check offset type 187 | 188 | ## 4.7.6 (2021-03-15) 189 | 190 | - [#765](https://github.com/FormidableLabs/nuka-carousel/pull/765) Fixes #732 - remove preventDefault from touch handler 191 | - [#764](https://github.com/FormidableLabs/nuka-carousel/pull/764) Set up GH Actions 192 | - [#763](https://github.com/FormidableLabs/nuka-carousel/pull/763) Dep bumps 193 | - [#760](https://github.com/FormidableLabs/nuka-carousel/pull/760) Fixes: #754 - Center cellAlign bug 194 | 195 | ## 4.7.5 (2020-12-30) 196 | 197 | - [#746](https://github.com/FormidableLabs/nuka-carousel/pull/746) Fixes #705 - allow peeking slides when slide count is less than 3, and using Zoom animation 198 | - [#747](https://github.com/FormidableLabs/nuka-carousel/pull/747) Set proper offset on first render for slider-list 199 | - [#750](https://github.com/FormidableLabs/nuka-carousel/pull/750) Fixes #749 - Updates to `setDimensions` method to fix video height bug 200 | 201 | ## 4.7.4 (2020-11-30) 202 | 203 | - [#745](https://github.com/FormidableLabs/nuka-carousel/pull/745) Add transition when heightMode is set to 'current' 204 | - [#742](https://github.com/FormidableLabs/nuka-carousel/pull/742) ariaProps should not override child props values 205 | - [#739](https://github.com/FormidableLabs/nuka-carousel/pull/739) Fix non-clickable slides and several bugs in paging dots display 206 | 207 | ## 4.7.3 (2020-11-24) 208 | 209 | - [#740](https://github.com/FormidableLabs/nuka-carousel/pull/740) Update Wicg-inert and puppeteer 210 | - [#737](https://github.com/FormidableLabs/nuka-carousel/pull/737) Types update: fix PreviousButton, NextButton, and PagingDots def 211 | - [#734](https://github.com/FormidableLabs/nuka-carousel/pull/734) Fix: showing partial slides breaks navigation dots 212 | 213 | ## 4.7.2 (2020-10-30) 214 | 215 | - [#733](https://github.com/FormidableLabs/nuka-carousel/pull/733) Fix package vulnerability 216 | - [#731](https://github.com/FormidableLabs/nuka-carousel/pull/731) Update onDragStart type 217 | - [#729](https://github.com/FormidableLabs/nuka-carousel/pull/729) Type def updates 218 | - [#728](https://github.com/FormidableLabs/nuka-carousel/pull/728) Add opacityScale to type defs 219 | 220 | ## 4.7.1 (2020-09-08) 221 | 222 | - [#706](https://github.com/FormidableLabs/nuka-carousel/pull/706) Add a more descriptive explanation about wrapMode 223 | - [#707](https://github.com/FormidableLabs/nuka-carousel/pull/707) Push fresh yarn.lock 224 | - [#716](https://github.com/FormidableLabs/nuka-carousel/pull/716) Update types for missing slideOffset, zoomScale 225 | - [#717](https://github.com/FormidableLabs/nuka-carousel/pull/717) fixes #709 - Carousel collapsing with certain prop config 226 | - [#723](https://github.com/FormidableLabs/nuka-carousel/pull/723) Cleanup dev dependencies, fix vulnerabilities, eslint fixes 227 | - [#698, #714, #722](https://github.com/FormidableLabs/nuka-carousel/pull/698, https://github.com/FormidableLabs/nuka-carousel/pull/714, https://github.com/FormidableLabs/nuka-carousel/pull/722) Dependency bumps 228 | 229 | ## 4.7.0 (2020-05-20) 230 | 231 | - [#695](https://github.com/FormidableLabs/nuka-carousel/pull/695) Add `slide-current` className to current 'active' slide, cleanup some utility functions 232 | - [#667](https://github.com/FormidableLabs/nuka-carousel/pull/667) Fixes Issue 622: Only fully visible slides are interactive. update wicg-inert version 233 | 234 | ## 4.6.7 (2020-04-23) 235 | 236 | - [#687](https://github.com/FormidableLabs/nuka-carousel/pull/687) Fix Edge issue 237 | - [#683](https://github.com/FormidableLabs/nuka-carousel/pull/683) Rename param of afterSlide callback 238 | - [#680](https://github.com/FormidableLabs/nuka-carousel/pull/680) Fixes bug #586 - changing slide too quickly doesn't behave as expected 239 | - [#677](https://github.com/FormidableLabs/nuka-carousel/pull/677) Update incorrect prop `getControlsContainerStyle` 240 | 241 | ## 4.6.6 (2020-03-10) 242 | 243 | - [#676](https://github.com/FormidableLabs/nuka-carousel/pull/676) hot-fix: flash after wrapMode 244 | 245 | ## 4.6.5 (2020-03-05) 246 | 247 | - [#674](https://github.com/FormidableLabs/nuka-carousel/pull/674) Upgrade react-move to reduce bundle size 248 | - [#673](https://github.com/FormidableLabs/nuka-carousel/pull/673) Remove deprecated UNSAFE_componentWillReceiveProps 249 | 250 | ## 4.6.4 (2020-03-03) 251 | 252 | - [#672](https://github.com/FormidableLabs/nuka-carousel/pull/672) Add innerRef to type definitions 253 | - [#671](https://github.com/FormidableLabs/nuka-carousel/pull/671) Add innerRef prop 254 | - [#670](https://github.com/FormidableLabs/nuka-carousel/pull/670) Add scrollMode prop 255 | - [#669](https://github.com/FormidableLabs/nuka-carousel/pull/669) Fix getControlsContainerStyle prop name type in type definitions 256 | - [#668](https://github.com/FormidableLabs/nuka-carousel/pull/668) Fix edge swipe bug 257 | - [#666](https://github.com/FormidableLabs/nuka-carousel/pull/666) Add ability to drag to scroll multiple slides 258 | 259 | ## 4.6.3 (2020-02-12) 260 | 261 | - [#665](https://github.com/FormidableLabs/nuka-carousel/pull/665) Revert wicg-inert changes as part of PR #643 262 | 263 | ## 4.6.2 (2020-01-29) 264 | 265 | - [#656](https://github.com/FormidableLabs/nuka-carousel/pull/656) 266 | - [#655](https://github.com/FormidableLabs/nuka-carousel/pull/655) 267 | - [#653](https://github.com/FormidableLabs/nuka-carousel/pull/653) Minor clean up and fixes - update demo to use hooks, add demo title, 268 | fix slidesToScroll typing, cleaning up prop names, minor perf improvement 269 | - [#652](https://github.com/FormidableLabs/nuka-carousel/pull/652) fix: add missing type annotation 270 | - [#651](https://github.com/FormidableLabs/nuka-carousel/pull/651) Fix typo 271 | - [#650](https://github.com/FormidableLabs/nuka-carousel/pull/650) Change touchAction for fade-transition to pinch-zoom 272 | - [#648](https://github.com/FormidableLabs/nuka-carousel/pull/648) Add autoplay toggle to demo 273 | - [#647](https://github.com/FormidableLabs/nuka-carousel/pull/647) Prevent fade and fadeFromSlide from reaching/equalling slideCount to solve opacity issue 274 | - [#645](https://github.com/FormidableLabs/nuka-carousel/pull/645) Prevent swipe logic from taking over when no swipe was made 275 | - [#644](https://github.com/FormidableLabs/nuka-carousel/pull/644) Add prop to allow for customization of defaultControls elements 276 | - [#643](https://github.com/FormidableLabs/nuka-carousel/pull/643) Slides that are not fully visible cannot receive focus (REVERTED in 4.6.3) 277 | - [#639](https://github.com/FormidableLabs/nuka-carousel/pull/639) Keyboard controls will only work when keyboard is in focus 278 | 279 | ## 4.6.1 (2020-01-20) 280 | 281 | - [#632](https://github.com/FormidableLabs/nuka-carousel/pull/632) Dependency vulnerability fixed, upgrade handlebars from 4.1.2 to 4.5.3 282 | - [#633](https://github.com/FormidableLabs/nuka-carousel/pull/633) Fixes #618 max (and first) height mode calculations 283 | - [#635](https://github.com/FormidableLabs/nuka-carousel/pull/635) Fixes #494 Updates scroll transition logic to fix wrapMode flash 284 | - [#636](https://github.com/FormidableLabs/nuka-carousel/pull/636) Fixes #503 updates logic so Next button enables/disables correctly 285 | - [#638](https://github.com/FormidableLabs/nuka-carousel/pull/638) Fixes #531 styling issue in Demo and some minor cleanup of the code 286 | 287 | ## 4.6.0 (2019-12-17) 288 | 289 | - Fixes for `leftAlign` added for heightMode="current" and heightMode="max" 290 | - [#614](https://github.com/FormidableLabs/nuka-carousel/pull/614) Remove getListItemStyles() from type definitions 291 | - [#619](https://github.com/FormidableLabs/nuka-carousel/pull/619) Configure keyboard keyCodes so default ones can be overridden 292 | - [#620](https://github.com/FormidableLabs/nuka-carousel/pull/620) Avoid redundant dimension calculations after a DOM mutation 293 | - [#621](https://github.com/FormidableLabs/nuka-carousel/pull/621) Add TS definition for renderAnnounceSlideMessage 294 | - [#625](https://github.com/FormidableLabs/nuka-carousel/pull/625) Fixes Issue 521: Initial height calculations will be repeated until successful. 295 | - [#626](https://github.com/FormidableLabs/nuka-carousel/pull/626) Height mode 'current' uses tallest visible slide 296 | - [#628](https://github.com/FormidableLabs/nuka-carousel/pull/628) Adding missing Type For keyCodeConfig prop 297 | 298 | ## 4.5.13 (2019-11-08) 299 | 300 | - [#592](https://github.com/FormidableLabs/nuka-carousel/pull/592) change componentWillReceiveProps to UNSAFE_componentWillReceiveProps 301 | - [#600](https://github.com/FormidableLabs/nuka-carousel/pull/600) Fix wrapMode logic to account for cellAlign property 302 | - [#601](https://github.com/FormidableLabs/nuka-carousel/pull/601) Change dot styling 303 | - [#608](https://github.com/FormidableLabs/nuka-carousel/pull/608) Fix dragging issue in Safari 304 | - [#609](https://github.com/FormidableLabs/nuka-carousel/pull/609) Prevent scroll when dragging on iOS 305 | 306 | ## 4.5.12 (2019-09-13) 307 | 308 | - [#582](https://github.com/FormidableLabs/nuka-carousel/pull/582) Another attempt to fix the height issue by changing the default prop value for height 309 | - [#584](https://github.com/FormidableLabs/nuka-carousel/pull/584) Fix multi-slide wrapMode 310 | - [#585](https://github.com/FormidableLabs/nuka-carousel/pull/585) Fix onDragStart, should only happen with dragging/swiping 311 | - [#588](https://github.com/FormidableLabs/nuka-carousel/pull/588) Impossible to select last images when swiping, this fixes that 312 | 313 | ## 4.5.11 (2019-09-03) 314 | 315 | - [#578](https://github.com/FormidableLabs/nuka-carousel/pull/578) update vulnerable deps, fix broken scroll animation 316 | - [#576](https://github.com/FormidableLabs/nuka-carousel/pull/576) Clear timeouts when component unmounts to prevent memory leak 317 | 318 | ## 4.5.10 (2019-09-02) 319 | 320 | - [#574](https://github.com/FormidableLabs/nuka-carousel/pull/574) Add event param to onDragStart method 321 | - [#573](https://github.com/FormidableLabs/nuka-carousel/pull/573) Fix resizing height issue due to dynamically loaded elements and readyStateChange issues 322 | - [#572](https://github.com/FormidableLabs/nuka-carousel/pull/572) Fix Travis configuration 323 | - [#571](https://github.com/FormidableLabs/nuka-carousel/pull/571) Next and Previous buttons should not be submit type buttons 324 | - [#565](https://github.com/FormidableLabs/nuka-carousel/pull/565) Add missing controls to the TypeScript library definition 325 | - [#564](https://github.com/FormidableLabs/nuka-carousel/pull/564) Export NextButton, PreviousButton, PagingDots from main entry point to allow for easier style targeting 326 | - [#562](https://github.com/FormidableLabs/nuka-carousel/pull/562) Allow isEdgeSwiping to work with vertical slider 327 | - [#561](https://github.com/FormidableLabs/nuka-carousel/pull/561) Avoid the empty div wrapper for null custom controls 328 | 329 | ## 4.5.9 (2019-07-09) 330 | 331 | - [#557](https://github.com/FormidableLabs/nuka-carousel/pull/557) Add type for animation prop 332 | - [#555](https://github.com/FormidableLabs/nuka-carousel/pull/555) Fix wrongly calculated height for the current slide on slide change 333 | 334 | ## 4.5.8 (2019-05-23) 335 | 336 | - [#551](https://github.com/FormidableLabs/nuka-carousel/pull/551) Add disableEdgeSwiping prop to disable white space on last and first slide when swiping 337 | - [#549](https://github.com/FormidableLabs/nuka-carousel/pull/549) Add type=button to paging dots 338 | 339 | ## 4.5.5 (2019-05-07) 340 | 341 | - [#545](https://github.com/FormidableLabs/nuka-carousel/pull/545) Add onDragStart to index.d.ts 342 | - [#541](https://github.com/FormidableLabs/nuka-carousel/pull/541) Adds scroll3d to types 343 | - [#536](https://github.com/FormidableLabs/nuka-carousel/pull/536) Add type definition for PagingDots class 344 | - [#535](https://github.com/FormidableLabs/nuka-carousel/pull/535) Allow null to be passed to render controls 345 | 346 | ## 4.5.4 (2019-04-15) 347 | 348 | - [#517](https://github.com/FormidableLabs/nuka-carousel/pull/517) Fix bug when changing to vertical mode, add missing prop to types 349 | - [#524](https://github.com/FormidableLabs/nuka-carousel/pull/524) Remove extra function wrap around handleMouse methods 350 | - [#525](https://github.com/FormidableLabs/nuka-carousel/pull/525) Fix calculation of dimensions 351 | - [#526](https://github.com/FormidableLabs/nuka-carousel/pull/526) Add classnames to paging dots to improve styling ability 352 | - [#529](https://github.com/FormidableLabs/nuka-carousel/pull/529) Fix IE11 error related to #525 353 | - [#532](https://github.com/FormidableLabs/nuka-carousel/pull/532) Update README.md with OSS status 354 | 355 | ## 4.5.3 (2019-03-18) 356 | 357 | - Resolve dependency vulnerabilities 358 | - [#506](https://github.com/FormidableLabs/nuka-carousel/pull/506) Add Scroll3D transition mode option 359 | 360 | ## 4.5.2 (2019-03-08) 361 | 362 | - Fixes dragging bug 363 | 364 | ## 4.5.1 (2019-03-06) 365 | 366 | - [#511](https://github.com/FormidableLabs/nuka-carousel/pull/511) Adds style and transitionMode types 367 | - [#510](https://github.com/FormidableLabs/nuka-carousel/pull/510) Adds new prop to allow autoplay in reverse 368 | 369 | ## 4.4.9 (2019-03-04) 370 | 371 | - [#508](https://github.com/FormidableLabs/nuka-carousel/pull/508) Fix speed slide flicker/blinking 372 | 373 | ## 4.4.8 (2019-02-21) 374 | 375 | - [#501](https://github.com/FormidableLabs/nuka-carousel/pull/501) Add cellAlign type to CarouselSlideRenderControlProps interface 376 | - [#500](https://github.com/FormidableLabs/nuka-carousel/pull/500) Animation performance enhancements 377 | - [#496](https://github.com/FormidableLabs/nuka-carousel/pull/496) Upgrade react-move 378 | - [#492](https://github.com/FormidableLabs/nuka-carousel/pull/492) Fix Paging Dots sync, fix 'Next' button validation 379 | - [#490](https://github.com/FormidableLabs/nuka-carousel/pull/490) Disable animation for an initial slide when non-default slideIndex prop is passed 380 | - [#489](https://github.com/FormidableLabs/nuka-carousel/pull/489) Disable animation prop created 381 | - [#488](https://github.com/FormidableLabs/nuka-carousel/pull/488) Configurable zoom scale prop 382 | - [#487](https://github.com/FormidableLabs/nuka-carousel/pull/487) updates to README 383 | 384 | ## 4.4.7 (2019-01-21) 385 | 386 | - [#480](https://github.com/FormidableLabs/nuka-carousel/pull/480) Add Types to build 387 | - [#486](https://github.com/FormidableLabs/nuka-carousel/pull/486) Fix mouse event handling to account for click events within the slide 388 | 389 | ## 4.4.6 (2019-01-12) 390 | 391 | - [#477](https://github.com/FormidableLabs/nuka-carousel/pull/477) Add TypeScript types 392 | - [#475](https://github.com/FormidableLabs/nuka-carousel/pull/475) Fix click event handler on button within slide element 393 | - [#473](https://github.com/FormidableLabs/nuka-carousel/pull/473) Add zoom effect prop 394 | 395 | ## 4.4.5 (2018-12-31) 396 | 397 | - [#469](https://github.com/FormidableLabs/nuka-carousel/pull/469) Fix initial height issue 398 | 399 | ## 4.4.4 (2018-12-07) 400 | 401 | - [#460](https://github.com/FormidableLabs/nuka-carousel/pull/460) Ability to configure aria-live message with prop 402 | - [#458](https://github.com/FormidableLabs/nuka-carousel/pull/458) Fix flicker in wrap-around 403 | - [#453](https://github.com/FormidableLabs/nuka-carousel/pull/453) Fix issue involving updating props on beforeSlide 404 | 405 | ## 4.4.3 (2018-11-16) 406 | 407 | - [#451](https://github.com/FormidableLabs/nuka-carousel/pull/451) Allow clicks with modifier present 408 | - [#446](https://github.com/FormidableLabs/nuka-carousel/pull/446) Fix for demo (toggle between 2 and 6 slides showing) 409 | - [#445](https://github.com/FormidableLabs/nuka-carousel/pull/445) Add `disableKeyboardControls` prop to allow users to disable keyboard controls 410 | - [#441](https://github.com/FormidableLabs/nuka-carousel/pull/441) Start work on removing deprecated lifecycle methods 411 | - [#439](https://github.com/FormidableLabs/nuka-carousel/pull/439) Allow `pauseOnHover` to work when swiping/dragging is disabled 412 | - [#436](https://github.com/FormidableLabs/nuka-carousel/pull/436) Refactoring 413 | 414 | ## 4.4.2 (2018-10-29) 415 | 416 | - [#425](https://github.com/FormidableLabs/nuka-carousel/pull/425) Accessibility features added 417 | 418 | 1. Adding keyboard controls 419 | 2. Reader lets you know what slide you are on 420 | 3. Slide that is being display will be read by reader 421 | 422 | - [#435](https://github.com/FormidableLabs/nuka-carousel/pull/435) Fix issues where `clickDisabled` is set to true too often 423 | - [#433](https://github.com/FormidableLabs/nuka-carousel/pull/433) add function to add ariaProps to all visible slides 424 | - [#432](https://github.com/FormidableLabs/nuka-carousel/pull/432) Add `slide-visible` class to all currently visible slides 425 | - [#431](https://github.com/FormidableLabs/nuka-carousel/pull/431) Carousel would go into an infinite loop between two slide. Added a isTransitioning check to wait until afterSlide is finish. 426 | 427 | ## 4.4.1 (2018-10-08) 428 | 429 | - [#423](https://github.com/FormidableLabs/nuka-carousel/pull/423) Prevent click events only when swiping 430 | -------------------------------------------------------------------------------- /packages/nuka/__mocks__/styleMock.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | -------------------------------------------------------------------------------- /packages/nuka/globals.d.ts: -------------------------------------------------------------------------------- 1 | interface Window { 2 | attachEvent(event: string, listener: EventListener): boolean; 3 | detachEvent(event: string, listener: EventListener): void; 4 | [index: string]: EventListener | null; 5 | } 6 | 7 | interface Document { 8 | attachEvent(event: string, listener: EventListener): boolean; 9 | detachEvent(event: string, listener: EventListener): void; 10 | [index: string]: EventListener | null; 11 | } 12 | -------------------------------------------------------------------------------- /packages/nuka/jest-setup.ts: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | import { toHaveNoViolations } from 'jest-axe'; 3 | 4 | expect.extend(toHaveNoViolations); 5 | -------------------------------------------------------------------------------- /packages/nuka/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | collectCoverage: true, 3 | collectCoverageFrom: ['./src/**'], 4 | moduleNameMapper: { 5 | '\\.(css|less)$': '/__mocks__/styleMock.js', 6 | }, 7 | setupFilesAfterEnv: ['/jest-setup.ts'], 8 | testEnvironment: 'jsdom', 9 | }; 10 | -------------------------------------------------------------------------------- /packages/nuka/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuka-carousel", 3 | "version": "8.2.0", 4 | "description": "Pure React Carousel", 5 | "main": "dist/index.js", 6 | "module": "dist/index.mjs", 7 | "types": "dist/index.d.ts", 8 | "scripts": { 9 | "build": "tsup", 10 | "build:watch": "tsup --watch", 11 | "check": "pnpm run lint && pnpm run check:typescript", 12 | "check:typescript": "tsc --noEmit", 13 | "lint": "eslint --ext .js,.ts,.tsx .", 14 | "lint:fix": "eslint --ext .js,.ts,.tsx . --fix", 15 | "prettier": "prettier \"**/*.{js,json,ts,tsx,css,md}\"", 16 | "prettier:fix": "prettier \"**/*.{js,json,ts,tsx,css,md}\" --write", 17 | "preversion": "pnpm run check", 18 | "test": "pnpm run test:unit", 19 | "test:ci": "pnpm run test:unit:ci", 20 | "test:unit": "jest", 21 | "test:unit:ci": "pnpm run test:unit", 22 | "test:unit:watch": "pnpm run test:unit --watchAll", 23 | "test:storybook": "test-storybook", 24 | "package": "pnpm pack", 25 | "prepublishOnly": "shx cp ../../README.md ./README.md && shx cp ../../LICENSE ./LICENSE && pnpm run build", 26 | "postpack": "shx rm ./README.md && shx rm ./LICENSE", 27 | "storybook": "storybook dev -p 6006", 28 | "storybook:build": "storybook build", 29 | "chromatic": "chromatic --exit-zero-on-changes" 30 | }, 31 | "devDependencies": { 32 | "@babel/core": "^7.24.3", 33 | "@storybook/addon-actions": "^7.4.6", 34 | "@storybook/addon-essentials": "^7.4.6", 35 | "@storybook/addon-interactions": "^7.6.17", 36 | "@storybook/addon-links": "^7.4.6", 37 | "@storybook/jest": "^0.2.3", 38 | "@storybook/react": "^7.4.6", 39 | "@storybook/react-webpack5": "^7.4.6", 40 | "@storybook/testing-library": "^0.2.2", 41 | "@testing-library/jest-dom": "^5.16.5", 42 | "@testing-library/react": "^13.3.0", 43 | "@types/jest-axe": "^3.5.6", 44 | "@types/node": "^18.7.5", 45 | "@types/react": "^18.0.0", 46 | "@types/react-dom": "^18.0.0", 47 | "@types/testing-library__jest-dom": "^5.14.5", 48 | "babel-loader": "8.1.0", 49 | "chromatic": "^6.2.0", 50 | "jest": "^29.7.0", 51 | "jest-axe": "^8.0.0", 52 | "jest-environment-jsdom": "^28.1.3", 53 | "react": "^18.0.0", 54 | "react-dom": "^18.0.0", 55 | "require-from-string": "^2.0.2", 56 | "storybook": "^7.4.6", 57 | "tsup": "^8.0.2", 58 | "typescript": "^5.4.2", 59 | "webpack": "^5.91.0", 60 | "webpack-cli": "^5.1.4", 61 | "webpack-dev-server": "^5.0.4" 62 | }, 63 | "peerDependencies": { 64 | "react": ">=18.0.0", 65 | "react-dom": ">=18.0.0" 66 | }, 67 | "resolutions": { 68 | "jsdom": "^14.0.0" 69 | }, 70 | "repository": { 71 | "type": "git", 72 | "url": "https://github.com/FormidableLabs/nuka-carousel" 73 | }, 74 | "keywords": [ 75 | "react", 76 | "carousel", 77 | "nuka" 78 | ], 79 | "author": "@FormidableLabs", 80 | "license": "MIT", 81 | "bugs": { 82 | "url": "https://github.com/FormidableLabs/nuka-carousel/issues" 83 | }, 84 | "homepage": "https://github.com/FormidableLabs/nuka-carousel", 85 | "sideEffects": true, 86 | "publishConfig": { 87 | "provenance": true 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /packages/nuka/src/Carousel/Carousel.css: -------------------------------------------------------------------------------- 1 | .nuka-hidden { 2 | position: absolute; 3 | width: 1px; 4 | height: 1px; 5 | padding: 0; 6 | margin: -1px; 7 | overflow: hidden; 8 | clip: rect(0, 0, 0, 0); 9 | white-space: nowrap; 10 | border-width: 0; 11 | } 12 | .nuka-container { 13 | position: relative; 14 | } 15 | .nuka-slide-container { 16 | position: relative; 17 | } 18 | .nuka-overflow { 19 | overflow: scroll; 20 | -ms-overflow-style: none; /* IE and Edge */ 21 | scrollbar-width: none; /* Firefox */ 22 | } 23 | .nuka-overflow.scroll-smooth { 24 | scroll-behavior: smooth; 25 | } 26 | .nuka-overflow.scroll-auto { 27 | scroll-behavior: auto; 28 | } 29 | .nuka-overflow::-webkit-scrollbar { 30 | display: none; 31 | } 32 | .nuka-wrapper { 33 | display: flex; 34 | } 35 | .nuka-nav-button { 36 | position: absolute; 37 | top: calc(50% - 2rem); 38 | margin: 1rem; 39 | display: none; 40 | height: 2rem; 41 | width: 2rem; 42 | cursor: pointer; 43 | background-color: rgba(146, 148, 151, 0.5); 44 | color: white; 45 | border-radius: 9999px; 46 | font-size: 1rem; 47 | user-select: none; 48 | } 49 | .nuka-nav-button.nuka-nav-button-prev { 50 | left: 0; 51 | margin-right: 1rem; 52 | } 53 | .nuka-nav-button.nuka-nav-button-next { 54 | right: 0; 55 | margin-left: 1rem; 56 | } 57 | .nuka-nav-button:hover { 58 | background-color: rgba(146, 148, 151, 0.65); 59 | } 60 | .nuka-nav-button-enabled { 61 | display: block; 62 | } 63 | .nuka-container-auto-hide .nuka-nav-button { 64 | display: none; 65 | } 66 | .nuka-container-auto-hide:hover .nuka-nav-button-enabled { 67 | display: block; 68 | } 69 | .nuka-page-container { 70 | padding-top: 1rem; 71 | padding-bottom: 1rem; 72 | display: flex; 73 | gap: 0.5rem; 74 | justify-content: center; 75 | align-items: center; 76 | } 77 | .nuka-page-indicator { 78 | width: 0.75rem; 79 | height: 0.75rem; 80 | cursor: pointer; 81 | border-radius: 9999px; 82 | border-style: none; 83 | background-color: rgba(146, 148, 151, 0.65); 84 | } 85 | .nuka-page-indicator.nuka-page-indicator-active, 86 | .nuka-page-indicator.nuka-page-indicator-active:hover { 87 | background-color: rgb(229 231 235 / 1); 88 | } 89 | .nuka-page-indicator:hover { 90 | background-color: rgb(229 231 235 / 1); 91 | } 92 | -------------------------------------------------------------------------------- /packages/nuka/src/Carousel/Carousel.stories.tsx: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from '@storybook/react'; 2 | import { useRef } from 'react'; 3 | 4 | import { Carousel } from './Carousel'; 5 | import { 6 | ExampleSlide, 7 | FocusableLinkSlide, 8 | FullWidthSlide, 9 | } from './ExampleSlide'; 10 | 11 | import './CarouselStories.css'; 12 | import { CarouselProps, SlideHandle } from '../types'; 13 | 14 | const StorybookComponent = (props: CarouselProps) => { 15 | const ref = useRef(null); 16 | return ( 17 |
18 | 25 | 32 | 33 |
34 | ); 35 | }; 36 | 37 | const meta: Meta = { 38 | title: 'components/Carousel', 39 | component: StorybookComponent, 40 | tags: ['autodocs'], 41 | }; 42 | 43 | export default meta; 44 | 45 | type Story = StoryObj; 46 | 47 | export const FixedWidthScroll: Story = { 48 | args: { 49 | scrollDistance: 200, 50 | children: ( 51 | <> 52 | {[...Array(6)].map((_, index) => ( 53 | 54 | ))} 55 | 56 | ), 57 | }, 58 | }; 59 | 60 | export const Slide: Story = { 61 | args: { 62 | scrollDistance: 'slide', 63 | className: 'slide__with-gap', 64 | children: ( 65 | <> 66 | {[...Array(10)].map((_, index) => ( 67 | 68 | ))} 69 | 70 | ), 71 | }, 72 | }; 73 | 74 | export const FullWidth: Story = { 75 | args: { 76 | scrollDistance: 'slide', 77 | children: ( 78 | <> 79 | {[...Array(10)].map((_, index) => ( 80 | 81 | ))} 82 | 83 | ), 84 | }, 85 | }; 86 | 87 | export const Screen: Story = { 88 | args: { 89 | scrollDistance: 'screen', 90 | className: 'slide__with-gap', 91 | children: ( 92 | <> 93 | {[...Array(10)].map((_, index) => ( 94 | 95 | ))} 96 | 97 | ), 98 | }, 99 | }; 100 | 101 | export const AutoPlay: Story = { 102 | args: { 103 | scrollDistance: 'slide', 104 | autoplay: true, 105 | children: ( 106 | <> 107 | {[...Array(10)].map((_, index) => ( 108 | 109 | ))} 110 | 111 | ), 112 | }, 113 | }; 114 | 115 | export const PageIndicators: Story = { 116 | args: { 117 | scrollDistance: 'screen', 118 | showDots: true, 119 | children: ( 120 | <> 121 | {[...Array(10)].map((_, index) => ( 122 | 123 | ))} 124 | 125 | ), 126 | }, 127 | }; 128 | 129 | export const FocusableCards: Story = { 130 | args: { 131 | children: ( 132 | <> 133 | {[...Array(10)].map((_, index) => ( 134 | 135 | ))} 136 | 137 | ), 138 | }, 139 | }; 140 | 141 | const CustomGoToPageRenderComponent = (props: CarouselProps) => { 142 | const ref = useRef(null); 143 | return ( 144 |
145 | 154 | 155 |
156 | ); 157 | }; 158 | 159 | export const GoToPage: Story = { 160 | render: CustomGoToPageRenderComponent, 161 | args: { 162 | children: ( 163 | <> 164 | {[...Array(10)].map((_, index) => ( 165 | 166 | ))} 167 | 168 | ), 169 | }, 170 | }; 171 | 172 | export const InitialPage: Story = { 173 | args: { 174 | initialPage: 2, 175 | scrollDistance: 'slide', 176 | children: ( 177 | <> 178 | {[...Array(10)].map((_, index) => ( 179 | 180 | ))} 181 | 182 | ), 183 | }, 184 | }; 185 | 186 | export const BeforeSlide: Story = { 187 | args: { 188 | beforeSlide: (currentSlideIndex, endSlideIndex) => 189 | console.log( 190 | 'Function was called before scroll occurred ', 191 | currentSlideIndex, 192 | endSlideIndex, 193 | ), 194 | children: ( 195 | <> 196 | {[...Array(10)].map((_, index) => ( 197 | 198 | ))} 199 | 200 | ), 201 | }, 202 | }; 203 | 204 | export const AfterSlide: Story = { 205 | args: { 206 | afterSlide: (endSlideIndex) => 207 | console.log('Function was called after scroll occurred ', endSlideIndex), 208 | children: ( 209 | <> 210 | {[...Array(10)].map((_, index) => ( 211 | 212 | ))} 213 | 214 | ), 215 | }, 216 | }; 217 | -------------------------------------------------------------------------------- /packages/nuka/src/Carousel/Carousel.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | MouseEvent, 3 | TouchEvent, 4 | forwardRef, 5 | useEffect, 6 | useImperativeHandle, 7 | useRef, 8 | useState, 9 | } from 'react'; 10 | 11 | import { useInterval } from '../hooks/use-interval'; 12 | import { usePaging } from '../hooks/use-paging'; 13 | import { useMeasurement } from '../hooks/use-measurement'; 14 | import { useHover } from '../hooks/use-hover'; 15 | import { useKeyboard } from '../hooks/use-keyboard'; 16 | import { useReducedMotion } from '../hooks/use-reduced-motion'; 17 | import { CarouselProvider } from '../hooks/use-carousel'; 18 | import { CarouselProps, SlideHandle } from '../types'; 19 | import { cls, isMouseEvent } from '../utils'; 20 | import { NavButtons } from './NavButtons'; 21 | import { PageIndicators } from './PageIndicators'; 22 | 23 | import './Carousel.css'; 24 | 25 | const defaults = { 26 | arrows: , 27 | autoplay: false, 28 | autoplayInterval: 3000, 29 | dots: , 30 | id: 'nuka-carousel', 31 | keyboard: true, 32 | minSwipeDistance: 50, 33 | scrollDistance: 'screen', 34 | showArrows: false, 35 | showDots: false, 36 | swiping: true, 37 | wrapMode: 'nowrap', 38 | } satisfies Partial; 39 | 40 | export const Carousel = forwardRef( 41 | (props: CarouselProps, ref) => { 42 | const options = { ...defaults, ...props }; 43 | 44 | const { 45 | afterSlide, 46 | arrows, 47 | autoplay, 48 | autoplayInterval, 49 | beforeSlide, 50 | children, 51 | className, 52 | dots, 53 | id, 54 | keyboard, 55 | minSwipeDistance, 56 | scrollDistance, 57 | showArrows, 58 | showDots, 59 | swiping, 60 | title, 61 | wrapMode, 62 | initialPage, 63 | } = options; 64 | 65 | const carouselRef = useRef(null); 66 | const containerRef = useRef(null); 67 | const previousPageRef = useRef(-1); 68 | const arrowsContainerRef = useRef(null); 69 | 70 | // -- update page count and scroll offset based on scroll distance 71 | const { totalPages, scrollOffset } = useMeasurement({ 72 | element: containerRef, 73 | scrollDistance, 74 | }); 75 | 76 | // -- paging 77 | const { currentPage, goBack, goForward, goToPage } = usePaging({ 78 | totalPages, 79 | wrapMode, 80 | initialPage, 81 | }); 82 | 83 | // -- handle touch scroll events 84 | const [touchStart, setTouchStart] = useState(null); 85 | const [touchEnd, setTouchEnd] = useState(null); 86 | 87 | const onTouchStart = (e: MouseEvent | TouchEvent) => { 88 | if (!swiping) return; 89 | setTouchEnd(null); 90 | setTouchStart(isMouseEvent(e) ? e.clientX : e.targetTouches[0].clientX); 91 | }; 92 | 93 | const onTouchMove = (e: MouseEvent | TouchEvent) => { 94 | if (!swiping) return; 95 | setTouchEnd(isMouseEvent(e) ? e.clientX : e.targetTouches[0].clientX); 96 | }; 97 | 98 | const onTouchEnd = () => { 99 | if (!swiping) return; 100 | if (!containerRef.current) return; 101 | if (!touchStart || !touchEnd) return; 102 | 103 | const distance = touchStart - touchEnd; 104 | const isLeftSwipe = distance > minSwipeDistance; 105 | const isRightSwipe = distance < -minSwipeDistance; 106 | if (isLeftSwipe || isRightSwipe) { 107 | if (isLeftSwipe) { 108 | goForward(); 109 | } else { 110 | goBack(); 111 | } 112 | } 113 | }; 114 | 115 | // -- keyboard nav 116 | useKeyboard({ 117 | element: carouselRef, 118 | enabled: keyboard, 119 | goForward, 120 | goBack, 121 | }); 122 | 123 | // -- forward events to ref 124 | useImperativeHandle(ref, () => ({ goForward, goBack, goToPage }), [ 125 | goForward, 126 | goBack, 127 | goToPage, 128 | ]); 129 | 130 | // -- autoplay 131 | const isHovered = useHover({ element: containerRef, enabled: autoplay }); 132 | const isArrowHovered = useHover({ 133 | element: arrowsContainerRef, 134 | enabled: autoplay && showArrows === true, 135 | }); 136 | const prefersReducedMotion = useReducedMotion({ enabled: autoplay }); 137 | const autoplayEnabled = 138 | autoplay && !(isHovered || prefersReducedMotion || isArrowHovered); 139 | useInterval(goForward, autoplayInterval, autoplayEnabled); 140 | 141 | // -- scroll container when page index changes 142 | useEffect(() => { 143 | if (containerRef.current) { 144 | const currentSlideIndex = previousPageRef.current; 145 | const endSlideIndex = currentPage; 146 | beforeSlide && beforeSlide(currentSlideIndex, endSlideIndex); 147 | containerRef.current.scrollLeft = scrollOffset[currentPage]; 148 | afterSlide && setTimeout(() => afterSlide(endSlideIndex), 0); 149 | previousPageRef.current = currentPage; 150 | if (initialPage === undefined || currentPage === initialPage) { 151 | containerRef.current.classList.remove('scroll-auto'); 152 | containerRef.current.classList.add('scroll-smooth'); 153 | } 154 | } 155 | }, [currentPage, scrollOffset, beforeSlide, afterSlide, initialPage]); 156 | 157 | const containerClassName = cls( 158 | 'nuka-container', 159 | showArrows === 'hover' && 'nuka-container-auto-hide', 160 | className, 161 | ); 162 | 163 | const providerValues = { 164 | ...options, 165 | totalPages, 166 | currentPage, 167 | scrollOffset, 168 | goBack, 169 | goForward, 170 | goToPage, 171 | }; 172 | 173 | return ( 174 | 175 |
182 | {title && ( 183 | 186 | )} 187 |
188 |
198 |
203 | {children} 204 |
205 |
206 | {showArrows &&
{arrows}
} 207 |
208 |
209 | {showDots && dots} 210 |
211 | ); 212 | }, 213 | ); 214 | 215 | Carousel.displayName = 'Carousel'; 216 | -------------------------------------------------------------------------------- /packages/nuka/src/Carousel/CarouselStories.css: -------------------------------------------------------------------------------- 1 | .slide__with-gap { 2 | gap: 8px; 3 | } 4 | 5 | .indicator-container { 6 | display: flex; 7 | gap: 4px; 8 | position: fixed; 9 | align-items: center; 10 | justify-content: center; 11 | width: 100%; 12 | padding: 12px 0; 13 | } 14 | 15 | .indicator { 16 | width: 10px; 17 | height: 10px; 18 | background-color: lightgray; 19 | } 20 | 21 | .indicator__current { 22 | background-color: darkslategray; 23 | } 24 | 25 | .focusable:focus::after { 26 | display: block; 27 | content: 'focused'; 28 | } 29 | -------------------------------------------------------------------------------- /packages/nuka/src/Carousel/ExampleSlide.tsx: -------------------------------------------------------------------------------- 1 | import { CSSProperties } from 'react'; 2 | 3 | const getExampleSlideStyles = (index: number): CSSProperties => ({ 4 | backgroundColor: index % 2 == 0 ? 'gray' : 'lightGray', 5 | display: 'flex', 6 | alignItems: 'center', 7 | justifyContent: 'center', 8 | padding: '15vw', 9 | minWidth: index === 1 ? '300px' : 'auto', 10 | }); 11 | 12 | export const ExampleSlide = ({ index }: { index: number }) => ( 13 |
{index}
14 | ); 15 | 16 | export const FullWidthSlide = ({ index }: { index: number }) => ( 17 |
24 | {index} 25 |
26 | ); 27 | 28 | export const FocusableLinkSlide = ({ index }: { index: number }) => ( 29 | event.target.scrollIntoView()} 36 | className="focusable" 37 | > 38 | Card {index} 39 | 40 | ); 41 | -------------------------------------------------------------------------------- /packages/nuka/src/Carousel/NavButtons.tsx: -------------------------------------------------------------------------------- 1 | import { useCarousel } from '../hooks/use-carousel'; 2 | import { cls } from '../utils'; 3 | 4 | export function NavButtons() { 5 | const { currentPage, totalPages, wrapMode, goBack, goForward } = 6 | useCarousel(); 7 | 8 | const allowWrap = wrapMode !== 'nowrap'; 9 | 10 | const enablePrevNavButton = allowWrap || currentPage > 0; 11 | const enableNextNavButton = allowWrap || currentPage < totalPages - 1; 12 | 13 | const prevNavClassName = cls( 14 | 'nuka-nav-button', 15 | 'nuka-nav-button-prev', 16 | enablePrevNavButton && 'nuka-nav-button-enabled', 17 | ); 18 | 19 | const nextNavClassName = cls( 20 | 'nuka-nav-button', 21 | 'nuka-nav-button-next', 22 | enableNextNavButton && 'nuka-nav-button-enabled', 23 | ); 24 | 25 | return ( 26 | <> 27 |
28 | 33 | 38 | 39 |
40 |
41 | 46 | 51 | 52 |
53 | 54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /packages/nuka/src/Carousel/PageIndicators.tsx: -------------------------------------------------------------------------------- 1 | import { useCarousel } from '../hooks/use-carousel'; 2 | import { cls } from '../utils'; 3 | 4 | export const PageIndicators = () => { 5 | const { totalPages, currentPage, goToPage } = useCarousel(); 6 | 7 | const className = (index: number) => 8 | cls( 9 | 'nuka-page-indicator', 10 | currentPage === index ? 'nuka-page-indicator-active' : '', 11 | ); 12 | 13 | return ( 14 |
15 | {[...Array(totalPages)].map((_, index) => ( 16 | 23 | ))} 24 |
25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /packages/nuka/src/hooks/use-carousel.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | 3 | import { CarouselProps } from '../types'; 4 | 5 | type CarouselContextType = CarouselProps & { 6 | currentPage: number; 7 | scrollOffset: number[]; 8 | totalPages: number; 9 | goToPage: (idx: number) => void; 10 | goForward: () => void; 11 | goBack: () => void; 12 | }; 13 | 14 | const CarouselContext = React.createContext( 15 | {} as unknown as CarouselContextType, 16 | ); 17 | export const CarouselProvider = CarouselContext.Provider; 18 | 19 | export const useCarousel = () => { 20 | const context = useContext(CarouselContext); 21 | return context; 22 | }; 23 | -------------------------------------------------------------------------------- /packages/nuka/src/hooks/use-hover.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import type { RefObject } from 'react'; 3 | 4 | export function useHover({ 5 | element, 6 | enabled, 7 | }: { 8 | element: RefObject; 9 | enabled: boolean; 10 | }) { 11 | const [hovered, setHovered] = useState(false); 12 | 13 | const target = element?.current; 14 | 15 | useEffect(() => { 16 | if (!(target && target.addEventListener)) return; 17 | 18 | if (enabled) { 19 | const onMouseOver = () => setHovered(true); 20 | const onMouseOut = () => setHovered(false); 21 | 22 | target.addEventListener('mouseover', onMouseOver); 23 | target.addEventListener('mouseout', onMouseOut); 24 | 25 | return () => { 26 | target.removeEventListener('mouseover', onMouseOver); 27 | target.removeEventListener('mouseout', onMouseOut); 28 | }; 29 | } 30 | }, [target, enabled]); 31 | 32 | return hovered; 33 | } 34 | -------------------------------------------------------------------------------- /packages/nuka/src/hooks/use-interval.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | 3 | type CallbackFunction = (...args: never[]) => void; 4 | 5 | export function useInterval( 6 | callback: CallbackFunction, 7 | delay: number, 8 | enabled = true, 9 | ) { 10 | const _callback = useRef(); 11 | 12 | useEffect(() => { 13 | _callback.current = callback; 14 | }, [callback]); 15 | 16 | useEffect(() => { 17 | if (enabled && delay !== null) { 18 | const id = setInterval(() => { 19 | if (_callback.current) _callback.current(); 20 | }, delay); 21 | return () => clearInterval(id); 22 | } 23 | }, [delay, enabled]); 24 | } 25 | -------------------------------------------------------------------------------- /packages/nuka/src/hooks/use-keyboard.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import type { RefObject } from 'react'; 3 | 4 | export function useKeyboard({ 5 | element, 6 | enabled, 7 | goForward, 8 | goBack, 9 | }: { 10 | element: RefObject; 11 | enabled: boolean; 12 | goForward: () => void; 13 | goBack: () => void; 14 | }) { 15 | const target = element?.current; 16 | 17 | useEffect(() => { 18 | if (target && enabled) { 19 | const onKeyDown = (e: KeyboardEvent) => { 20 | if (e.key === 'ArrowLeft') { 21 | goBack(); 22 | } else if (e.key === 'ArrowRight') { 23 | goForward(); 24 | } 25 | }; 26 | target.addEventListener('keydown', onKeyDown); 27 | return () => target.removeEventListener('keydown', onKeyDown); 28 | } 29 | }, [enabled, goBack, goForward, target]); 30 | } 31 | -------------------------------------------------------------------------------- /packages/nuka/src/hooks/use-measurement.test.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { renderHook } from '@testing-library/react'; 3 | 4 | import { useMeasurement } from './use-measurement'; 5 | import * as hooks from './use-resize-observer'; 6 | 7 | const domElement = {} as any; 8 | jest.spyOn(hooks, 'useResizeObserver').mockImplementation(() => domElement); 9 | 10 | describe('useMeasurement', () => { 11 | it('return the default values', () => { 12 | const { result } = renderHook(() => 13 | useMeasurement({ 14 | element: { current: null }, 15 | scrollDistance: 'screen', 16 | }), 17 | ); 18 | 19 | const { totalPages, scrollOffset } = result.current; 20 | 21 | expect(totalPages).toBe(0); 22 | expect(scrollOffset).toEqual([]); 23 | }); 24 | 25 | it('should return default values if offsetWidth is 0', () => { 26 | const element = { 27 | current: { 28 | scrollWidth: 0, 29 | offsetWidth: 0, 30 | querySelector: () => ({ 31 | children: [], 32 | }), 33 | }, 34 | } as any; 35 | 36 | const { result } = renderHook(() => 37 | useMeasurement({ 38 | element, 39 | scrollDistance: 'screen', 40 | }), 41 | ); 42 | 43 | const { totalPages, scrollOffset } = result.current; 44 | 45 | expect(totalPages).toBe(0); 46 | expect(scrollOffset).toEqual([]); 47 | }); 48 | 49 | it('should return default values if scrollDistance is 0', () => { 50 | const element = { 51 | current: { 52 | scrollWidth: 1000, 53 | offsetWidth: 500, 54 | querySelector: () => ({ 55 | children: [], 56 | }), 57 | }, 58 | } as any; 59 | 60 | const { result } = renderHook(() => 61 | useMeasurement({ 62 | element, 63 | scrollDistance: 0, 64 | }), 65 | ); 66 | 67 | const { totalPages, scrollOffset } = result.current; 68 | 69 | expect(totalPages).toBe(0); 70 | expect(scrollOffset).toEqual([]); 71 | }); 72 | 73 | it('should return default values if scrollDistance is < 0', () => { 74 | const element = { 75 | current: { 76 | scrollWidth: 1000, 77 | offsetWidth: 500, 78 | querySelector: () => ({ 79 | children: [], 80 | }), 81 | }, 82 | } as any; 83 | 84 | const { result } = renderHook(() => 85 | useMeasurement({ 86 | element, 87 | scrollDistance: -1, 88 | }), 89 | ); 90 | 91 | const { totalPages, scrollOffset } = result.current; 92 | 93 | expect(totalPages).toBe(0); 94 | expect(scrollOffset).toEqual([]); 95 | }); 96 | 97 | it('should return measurements for screen', () => { 98 | const element = { 99 | current: { 100 | // this test covers a specific rounding error that can 101 | // occur when the scrollWidth/offsetWidth returns a float 102 | scrollWidth: 900, 103 | offsetWidth: 500, 104 | querySelector: () => ({ 105 | children: [ 106 | { offsetWidth: 200 }, 107 | { offsetWidth: 300 }, 108 | { offsetWidth: 400 }, 109 | ], 110 | }), 111 | }, 112 | } as any; 113 | 114 | const { result } = renderHook(() => 115 | useMeasurement({ 116 | element, 117 | scrollDistance: 'screen', 118 | }), 119 | ); 120 | 121 | const { totalPages, scrollOffset } = result.current; 122 | 123 | expect(totalPages).toBe(2); 124 | expect(scrollOffset).toEqual([0, 500]); 125 | }); 126 | 127 | it('should return measurements for screen with fractional pixels', () => { 128 | const element = { 129 | current: { 130 | // this test covers a specific rounding error that can 131 | // occur when the scrollWidth/offsetWidth returns a float 132 | scrollWidth: 1720, 133 | offsetWidth: 573, 134 | querySelector: () => ({ 135 | children: [ 136 | { offsetWidth: 573 }, 137 | { offsetWidth: 573 }, 138 | { offsetWidth: 573 }, 139 | ], 140 | }), 141 | }, 142 | } as any; 143 | 144 | const { result } = renderHook(() => 145 | useMeasurement({ 146 | element, 147 | scrollDistance: 'screen', 148 | }), 149 | ); 150 | 151 | const { totalPages, scrollOffset } = result.current; 152 | 153 | expect(totalPages).toBe(3); 154 | expect(scrollOffset).toEqual([0, 573, 1146]); 155 | }); 156 | 157 | it('should return measurements for slide distance', () => { 158 | const element = { 159 | current: { 160 | scrollWidth: 900, 161 | offsetWidth: 500, 162 | querySelector: () => ({ 163 | children: [ 164 | { offsetWidth: 200 }, 165 | { offsetWidth: 300 }, 166 | { offsetWidth: 400 }, 167 | ], 168 | }), 169 | }, 170 | } as any; 171 | 172 | const { result } = renderHook(() => 173 | useMeasurement({ 174 | element, 175 | scrollDistance: 'slide', 176 | }), 177 | ); 178 | 179 | const { totalPages, scrollOffset } = result.current; 180 | 181 | expect(totalPages).toBe(3); 182 | expect(scrollOffset).toEqual([0, 200, 500]); 183 | }); 184 | 185 | it('should return measurements for numbered distance', () => { 186 | const element = { 187 | current: { 188 | scrollWidth: 900, 189 | offsetWidth: 500, 190 | querySelector: () => ({ 191 | children: [ 192 | { offsetWidth: 200 }, 193 | { offsetWidth: 300 }, 194 | { offsetWidth: 400 }, 195 | ], 196 | }), 197 | }, 198 | } as any; 199 | 200 | const { result } = renderHook(() => 201 | useMeasurement({ 202 | element, 203 | scrollDistance: 200, 204 | }), 205 | ); 206 | 207 | const { totalPages, scrollOffset } = result.current; 208 | 209 | expect(totalPages).toBe(3); 210 | expect(scrollOffset).toEqual([0, 200, 400]); 211 | }); 212 | }); 213 | -------------------------------------------------------------------------------- /packages/nuka/src/hooks/use-measurement.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | import { arraySeq, arraySum } from '../utils'; 4 | import { useResizeObserver } from './use-resize-observer'; 5 | 6 | type MeasurementProps = { 7 | element: React.RefObject; 8 | scrollDistance: number | 'slide' | 'screen'; 9 | }; 10 | 11 | export function useMeasurement({ element, scrollDistance }: MeasurementProps) { 12 | const [totalPages, setTotalPages] = useState(0); 13 | const [scrollOffset, setScrollOffset] = useState(arraySeq(totalPages, 0)); 14 | const dimensions = useResizeObserver(element); 15 | 16 | useEffect(() => { 17 | const container = element.current; 18 | if (!(container && dimensions)) return; 19 | 20 | // determine the width of the content that is not visible (overflow) 21 | // we ignore the bounding box width because its a float 22 | // and scrollWidth is an integer, so it creates an imperfect 23 | // calculation when the scrollWidth is a few pixels larger 24 | const scrollWidth = container.scrollWidth; 25 | const visibleWidth = container.offsetWidth; 26 | const remainder = scrollWidth - visibleWidth; 27 | 28 | if (visibleWidth === 0) return; 29 | 30 | switch (scrollDistance) { 31 | case 'screen': { 32 | const pageCount = Math.round(scrollWidth / visibleWidth); 33 | 34 | setTotalPages(pageCount); 35 | setScrollOffset(arraySeq(pageCount, visibleWidth)); 36 | break; 37 | } 38 | case 'slide': { 39 | // creates an array of slide widths in order to support 40 | // slides of varying widths as children 41 | const children = 42 | container.querySelector('#nuka-wrapper')?.children || []; 43 | const offsets = Array.from(children).map( 44 | (child) => (child as HTMLElement).offsetWidth, 45 | ); 46 | 47 | // shift the scroll offsets by one to account for the first slide 48 | const scrollOffsets = arraySum([0, ...offsets.slice(0, -1)]); 49 | 50 | // find the index of the scroll offset that is greater than 51 | // the remainder of the full width and window width 52 | const pageCount = 53 | scrollOffsets.findIndex((offset) => offset >= remainder) + 1; 54 | 55 | setTotalPages(pageCount); 56 | setScrollOffset(scrollOffsets); 57 | break; 58 | } 59 | default: { 60 | if (typeof scrollDistance === 'number' && scrollDistance > 0) { 61 | // find the number of pages required to scroll all the slides 62 | // to the end of the container 63 | const pageCount = Math.ceil(remainder / scrollDistance) + 1; 64 | 65 | setTotalPages(pageCount); 66 | setScrollOffset(arraySeq(pageCount, scrollDistance)); 67 | } 68 | } 69 | } 70 | }, [element, scrollDistance, dimensions]); 71 | 72 | return { totalPages, scrollOffset }; 73 | } 74 | -------------------------------------------------------------------------------- /packages/nuka/src/hooks/use-paging.test.tsx: -------------------------------------------------------------------------------- 1 | import { renderHook, act } from '@testing-library/react'; 2 | 3 | import { usePaging } from './use-paging'; 4 | 5 | describe('usePaging', () => { 6 | it('should return the current page', () => { 7 | const { result } = renderHook(() => 8 | usePaging({ totalPages: 3, wrapMode: 'wrap' }), 9 | ); 10 | expect(result.current.currentPage).toBe(0); 11 | }); 12 | 13 | it('should go to the next page', () => { 14 | const { result } = renderHook(() => 15 | usePaging({ totalPages: 3, wrapMode: 'wrap' }), 16 | ); 17 | act(() => { 18 | result.current.goForward(); 19 | }); 20 | expect(result.current.currentPage).toBe(1); 21 | }); 22 | 23 | it('should go to the next page and loop back to the first page', () => { 24 | const { result } = renderHook(() => 25 | usePaging({ totalPages: 3, wrapMode: 'wrap' }), 26 | ); 27 | act(() => { 28 | result.current.goForward(); 29 | result.current.goForward(); 30 | result.current.goForward(); 31 | }); 32 | expect(result.current.currentPage).toBe(0); 33 | }); 34 | 35 | it('should stop at the last page', () => { 36 | const { result } = renderHook(() => 37 | usePaging({ totalPages: 3, wrapMode: 'nowrap' }), 38 | ); 39 | act(() => { 40 | result.current.goForward(); 41 | result.current.goForward(); 42 | result.current.goForward(); 43 | }); 44 | expect(result.current.currentPage).toBe(2); 45 | }); 46 | 47 | it('should go to the previous page', () => { 48 | const { result } = renderHook(() => 49 | usePaging({ totalPages: 3, wrapMode: 'wrap' }), 50 | ); 51 | act(() => { 52 | result.current.goForward(); 53 | result.current.goBack(); 54 | }); 55 | expect(result.current.currentPage).toBe(0); 56 | }); 57 | 58 | it('should go to the previous page and wrap back to the last page', () => { 59 | const { result } = renderHook(() => 60 | usePaging({ totalPages: 3, wrapMode: 'wrap' }), 61 | ); 62 | act(() => { 63 | result.current.goBack(); 64 | }); 65 | expect(result.current.currentPage).toBe(2); 66 | }); 67 | 68 | it('should stop at the first page', () => { 69 | const { result } = renderHook(() => 70 | usePaging({ totalPages: 3, wrapMode: 'nowrap' }), 71 | ); 72 | act(() => { 73 | result.current.goBack(); 74 | }); 75 | expect(result.current.currentPage).toBe(0); 76 | }); 77 | 78 | it('should go to any page', () => { 79 | const { result } = renderHook(() => 80 | usePaging({ totalPages: 5, wrapMode: 'wrap' }), 81 | ); 82 | act(() => { 83 | result.current.goToPage(3); 84 | }); 85 | expect(result.current.currentPage).toBe(3); 86 | }); 87 | 88 | it('should not go to a page that is out of bounds', () => { 89 | const { result } = renderHook(() => 90 | usePaging({ totalPages: 5, wrapMode: 'wrap' }), 91 | ); 92 | act(() => { 93 | result.current.goToPage(10); 94 | }); 95 | expect(result.current.currentPage).toBe(0); 96 | }); 97 | 98 | it('should start at index 0 if not given an initial page index', () => { 99 | const { result } = renderHook(() => 100 | usePaging({ totalPages: 5, wrapMode: 'wrap' }), 101 | ); 102 | expect(result.current.currentPage).toBe(0); 103 | }); 104 | 105 | it('should start at the given initial page index', () => { 106 | const { result } = renderHook(() => 107 | usePaging({ totalPages: 5, wrapMode: 'wrap', initialPage: 2 }), 108 | ); 109 | expect(result.current.currentPage).toBe(2); 110 | }); 111 | 112 | it('should start at in bound indices if initial page is out of bounds', () => { 113 | const { result } = renderHook(() => 114 | usePaging({ totalPages: 5, wrapMode: 'wrap', initialPage: 200 }), 115 | ); 116 | expect(result.current.currentPage).toBe(5); 117 | }); 118 | 119 | it('should start at 0 indices if initial page is out of bounds', () => { 120 | const { result } = renderHook(() => 121 | usePaging({ totalPages: 5, wrapMode: 'wrap', initialPage: -2 }), 122 | ); 123 | expect(result.current.currentPage).toBe(0); 124 | }); 125 | }); 126 | -------------------------------------------------------------------------------- /packages/nuka/src/hooks/use-paging.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | import { CarouselProps } from '../types'; 4 | 5 | type UsePagingReturnType = { 6 | currentPage: number; 7 | goToPage: (idx: number) => void; 8 | goForward: () => void; 9 | goBack: () => void; 10 | }; 11 | 12 | type PagingProps = { 13 | totalPages: number; 14 | wrapMode: CarouselProps['wrapMode']; 15 | initialPage?: number; 16 | }; 17 | 18 | export function usePaging({ 19 | totalPages, 20 | wrapMode, 21 | initialPage, 22 | }: PagingProps): UsePagingReturnType { 23 | const [currentPage, setCurrentPage] = useState(0); 24 | 25 | useEffect(() => { 26 | if (initialPage) { 27 | setCurrentPage(Math.max(0, Math.min(initialPage, totalPages))); 28 | } 29 | }, [initialPage, totalPages]); 30 | 31 | const goToPage = (idx: number) => { 32 | if (idx < 0 || idx >= totalPages) return; 33 | setCurrentPage(idx); 34 | }; 35 | 36 | const goForward = () => { 37 | if (wrapMode === 'wrap') { 38 | setCurrentPage((prev) => (prev + 1) % totalPages); 39 | } else { 40 | setCurrentPage((prev) => Math.min(prev + 1, totalPages - 1)); 41 | } 42 | }; 43 | 44 | const goBack = () => { 45 | if (wrapMode === 'wrap') { 46 | setCurrentPage((prev) => (prev - 1 + totalPages) % totalPages); 47 | } else { 48 | setCurrentPage((prev) => Math.max(prev - 1, 0)); 49 | } 50 | }; 51 | 52 | return { currentPage, goToPage, goForward, goBack }; 53 | } 54 | -------------------------------------------------------------------------------- /packages/nuka/src/hooks/use-reduced-motion.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | import { isBrowser } from '../utils'; 4 | 5 | const QUERY = '(prefers-reduced-motion: no-preference)'; 6 | const getInitialState = () => 7 | isBrowser() ? !window.matchMedia(QUERY).matches : true; 8 | 9 | export function useReducedMotion({ enabled }: { enabled: boolean }) { 10 | const [reduceMotion, setReducedMotion] = useState(getInitialState); 11 | 12 | useEffect(() => { 13 | if (!(isBrowser() && enabled)) return; 14 | 15 | const mediaQueryList = window.matchMedia(QUERY); 16 | 17 | const listener = (event: MediaQueryListEvent) => { 18 | setReducedMotion(!event.matches); 19 | }; 20 | 21 | mediaQueryList.addEventListener('change', listener); 22 | 23 | return () => { 24 | mediaQueryList.removeEventListener('change', listener); 25 | }; 26 | }, [enabled]); 27 | return reduceMotion; 28 | } 29 | -------------------------------------------------------------------------------- /packages/nuka/src/hooks/use-resize-observer.tsx: -------------------------------------------------------------------------------- 1 | import { RefObject, useEffect, useState } from 'react'; 2 | 3 | export function useResizeObserver(element: RefObject) { 4 | const [dimensions, setDimensions] = useState(); 5 | 6 | useEffect(() => { 7 | if (!element.current) return; 8 | 9 | const target = element.current; 10 | 11 | const observer = new ResizeObserver(() => 12 | setDimensions(target.getBoundingClientRect()), 13 | ); 14 | 15 | observer.observe(target); 16 | return () => { 17 | observer.unobserve(target); 18 | }; 19 | }, [element]); 20 | 21 | return dimensions; 22 | } 23 | -------------------------------------------------------------------------------- /packages/nuka/src/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './hooks/use-carousel'; 2 | export * from './Carousel/Carousel'; 3 | export * from './types'; 4 | -------------------------------------------------------------------------------- /packages/nuka/src/types.ts: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | 3 | export type ShowArrowsOption = boolean | 'always' | 'hover'; 4 | export type ScrollDistanceType = number | 'slide' | 'screen'; 5 | 6 | export type CarouselCallbacks = { 7 | beforeSlide?: (currentSlideIndex: number, endSlideIndex: number) => void; 8 | afterSlide?: (endSlideIndex: number) => void; 9 | }; 10 | 11 | export type CarouselProps = CarouselCallbacks & { 12 | children: ReactNode; 13 | 14 | arrows?: ReactNode; 15 | autoplay?: boolean; 16 | autoplayInterval?: number; 17 | className?: string; 18 | dots?: ReactNode; 19 | id?: string; 20 | keyboard?: boolean; 21 | minSwipeDistance?: number; 22 | scrollDistance?: ScrollDistanceType; 23 | showArrows?: ShowArrowsOption; 24 | showDots?: boolean; 25 | swiping?: boolean; 26 | title?: string; 27 | wrapMode?: 'nowrap' | 'wrap'; 28 | initialPage?: number; 29 | }; 30 | 31 | export type SlideHandle = { 32 | goForward: () => void; 33 | goBack: () => void; 34 | goToPage: (proposedIndex: number) => void; 35 | }; 36 | -------------------------------------------------------------------------------- /packages/nuka/src/utils/array.test.ts: -------------------------------------------------------------------------------- 1 | import { arraySeq, arraySum } from '.'; 2 | 3 | describe('utils', () => { 4 | describe('arraySeq', () => { 5 | it('should return an array of sequential numbers', () => { 6 | const result = arraySeq(5, 2); 7 | expect(result).toEqual([0, 2, 4, 6, 8]); 8 | }); 9 | }); 10 | 11 | describe('arraySum', () => { 12 | it('should return a compounded array', () => { 13 | const result = arraySum([0, 1, 2, 3, 4]); 14 | expect(result).toEqual([0, 1, 3, 6, 10]); 15 | }); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/nuka/src/utils/array.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Creates an zero based array of numbers each multiplied by initialValue 3 | * @param length The length of the array 4 | * @param initialValue The seed value that each element will be multiplied by 5 | * @returns An array of numbers 6 | */ 7 | export function arraySeq(length: number, initialValue: number): number[] { 8 | return new Array(length).fill(0).map((_, i) => i * initialValue); 9 | } 10 | 11 | /** 12 | * Creates an array where each element is the sum of all previous elements 13 | * @param values The seed array 14 | * @returns An array of numbers 15 | */ 16 | export function arraySum(values: number[]): number[] { 17 | let sum = 0; 18 | return values.map((value) => (sum += value)); 19 | } 20 | -------------------------------------------------------------------------------- /packages/nuka/src/utils/browser.ts: -------------------------------------------------------------------------------- 1 | export const isBrowser = () => typeof window !== 'undefined'; 2 | -------------------------------------------------------------------------------- /packages/nuka/src/utils/css.ts: -------------------------------------------------------------------------------- 1 | type ClassName = string | boolean | undefined; 2 | 3 | export function cls(...classes: ClassName[]) { 4 | return classes.filter(Boolean).join(' '); 5 | } 6 | -------------------------------------------------------------------------------- /packages/nuka/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './array'; 2 | export * from './browser'; 3 | export * from './css'; 4 | export * from './mouse'; 5 | -------------------------------------------------------------------------------- /packages/nuka/src/utils/mouse.ts: -------------------------------------------------------------------------------- 1 | export function isMouseEvent( 2 | e: React.MouseEvent | React.TouchEvent, 3 | ): e is React.MouseEvent { 4 | return 'clientX' && 'clientY' in e; 5 | } 6 | -------------------------------------------------------------------------------- /packages/nuka/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src/**/*"], 4 | "compilerOptions": { 5 | "baseUrl": ".", 6 | "declaration": true, 7 | "esModuleInterop": true, 8 | "declarationMap": true, 9 | "sourceMap": true, 10 | "outDir": "./lib" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/nuka/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup'; 2 | 3 | export default defineConfig({ 4 | entry: ['src/index.tsx'], 5 | sourcemap: true, 6 | clean: true, 7 | dts: true, 8 | format: ['cjs', 'esm'], 9 | target: 'es6', 10 | injectStyle: true, 11 | }); 12 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/*' 3 | - 'website' 4 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: true, 3 | }; 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Language and Environment */ 4 | "target": "ES6", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 5 | "lib": [ 6 | "DOM", 7 | "ES2022" 8 | ], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 9 | "jsx": "react-jsx", /* Specify what JSX code is generated. */ 10 | 11 | /* Modules */ 12 | "module": "commonjs", /* Specify what module code is generated. */ 13 | "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 14 | "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 15 | 16 | /* Interop Constraints */ 17 | "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 18 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ 19 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 20 | 21 | /* Type Checking */ 22 | "strict": true, /* Enable all strict type-checking options. */ 23 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "redirects": [ 3 | { 4 | "source": "/open-source/nuka-carousel/api", 5 | "destination": "/open-source/nuka-carousel/", 6 | "permanent": true 7 | } 8 | ] 9 | } -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /website/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /website/docusaurus.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Note: type annotations allow type checking and IDEs autocompletion 3 | 4 | const lightCodeTheme = require('prism-react-renderer/themes/github'); 5 | const darkCodeTheme = require('prism-react-renderer/themes/okaidia'); 6 | 7 | const title = 'Nuka Carousel'; 8 | const tagline = 9 | 'Small, fast, and accessibility-first React carousel library with easily customizable UI and behavior to fit your brand and site.'; 10 | 11 | /** @type {import('@docusaurus/types').Config} */ 12 | const config = { 13 | title, 14 | tagline, 15 | url: 'https://commerce.nearform.com', 16 | baseUrl: '/open-source/nuka-carousel', 17 | onBrokenLinks: 'throw', 18 | onBrokenMarkdownLinks: 'warn', 19 | favicon: 'img/nearform-icon.svg', 20 | 21 | // Even if you don't use internalization, you can use this field to set useful 22 | // metadata like html lang. For example, if your site is Chinese, you may want 23 | // to replace "en" with "zh-Hans". 24 | i18n: { 25 | defaultLocale: 'en', 26 | locales: ['en'], 27 | }, 28 | 29 | presets: [ 30 | [ 31 | 'classic', 32 | /** @type {import('@docusaurus/preset-classic').Options} */ 33 | ({ 34 | docs: { 35 | routeBasePath: '/docs', 36 | path: '../docs', 37 | sidebarPath: require.resolve('./sidebars.js'), 38 | editUrl: 39 | 'https://github.com/FormidableLabs/nuka-carousel/tree/main/website', 40 | }, 41 | theme: { 42 | customCss: require.resolve('./src/css/custom.css'), 43 | }, 44 | gtag: { 45 | trackingID: 'G-M971D063B9', 46 | }, 47 | }), 48 | ], 49 | ], 50 | 51 | plugins: [ 52 | async function myPlugin() { 53 | return { 54 | name: 'docusaurus-tailwindcss', 55 | configurePostCss(postcssOptions) { 56 | // Appends TailwindCSS and AutoPrefixer. 57 | postcssOptions.plugins.push(require('tailwindcss')); 58 | postcssOptions.plugins.push(require('autoprefixer')); 59 | return postcssOptions; 60 | }, 61 | }; 62 | }, 63 | ], 64 | 65 | themeConfig: 66 | /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ 67 | ({ 68 | navbar: { 69 | title: 'Nuka Carousel', 70 | logo: { 71 | alt: 'Nearform logo', 72 | src: 'img/nearform-logo-white.svg', 73 | }, 74 | items: [ 75 | { to: 'docs', label: 'Documentation', position: 'left' }, 76 | { 77 | href: 'https://github.com/FormidableLabs/nuka-carousel', 78 | className: 'header-github-link', 79 | 'aria-label': 'GitHub Repository', 80 | position: 'right', 81 | }, 82 | ], 83 | }, 84 | footer: { 85 | logo: { 86 | alt: 'Nearform logo', 87 | src: 'img/nearform-logo-white.svg', 88 | href: 'https://nearform.com', 89 | width: 100, 90 | height: 100, 91 | }, 92 | copyright: `Copyright © 2013-${new Date().getFullYear()} Nearform`, 93 | }, 94 | prism: { 95 | theme: lightCodeTheme, 96 | darkTheme: darkCodeTheme, 97 | }, 98 | metadata: [ 99 | { 100 | name: 'title', 101 | content: title, 102 | }, 103 | { 104 | name: 'description', 105 | content: tagline, 106 | }, 107 | { 108 | name: 'viewport', 109 | content: 'width=device-width, initial-scale=1, maximum-scale=1', 110 | }, 111 | { 112 | property: 'og:type', 113 | content: 'website', 114 | }, 115 | { 116 | property: 'og:url', 117 | content: 'https://commerce.nearform.com/open-source/nuka-carousel/', 118 | }, 119 | { 120 | property: 'og:title', 121 | content: title, 122 | }, 123 | { 124 | property: 'og:description', 125 | content: tagline, 126 | }, 127 | { 128 | property: 'og:image', 129 | content: 130 | 'https://commerce.nearform.com/open-source/nuka-carousel/open-graph.png', 131 | }, 132 | { 133 | property: 'twitter:card', 134 | content: 'summary_large_image', 135 | }, 136 | { 137 | property: 'twitter:title', 138 | content: title, 139 | }, 140 | { 141 | property: 'twitter:description', 142 | content: tagline, 143 | }, 144 | { 145 | property: 'twitter:image', 146 | content: 147 | 'https://commerce.nearform.com/open-source/nuka-carousel/open-graph.png', 148 | }, 149 | ], 150 | }), 151 | }; 152 | 153 | module.exports = config; 154 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "postinstall": "pnpm run -C ../ build", 8 | "start": "docusaurus start", 9 | "build": "pnpm run -C ../ build && docusaurus build --out-dir build/open-source/nuka-carousel", 10 | "swizzle": "docusaurus swizzle", 11 | "deploy": "docusaurus deploy", 12 | "clear": "docusaurus clear", 13 | "serve": "docusaurus serve", 14 | "write-translations": "docusaurus write-translations", 15 | "write-heading-ids": "docusaurus write-heading-ids", 16 | "typecheck": "tsc" 17 | }, 18 | "dependencies": { 19 | "@docusaurus/core": "2.4.0", 20 | "@docusaurus/plugin-content-pages": "2.4.0", 21 | "@docusaurus/plugin-google-gtag": "^3.5.2", 22 | "@docusaurus/preset-classic": "2.4.0", 23 | "@mdx-js/react": "^1.6.22", 24 | "clsx": "^1.2.1", 25 | "nuka-carousel": "*", 26 | "prism-react-renderer": "^1.3.5", 27 | "react": "^18.2.0", 28 | "react-dom": "^18.2.0", 29 | "react-icons": "^4.8.0" 30 | }, 31 | "devDependencies": { 32 | "@algolia/client-search": "^4.17.0", 33 | "@docusaurus/module-type-aliases": "2.4.0", 34 | "@docusaurus/types": "^2.4.0", 35 | "@tsconfig/docusaurus": "^1.0.5", 36 | "@types/node": "^18.7.5", 37 | "autoprefixer": "^10.4.14", 38 | "formidable-oss-badges": "^1.3.2", 39 | "postcss": "^8.4.21", 40 | "tailwindcss": "^3.3.1", 41 | "ts-node": "^10.9.2" 42 | }, 43 | "browserslist": { 44 | "production": [ 45 | ">0.5%", 46 | "not dead", 47 | "not op_mini all" 48 | ], 49 | "development": [ 50 | "last 1 chrome version", 51 | "last 1 firefox version", 52 | "last 1 safari version" 53 | ] 54 | }, 55 | "engines": { 56 | "node": ">=18.0.0" 57 | } 58 | } -------------------------------------------------------------------------------- /website/sidebars.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creating a sidebar enables you to: 3 | - create an ordered group of docs 4 | - render a sidebar for each doc of that group 5 | - provide next/previous navigation 6 | 7 | The sidebars can be generated from the filesystem, or explicitly defined here. 8 | 9 | Create as many sidebars as you want. 10 | */ 11 | 12 | // @ts-check 13 | 14 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ 15 | const sidebars = { 16 | // By default, Docusaurus generates a sidebar from the docs folder structure 17 | tutorialSidebar: [{ type: 'autogenerated', dirName: '.' }], 18 | 19 | // But you can create a sidebar manually 20 | /* 21 | tutorialSidebar: [ 22 | 'intro', 23 | 'hello', 24 | { 25 | type: 'category', 26 | label: 'Tutorial', 27 | items: ['tutorial-basics/create-a-document'], 28 | }, 29 | ], 30 | */ 31 | }; 32 | 33 | module.exports = sidebars; 34 | -------------------------------------------------------------------------------- /website/src/assets/fonts/inter/InterBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/nuka-carousel/e839a97883f06cd29d3a537b51e5ffb67216649c/website/src/assets/fonts/inter/InterBold.woff2 -------------------------------------------------------------------------------- /website/src/assets/fonts/inter/InterMedium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/nuka-carousel/e839a97883f06cd29d3a537b51e5ffb67216649c/website/src/assets/fonts/inter/InterMedium.woff2 -------------------------------------------------------------------------------- /website/src/assets/fonts/inter/InterRegular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/nuka-carousel/e839a97883f06cd29d3a537b51e5ffb67216649c/website/src/assets/fonts/inter/InterRegular.woff2 -------------------------------------------------------------------------------- /website/src/assets/images/hero-pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/nuka-carousel/e839a97883f06cd29d3a537b51e5ffb67216649c/website/src/assets/images/hero-pattern.png -------------------------------------------------------------------------------- /website/src/assets/images/native-support.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/nuka-carousel/e839a97883f06cd29d3a537b51e5ffb67216649c/website/src/assets/images/native-support.png -------------------------------------------------------------------------------- /website/src/assets/images/responsive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/nuka-carousel/e839a97883f06cd29d3a537b51e5ffb67216649c/website/src/assets/images/responsive.png -------------------------------------------------------------------------------- /website/src/assets/images/style.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/nuka-carousel/e839a97883f06cd29d3a537b51e5ffb67216649c/website/src/assets/images/style.png -------------------------------------------------------------------------------- /website/src/components/basic-button-demo.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react'; 2 | import { Carousel, CarouselProps, SlideHandle } from 'nuka-carousel'; 3 | import { BsChevronLeft, BsChevronRight } from 'react-icons/bs'; 4 | import { FlatDarkButton } from './buttons'; 5 | 6 | export const BasicButtonDemo = (props: CarouselProps) => { 7 | const ref = useRef(null); 8 | return ( 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | ref.current && ref.current.goBack()}> 18 | 20 | ref.current && ref.current.goForward()}> 21 | 23 |
24 |
25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /website/src/components/buttons.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export function FlatDarkButton({ children, ...props }) { 4 | return ( 5 | 9 | {children} 10 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /website/src/components/carousel-demo.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Carousel } from 'nuka-carousel'; 3 | import { LandingDivider } from './landing/landing-divider'; 4 | 5 | function DemoCard({ title, description, imageSrc, price }) { 6 | return ( 7 |
8 |
9 |
10 | ${price.toFixed(2)} 11 |
12 | {title} 13 |
14 |
15 |
16 | {title} 17 |
18 |
19 | {description} 20 |
21 | 29 |
30 |
31 | ); 32 | } 33 | 34 | export const CarouselDemo = () => ( 35 |
36 |
37 | 38 |

Carousel Demo

39 |

40 | This carousel library is small but mighty! Here's this demo of 41 | product cards turned into a carousel with a few simple lines! 42 |

43 |
44 |
45 | 46 | 52 | 58 | 64 | 70 | 76 | 77 |
78 |
79 | ); 80 | -------------------------------------------------------------------------------- /website/src/components/custom-arrows-demo.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { useCarousel } from 'nuka-carousel'; 4 | 5 | function cls(...classes) { 6 | return classes.filter(Boolean).join(' '); 7 | } 8 | 9 | export function CustomArrows() { 10 | const { currentPage, totalPages, wrapMode, goBack, goForward } = 11 | useCarousel(); 12 | 13 | const allowWrap = wrapMode !== 'nowrap'; 14 | 15 | const enablePrevNavButton = allowWrap || currentPage > 0; 16 | const enableNextNavButton = allowWrap || currentPage < totalPages - 1; 17 | 18 | const prevNavClassName = cls( 19 | 'inline-block px-4 py-2 bg-pink-800 cursor-pointer invisible', 20 | enablePrevNavButton && '!visible', 21 | ); 22 | 23 | const nextNavClassName = cls( 24 | 'inline-block px-4 py-2 bg-pink-800 cursor-pointer invisible', 25 | enableNextNavButton && '!visible', 26 | ); 27 | 28 | return ( 29 |
30 |
31 | PREV 32 |
33 |
34 | NEXT 35 |
36 |
37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /website/src/components/custom-dots-demo.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { useCarousel } from 'nuka-carousel'; 4 | 5 | export const CustomDots = () => { 6 | const { totalPages, currentPage, goToPage } = useCarousel(); 7 | 8 | const className = (index: number) => { 9 | let value = 10 | 'w-3 h-3 p-0 rounded-full bg-gray-200 border-none cursor-pointer hover:bg-green-200'; 11 | if (currentPage === index) { 12 | value += ' bg-green-500 hover:bg-green-500'; 13 | } 14 | return value; 15 | }; 16 | 17 | return ( 18 |
19 | {[...Array(totalPages)].map((_, index) => ( 20 |
27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /website/src/components/full-feature-demo.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Carousel } from 'nuka-carousel'; 3 | 4 | const FeatureCard = ({ src, title }: { src: string; title: string }) => ( 5 |
6 |
7 | 8 | 9 | 10 |
11 | 12 |
13 | {title} 14 |
15 |
16 |

17 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do 18 | eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad 19 | minim veniam, quis nostrud exercitation ullamco laboris nisi ut 20 | aliquip ex ea commodo consequat. 21 |

22 |
23 |
24 |
25 | ); 26 | 27 | export const FullFeatureDemo = () => { 28 | return ( 29 |
30 | 31 | 35 | 39 | 43 | 47 | 51 | 52 |
53 | ); 54 | }; 55 | -------------------------------------------------------------------------------- /website/src/components/landing/landing-banner.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { NFLinkButton } from './nf-link-button'; 3 | import { LandingDivider } from './landing-divider'; 4 | 5 | export const LandingBanner = ({ 6 | body, 7 | cta, 8 | heading, 9 | showDivider, 10 | }: { 11 | body: string; 12 | cta: { link: string; text: string }; 13 | heading: string; 14 | showDivider?: boolean; 15 | }) => ( 16 |
17 | {showDivider && } 18 | 19 |

{heading}

20 |

{body}

21 | {cta.text} 22 |
23 | ); 24 | -------------------------------------------------------------------------------- /website/src/components/landing/landing-divider.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const LandingDivider = () => ( 4 |
5 | ); 6 | -------------------------------------------------------------------------------- /website/src/components/landing/landing-featured-projects.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { FeaturedBadge } from 'formidable-oss-badges'; 3 | import { NFLinkButton } from './nf-link-button'; 4 | import { LandingDivider } from './landing-divider'; 5 | 6 | type featuredProject = 7 | | 'renature' 8 | | 'spectacle' 9 | | 'urql' 10 | | 'victory' 11 | | 'nuka' 12 | | 'owl' 13 | | 'groqd' 14 | | 'envy' 15 | | 'figlog'; 16 | 17 | export const LandingFeaturedProjects = ({ 18 | heading, 19 | projects, 20 | showDivider, 21 | }: { 22 | heading: string; 23 | projects: { 24 | name: featuredProject; 25 | link: string; 26 | description: string; 27 | title?: string; 28 | }[]; 29 | showDivider?: boolean; 30 | }) => ( 31 |
32 | {showDivider && } 33 |

{heading}

34 |
35 | {projects.map(({ name, link, description, title }) => ( 36 | 41 | 42 | 43 | 44 | {title || name} 45 | 46 | {description} 47 | 48 | 49 | ))} 50 |
51 | 52 |
53 | 54 | View All Projects 55 | 56 |
57 |
58 | ); 59 | -------------------------------------------------------------------------------- /website/src/components/landing/landing-features.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { LandingDivider } from './landing-divider'; 3 | 4 | export const LandingFeatures = ({ 5 | heading, 6 | list, 7 | showDivider, 8 | }: { 9 | heading: string; 10 | list: { imgSrc: string; alt: string; title: string; body: string }[]; 11 | showDivider?: boolean; 12 | }) => ( 13 |
14 | {showDivider && } 15 |

{heading}

16 |
    17 | {list.map(({ alt, body, imgSrc, title }) => ( 18 |
  • 22 | {alt} 23 | {title} 24 | {body} 25 |
  • 26 | ))} 27 |
28 |
29 | ); 30 | -------------------------------------------------------------------------------- /website/src/components/landing/landing-hero.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { FeaturedBadge } from 'formidable-oss-badges'; 3 | 4 | export const LandingHero = ({ 5 | body, 6 | copyText, 7 | heading, 8 | navItems, 9 | }: { 10 | body: string; 11 | copyText: string; 12 | heading: string; 13 | navItems: { link: string; title: string }[]; 14 | }) => { 15 | return ( 16 |
17 |
18 |
19 |
20 | 21 |
22 |
23 |

24 | {heading} 25 |

26 |

{body}

27 |
28 | 39 |
40 | 51 |
52 |
53 |
54 |
55 | ); 56 | }; 57 | -------------------------------------------------------------------------------- /website/src/components/landing/landing-logos.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { LandingDivider } from './landing-divider'; 3 | 4 | export const LandingLogos = ({ 5 | body, 6 | heading, 7 | list, 8 | showDivider, 9 | }: { 10 | body: string; 11 | heading: string; 12 | list: { imgSrc: string; alt: string }[]; 13 | showDivider?: boolean; 14 | }) => ( 15 |
16 |
17 | {showDivider && } 18 | 19 |

{heading}

20 |

{body}

21 |
    22 | {list.map(({ imgSrc, alt }) => ( 23 |
  • 24 | {alt} 25 |
  • 26 | ))} 27 |
28 |
29 |
30 | ); 31 | -------------------------------------------------------------------------------- /website/src/components/landing/nf-link-button.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | interface ButtonProps { 4 | children: ChildNode | React.ReactNode; 5 | link: string; 6 | screenReaderLabel?: string; 7 | } 8 | 9 | export const NFLinkButton = ({ children, link }: ButtonProps) => { 10 | return ( 11 | 15 | {children} 16 | 17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /website/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Any CSS included here will be global. The classic template 3 | * bundles Infima by default. Infima is a CSS framework designed to 4 | * work well for content-centric websites. 5 | */ 6 | @tailwind base; 7 | @tailwind components; 8 | @tailwind utilities; 9 | 10 | body { 11 | scroll-behavior: smooth; 12 | text-rendering: optimizeSpeed; 13 | } 14 | 15 | @font-face { 16 | font-family: 'Inter'; 17 | src: url('../assets/fonts/inter/InterRegular.woff2') format('woff2'); 18 | font-weight: 400; 19 | font-style: normal; 20 | font-display: swap; 21 | } 22 | 23 | @font-face { 24 | font-family: 'Inter'; 25 | src: url('../assets/fonts/inter/InterMedium.woff2') format('woff2'); 26 | font-weight: 500; 27 | font-style: normal; 28 | font-display: swap; 29 | } 30 | 31 | @font-face { 32 | font-family: 'Inter'; 33 | src: url('../assets/fonts/inter/InterBold.woff2') format('woff2'); 34 | font-weight: 700; 35 | font-style: normal; 36 | font-display: swap; 37 | } 38 | 39 | .hero-pattern { 40 | background-image: url('../assets/images/hero-pattern.png'); 41 | } 42 | 43 | /* You can override the default Infima variables here. */ 44 | :root { 45 | --ifm-color-primary: #dd4add; 46 | --ifm-color-primary-dark: #b13bb1; 47 | --ifm-color-primary-darker: #852c85; 48 | --ifm-color-primary-darkest: #6f256f; 49 | --ifm-color-primary-light: #e05ce0; 50 | --ifm-color-primary-lighter: #e780e7; 51 | --ifm-color-primary-lightest: #eb92eb; 52 | --ifm-code-font-size: 95%; 53 | --ifm-list-item-margin: 0; 54 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); 55 | --ifm-navbar-background-color: #000e38; 56 | --ifm-navbar-link-color: #ffffff; 57 | --ifm-footer-background-color: #000e38; 58 | --ifm-footer-padding-vertical: 1rem; 59 | } 60 | 61 | /* For readability concerns, you should choose a lighter palette in dark mode. */ 62 | [data-theme='dark'] { 63 | --ifm-color-primary: #dd4add; 64 | --ifm-color-primary-dark: #c743c7; 65 | --ifm-color-primary-darker: #b13bb1; 66 | --ifm-color-primary-darkest: #9b349b; 67 | --ifm-color-primary-light: #eb92eb; 68 | --ifm-color-primary-lighter: #f5c9f5; 69 | --ifm-color-primary-lightest: #f8dbf8; 70 | --ifm-list-item-margin: 0; 71 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); 72 | --ifm-navbar-background-color: #242526; 73 | --ifm-footer-background-color: #242526; 74 | } 75 | 76 | /* Nav */ 77 | .header-github-link::before { 78 | content: ''; 79 | width: 24px; 80 | height: 24px; 81 | display: flex; 82 | background: url("data:image/svg+xml;charset=utf-8,%3Csvg fill='white' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E") 83 | no-repeat; 84 | } 85 | 86 | .navbar__inner button svg { 87 | color: white; 88 | } 89 | 90 | /* Custom Footer */ 91 | .footer__bottom.text--center { 92 | display: flex; 93 | justify-content: space-between; 94 | align-items: center; 95 | } 96 | 97 | .footer__bottom.text--center a { 98 | opacity: 1 !important; 99 | } 100 | 101 | .footer__copyright { 102 | color: white; 103 | } 104 | 105 | /* Demos */ 106 | 107 | .demo-slide { 108 | min-width: 100px; 109 | min-height: 100px; 110 | } 111 | -------------------------------------------------------------------------------- /website/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Layout from '@theme/Layout'; 3 | import { CarouselDemo } from '../components/carousel-demo'; 4 | import { LandingBanner } from '../components/landing/landing-banner'; 5 | import { LandingHero } from '../components/landing/landing-hero'; 6 | import { LandingFeaturedProjects } from '../components/landing/landing-featured-projects'; 7 | import { LandingFeatures } from '../components/landing/landing-features'; 8 | import responsiveFeature from '../assets/images/responsive.png'; 9 | import nativeFeature from '../assets/images/native-support.png'; 10 | import styleFeature from '../assets/images/style.png'; 11 | 12 | export default function Index() { 13 | return ( 14 | 18 |
19 | 33 |
34 | 57 | 58 | 64 | 95 |
96 | ); 97 | } 98 | -------------------------------------------------------------------------------- /website/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/nuka-carousel/e839a97883f06cd29d3a537b51e5ffb67216649c/website/static/.nojekyll -------------------------------------------------------------------------------- /website/static/img/av_cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/nuka-carousel/e839a97883f06cd29d3a537b51e5ffb67216649c/website/static/img/av_cover.jpg -------------------------------------------------------------------------------- /website/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/nuka-carousel/e839a97883f06cd29d3a537b51e5ffb67216649c/website/static/img/favicon.ico -------------------------------------------------------------------------------- /website/static/img/nearform-icon-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /website/static/img/nearform-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /website/static/img/nearform-logo-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /website/static/img/nearform-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /website/static/img/pexels-01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/nuka-carousel/e839a97883f06cd29d3a537b51e5ffb67216649c/website/static/img/pexels-01.jpg -------------------------------------------------------------------------------- /website/static/img/pexels-02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/nuka-carousel/e839a97883f06cd29d3a537b51e5ffb67216649c/website/static/img/pexels-02.jpg -------------------------------------------------------------------------------- /website/static/img/pexels-03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/nuka-carousel/e839a97883f06cd29d3a537b51e5ffb67216649c/website/static/img/pexels-03.jpg -------------------------------------------------------------------------------- /website/static/img/product-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/nuka-carousel/e839a97883f06cd29d3a537b51e5ffb67216649c/website/static/img/product-1.jpg -------------------------------------------------------------------------------- /website/static/img/product-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/nuka-carousel/e839a97883f06cd29d3a537b51e5ffb67216649c/website/static/img/product-2.jpg -------------------------------------------------------------------------------- /website/static/img/product-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/nuka-carousel/e839a97883f06cd29d3a537b51e5ffb67216649c/website/static/img/product-3.jpg -------------------------------------------------------------------------------- /website/static/img/product-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/nuka-carousel/e839a97883f06cd29d3a537b51e5ffb67216649c/website/static/img/product-4.jpg -------------------------------------------------------------------------------- /website/static/img/product-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/nuka-carousel/e839a97883f06cd29d3a537b51e5ffb67216649c/website/static/img/product-5.jpg -------------------------------------------------------------------------------- /website/static/open-graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/nuka-carousel/e839a97883f06cd29d3a537b51e5ffb67216649c/website/static/open-graph.png -------------------------------------------------------------------------------- /website/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | 3 | const NearFormColors = { 4 | White: 'hsla(0, 0%, 100%, 1)', 5 | Black: 'hsla(0, 0%, 0%, 1)', 6 | Green: 'hsla(163, 100%, 45%, 1)', 7 | Purple: 'hsla(260, 100%, 70%, 1)', 8 | LightPurple: 'hsla(262, 100%, 90%, 1)', 9 | Blue: 'hsla(218, 100%, 64%, 1)', 10 | LightBlue: 'hsla(217, 100%, 92%, 1)', 11 | Grey: 'hsla(0, 0%, 85%, 1)', 12 | LightGrey: '#F4F8FA', 13 | DeepGrey: 'hsla(240, 8%, 29%, 1)', 14 | Navy: 'hsla(205, 78%, 21%, 1)', 15 | LightNavy: 'hsla(222, 25%, 43%, 1)', 16 | DeepNavy: 'hsla(225, 100%, 11%, 1)', 17 | }; 18 | 19 | module.exports = { 20 | corePlugins: { 21 | preflight: false, // disable Tailwind's reset 22 | }, 23 | content: ['./src/**/*.{js,jsx,ts,tsx}', '../docs/**/*.mdx'], // my markdown stuff is in ../docs, not /src 24 | darkMode: ['class', '[data-theme="dark"]'], // hooks into docusaurus' dark mode settigns 25 | theme: { 26 | extend: { 27 | colors: { 28 | transparent: 'transparent', 29 | white: NearFormColors.White, 30 | black: NearFormColors.Black, 31 | grayscale: { 32 | 100: NearFormColors.White, 33 | 200: NearFormColors.LightGrey, 34 | 300: NearFormColors.Grey, 35 | 400: NearFormColors.DeepGrey, 36 | 500: NearFormColors.Black, 37 | 800: '#888888', 38 | }, 39 | 'theme-1': NearFormColors.Green, 40 | 'theme-2': NearFormColors.DeepNavy, 41 | 'theme-3': NearFormColors.DeepNavy, 42 | 'theme-4': NearFormColors.White, 43 | 'header-bg': NearFormColors.White, 44 | 'header-fg': NearFormColors.DeepNavy, 45 | 'button-bg': NearFormColors.Green, 46 | 'button-fg': NearFormColors.DeepNavy, 47 | 'button-bg-hover': NearFormColors.White, 48 | 'button-fg-hover': NearFormColors.DeepNavy, 49 | 'button-border': NearFormColors.Green, 50 | error: '#FF0000', 51 | }, 52 | width: { 53 | prose: '90ch', 54 | }, 55 | fontFamily: { 56 | sans: ['Inter, Helvetica, Arial, sans-serif'], 57 | }, 58 | }, 59 | }, 60 | plugins: [], 61 | }; 62 | -------------------------------------------------------------------------------- /website/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // This file is not used in compilation. It is here just for a nice editor experience. 3 | "extends": "@tsconfig/docusaurus/tsconfig.json", 4 | "compilerOptions": { 5 | "baseUrl": "." 6 | } 7 | } 8 | --------------------------------------------------------------------------------