├── .eslintrc.json ├── .github └── workflows │ └── node.js.yml ├── .gitignore ├── .npmrc ├── .storybook ├── main.js └── preview.js ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── docs ├── 0.7f12979c.iframe.bundle.js ├── 0.a050c77d9d5184a2984c.manager.bundle.js ├── 0.a050c77d9d5184a2984c.manager.bundle.js.LICENSE.txt ├── 1.12a002843cdd15ae748a.manager.bundle.js ├── 4.e60bf65e.iframe.bundle.js ├── 5.7f38562c1a9c59cb048f.manager.bundle.js ├── 5.7f38562c1a9c59cb048f.manager.bundle.js.LICENSE.txt ├── 6.5d0b5dbcc823408b3f75.manager.bundle.js ├── 7.e2782ab74fe8fdd3e210.manager.bundle.js ├── 8.f3dd91517cff71d9e5f0.manager.bundle.js ├── 8.f3dd91517cff71d9e5f0.manager.bundle.js.LICENSE.txt ├── 9.3dc9136cc3d4a723c52c.manager.bundle.js ├── favicon.ico ├── iframe.html ├── index.html ├── main.3de62775ef9963e7b0fb.manager.bundle.js ├── main.c2e2cf0f.iframe.bundle.js ├── project.json ├── runtime~main.00723566a33df1bda3c0.manager.bundle.js ├── runtime~main.695e7dde.iframe.bundle.js ├── vendors~main.c6e5cb4d563df4cc0ecd.manager.bundle.js ├── vendors~main.c6e5cb4d563df4cc0ecd.manager.bundle.js.LICENSE.txt ├── vendors~main.e65b8203.iframe.bundle.js ├── vendors~main.e65b8203.iframe.bundle.js.LICENSE.txt └── vendors~main.e65b8203.iframe.bundle.js.map ├── jest.config.js ├── package-lock.json ├── package.json ├── prettier.config.js ├── react-snap-carousel.gif ├── src ├── use-isomorphic-layout-effect.tsx ├── use-snap-carousel.test.tsx └── use-snap-carousel.tsx ├── stories ├── carousel.module.css ├── carousel.stories.tsx ├── carousel.tsx ├── infinite-carousel.module.css ├── infinite-carousel.stories.tsx ├── infinite-carousel.tsx ├── lib │ ├── button.module.css │ ├── button.tsx │ ├── select.module.css │ └── select.tsx ├── reset.css ├── slideshow.module.css ├── slideshow.stories.tsx └── slideshow.tsx └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["react-app", "react-app/jest"] 3 | } 4 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | pull_request: 10 | branches: [ "main" ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [16.x, 18.x] 20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 21 | 22 | steps: 23 | - uses: actions/checkout@v3 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v3 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | cache: 'npm' 29 | - run: npm ci 30 | - run: npm run build --if-present 31 | - run: npm run lint 32 | - run: npm test 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | dist 4 | *.scratch 5 | .vscode -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.com/ 2 | legacy-peer-deps=true 3 | -------------------------------------------------------------------------------- /.storybook/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | stories: [ 3 | '../stories/**/*.stories.mdx', 4 | '../stories/**/*.stories.@(js|jsx|ts|tsx)' 5 | ], 6 | addons: [ 7 | '@storybook/addon-links', 8 | { 9 | name: '@storybook/addon-essentials', 10 | options: { 11 | actions: false, 12 | controls: false, 13 | docs: false, 14 | toolbars: false 15 | } 16 | }, 17 | 'storybook-css-modules', 18 | '@riscarrott/storybook-source-link' 19 | ], 20 | framework: '@storybook/react' 21 | }; 22 | -------------------------------------------------------------------------------- /.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 | sourceLink: 'https://github.com/richardscarrott/react-snap-carousel', 10 | sourceLinkIcon: 'github' 11 | }; 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | https://github.com/richardscarrott/react-snap-carousel/releases 4 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | riscarrott@googlemail.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 Richard Scarrott 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Snap Carousel 🫰 2 | 3 | [![GitHub package.json version](https://img.shields.io/github/package-json/v/richardscarrott/react-snap-carousel.svg)](https://www.npmjs.com/package/react-snap-carousel) 4 | [![npm downloads](https://img.shields.io/npm/dw/react-snap-carousel)](https://www.npmjs.com/package/react-snap-carousel) 5 | [![CI](https://github.com/richardscarrott/react-snap-carousel/actions/workflows/node.js.yml/badge.svg)](https://github.com/richardscarrott/react-snap-carousel/actions/workflows/node.js.yml) 6 | [![GitHub license](https://img.shields.io/github/license/richardscarrott/react-snap-carousel.svg)](https://github.com/richardscarrott/react-snap-carousel/blob/main/LICENSE) 7 | 8 | DOM-first, headless carousel for React. 9 | 10 | React Snap Carousel leaves the DOM in charge of scrolling and simply computes derived state from the layout, allowing you to progressively enhance a scroll element with responsive carousel controls. 11 | 12 | ![Alt Text](react-snap-carousel.gif) 13 | 14 | 🧈 Utilizes native browser scrolling & CSS scroll snap points for best performance and UX 15 | 16 | 📏 Computes responsive page state from DOM layout & scroll position 17 | 18 | 📲 Dynamic page-based CSS snap point rendering 19 | 20 | 🙈 Headless design, giving you full control over UI using React Hooks API 21 | 22 | 🖋️ Written in TypeScript 23 | 24 | 🪶 [Lightweight (~1kB)](https://bundlephobia.com/package/react-snap-carousel) + zero dependencies 25 | 26 | ## Install 27 | 28 | ``` 29 | npm install react-snap-carousel 30 | ``` 31 | 32 | ## Resources 33 | 34 | 🔥[StoryBook Examples](https://richardscarrott.github.io/react-snap-carousel/)🔥 35 | 36 | [CodeSandbox StarterKit](https://codesandbox.io/s/react-snap-carousel-49vu6p?file=/src/Carousel.tsx) 37 | 38 | [Beginners Tutorial](https://dev.to/richardscarrott/build-your-own-carousel-component-with-react-snap-carousel-1e11) 39 | 40 | ## Usage 41 | 42 | React Snap Carousel doesn't expose a ready-made `` component and instead offers a single export `useSnapCarousel` which provides the state & functions necessary to build your own carousel component. 43 | 44 | The following code snippet is a good starting point. 45 | 46 | > Inline styles are used for simplicity. You can use whichever CSS framework you prefer. 47 | 48 | > You can see it in action on [CodeSandbox](https://codesandbox.io/s/react-snap-carousel-49vu6p?file=/src/Carousel.tsx). 49 | 50 | ```tsx 51 | // Carousel.tsx 52 | import React, { CSSProperties } from 'react'; 53 | import { useSnapCarousel } from 'react-snap-carousel'; 54 | 55 | const styles = { 56 | root: {}, 57 | scroll: { 58 | position: 'relative', 59 | display: 'flex', 60 | overflow: 'auto', 61 | scrollSnapType: 'x mandatory' 62 | }, 63 | item: { 64 | width: '250px', 65 | height: '250px', 66 | flexShrink: 0 67 | }, 68 | itemSnapPoint: { 69 | scrollSnapAlign: 'start' 70 | }, 71 | controls: { 72 | display: 'flex', 73 | justifyContent: 'center', 74 | alignItems: 'center' 75 | }, 76 | nextPrevButton: {}, 77 | nextPrevButtonDisabled: { opacity: 0.3 }, 78 | pagination: { 79 | display: 'flex' 80 | }, 81 | paginationButton: { 82 | margin: '10px' 83 | }, 84 | paginationButtonActive: { opacity: 0.3 }, 85 | pageIndicator: { 86 | display: 'flex', 87 | justifyContent: 'center' 88 | } 89 | } satisfies Record; 90 | 91 | interface CarouselProps { 92 | readonly items: T[]; 93 | readonly renderItem: ( 94 | props: CarouselRenderItemProps 95 | ) => React.ReactElement; 96 | } 97 | 98 | interface CarouselRenderItemProps { 99 | readonly item: T; 100 | readonly isSnapPoint: boolean; 101 | } 102 | 103 | export const Carousel = ({ 104 | items, 105 | renderItem 106 | }: CarouselProps) => { 107 | const { 108 | scrollRef, 109 | pages, 110 | activePageIndex, 111 | hasPrevPage, 112 | hasNextPage, 113 | prev, 114 | next, 115 | goTo, 116 | snapPointIndexes 117 | } = useSnapCarousel(); 118 | return ( 119 |
120 |
    121 | {items.map((item, i) => 122 | renderItem({ 123 | item, 124 | isSnapPoint: snapPointIndexes.has(i) 125 | }) 126 | )} 127 |
128 |
129 | 139 | {pages.map((_, i) => ( 140 | 150 | ))} 151 | 161 |
162 |
163 | {activePageIndex + 1} / {pages.length} 164 |
165 |
166 | ); 167 | }; 168 | 169 | interface CarouselItemProps { 170 | readonly isSnapPoint: boolean; 171 | readonly children?: React.ReactNode; 172 | } 173 | 174 | export const CarouselItem = ({ isSnapPoint, children }: CarouselItemProps) => ( 175 |
  • 181 | {children} 182 |
  • 183 | ); 184 | ``` 185 | 186 | ```tsx 187 | // App.tsx 188 | import { Carousel, CarouselItem } from './Carousel'; 189 | 190 | const items = Array.from({ length: 20 }).map((_, i) => ({ 191 | id: i, 192 | src: `https://picsum.photos/500?idx=${i}` 193 | })); 194 | 195 | const App = () => ( 196 | ( 199 | 200 | 201 | 202 | )} 203 | /> 204 | ); 205 | 206 | export default App; 207 | ``` 208 | 209 | ## Api 210 | 211 | ### `useSnapCarousel(options)` 212 | 213 | #### Parameters 214 | 215 | ##### Options 216 | 217 | ```ts 218 | interface SnapCarouselOptions { 219 | // Horizontal or vertical carousel 220 | readonly axis?: 'x' | 'y'; 221 | // Allows you to render pagination during SSR 222 | readonly initialPages?: number[][]; 223 | } 224 | ``` 225 | 226 | #### Return value 227 | 228 | ```ts 229 | export interface SnapCarouselResult { 230 | readonly pages: number[][]; 231 | readonly activePageIndex: number; 232 | readonly snapPointIndexes: Set; 233 | readonly hasPrevPage: boolean; 234 | readonly hasNextPage: boolean; 235 | readonly prev: (opts?: SnapCarouselGoToOptions) => void; 236 | readonly next: (opts?: SnapCarouselGoToOptions) => void; 237 | readonly goTo: (pageIndex: number, opts?: SnapCarouselGoToOptions) => void; 238 | readonly refresh: () => void; 239 | readonly scrollRef: (el: HTMLElement | null) => void; 240 | } 241 | ``` 242 | 243 | ## License 244 | 245 | [MIT](LICENSE) 246 | -------------------------------------------------------------------------------- /docs/0.7f12979c.iframe.bundle.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[0],{"./node_modules/@storybook/preview-web/dist/esm/renderDocs.js":function(module,__webpack_exports__,__webpack_require__){"use strict";__webpack_require__.r(__webpack_exports__),__webpack_require__.d(__webpack_exports__,"renderDocs",(function(){return renderDocs})),__webpack_require__.d(__webpack_exports__,"unmountDocs",(function(){return unmountDocs}));__webpack_require__("./node_modules/regenerator-runtime/runtime.js"),__webpack_require__("./node_modules/core-js/modules/es.object.to-string.js"),__webpack_require__("./node_modules/core-js/modules/es.promise.js");var react=__webpack_require__("./node_modules/react/index.js"),react_default=__webpack_require__.n(react),react_dom=__webpack_require__("./node_modules/react-dom/index.js"),react_dom_default=__webpack_require__.n(react_dom),wrapper={fontSize:"14px",letterSpacing:"0.2px",margin:"10px 0"},main={margin:"auto",padding:30,borderRadius:10,background:"rgba(0,0,0,0.03)"},heading={textAlign:"center"},NoDocs_NoDocs=function NoDocs(){return react_default.a.createElement("div",{style:wrapper,className:"sb-nodocs sb-wrapper"},react_default.a.createElement("div",{style:main},react_default.a.createElement("h1",{style:heading},"No Docs"),react_default.a.createElement("p",null,"Sorry, but there are no docs for the selected story. To add them, set the story's ",react_default.a.createElement("code",null,"docs")," parameter. If you think this is an error:"),react_default.a.createElement("ul",null,react_default.a.createElement("li",null,"Please check the story definition."),react_default.a.createElement("li",null,"Please check the Storybook config."),react_default.a.createElement("li",null,"Try reloading the page.")),react_default.a.createElement("p",null,"If the problem persists, check the browser console, or the terminal you've run Storybook from.")))};function asyncGeneratorStep(gen,resolve,reject,_next,_throw,key,arg){try{var info=gen[key](arg),value=info.value}catch(error){return void reject(error)}info.done?resolve(value):Promise.resolve(value).then(_next,_throw)}function _asyncToGenerator(fn){return function(){var self=this,args=arguments;return new Promise((function(resolve,reject){var gen=fn.apply(self,args);function _next(value){asyncGeneratorStep(gen,resolve,reject,_next,_throw,"next",value)}function _throw(err){asyncGeneratorStep(gen,resolve,reject,_next,_throw,"throw",err)}_next(void 0)}))}}function renderDocs(story,docsContext,element,callback){return function renderDocsAsync(_x,_x2,_x3){return _renderDocsAsync.apply(this,arguments)}(story,docsContext,element).then(callback)}function _renderDocsAsync(){return(_renderDocsAsync=_asyncToGenerator(regeneratorRuntime.mark((function _callee(story,docsContext,element){var _docs$getContainer,_docs$getPage,docs,DocsContainer,Page,docsElement;return regeneratorRuntime.wrap((function _callee$(_context){for(;;)switch(_context.prev=_context.next){case 0:if(!(null!=(docs=story.parameters.docs)&&docs.getPage||null!=docs&&docs.page)||(null!=docs&&docs.getContainer||null!=docs&&docs.container)){_context.next=3;break}throw new Error("No `docs.container` set, did you run `addon-docs/preset`?");case 3:if(_context.t1=docs.container,_context.t1){_context.next=8;break}return _context.next=7,null===(_docs$getContainer=docs.getContainer)||void 0===_docs$getContainer?void 0:_docs$getContainer.call(docs);case 7:_context.t1=_context.sent;case 8:if(_context.t0=_context.t1,_context.t0){_context.next=11;break}_context.t0=function(_ref){var children=_ref.children;return react_default.a.createElement(react_default.a.Fragment,null,children)};case 11:if(DocsContainer=_context.t0,_context.t3=docs.page,_context.t3){_context.next=17;break}return _context.next=16,null===(_docs$getPage=docs.getPage)||void 0===_docs$getPage?void 0:_docs$getPage.call(docs);case 16:_context.t3=_context.sent;case 17:if(_context.t2=_context.t3,_context.t2){_context.next=20;break}_context.t2=NoDocs_NoDocs;case 20:return Page=_context.t2,docsElement=react_default.a.createElement(DocsContainer,{key:story.componentId,context:docsContext},react_default.a.createElement(Page,null)),_context.next=24,new Promise((function(resolve){react_dom_default.a.render(docsElement,element,resolve)}));case 24:case"end":return _context.stop()}}),_callee)})))).apply(this,arguments)}function unmountDocs(element){react_dom_default.a.unmountComponentAtNode(element)}NoDocs_NoDocs.displayName="NoDocs"}}]); -------------------------------------------------------------------------------- /docs/0.a050c77d9d5184a2984c.manager.bundle.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /** 2 | * Prism: Lightweight, robust, elegant syntax highlighting 3 | * 4 | * @license MIT 5 | * @author Lea Verou 6 | * @namespace 7 | * @public 8 | */ 9 | -------------------------------------------------------------------------------- /docs/4.e60bf65e.iframe.bundle.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[4],{"./node_modules/react-dom/client.js":function(module,exports,__webpack_require__){"use strict";var m=__webpack_require__("./node_modules/react-dom/index.js");exports.createRoot=m.createRoot,exports.hydrateRoot=m.hydrateRoot}}]); -------------------------------------------------------------------------------- /docs/5.7f38562c1a9c59cb048f.manager.bundle.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google Inc. All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.io/license 7 | */ 8 | 9 | /** 10 | * @license 11 | * Copyright Google Inc. All Rights Reserved. 12 | * 13 | * Use of this source code is governed by an MIT-style license that can be 14 | * found in the LICENSE file at https://angular.io/license 15 | */ 16 | 17 | /** 18 | * @license 19 | * Copyright Google Inc. All Rights Reserved. 20 | * 21 | * Use of this source code is governed by an MIT-style license that can be 22 | * found in the LICENSE file at https://angular.io/license 23 | */ 24 | 25 | /** 26 | * @license 27 | * Copyright Google Inc. All Rights Reserved. 28 | * 29 | * Use of this source code is governed by an MIT-style license that can be 30 | * found in the LICENSE file at https://angular.io/license 31 | */ 32 | -------------------------------------------------------------------------------- /docs/6.5d0b5dbcc823408b3f75.manager.bundle.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[6],{794:function(module,__webpack_exports__,__webpack_require__){"use strict";__webpack_require__.r(__webpack_exports__),__webpack_require__.d(__webpack_exports__,"ColorControl",(function(){return ColorControl})),__webpack_require__.d(__webpack_exports__,"default",(function(){return ColorControl}));__webpack_require__(36),__webpack_require__(48),__webpack_require__(64),__webpack_require__(56),__webpack_require__(10),__webpack_require__(134),__webpack_require__(14),__webpack_require__(83),__webpack_require__(82),__webpack_require__(13),__webpack_require__(162),__webpack_require__(18),__webpack_require__(17),__webpack_require__(44),__webpack_require__(43),__webpack_require__(84),__webpack_require__(38),__webpack_require__(27),__webpack_require__(28),__webpack_require__(103),__webpack_require__(207),__webpack_require__(29),__webpack_require__(19),__webpack_require__(21),__webpack_require__(22),__webpack_require__(32);var _ColorPicker,_fallbackColor,_index_681e4b07_js__WEBPACK_IMPORTED_MODULE_26__=__webpack_require__(2),react__WEBPACK_IMPORTED_MODULE_27__=__webpack_require__(0),react__WEBPACK_IMPORTED_MODULE_27___default=__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_27__),_storybook_theming__WEBPACK_IMPORTED_MODULE_28__=__webpack_require__(1);__webpack_require__(16),__webpack_require__(46),__webpack_require__(72);function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}function _typeof(obj){return _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(obj){return typeof obj}:function(obj){return obj&&"function"==typeof Symbol&&obj.constructor===Symbol&&obj!==Symbol.prototype?"symbol":typeof obj},_typeof(obj)}function _slicedToArray(arr,i){return function _arrayWithHoles(arr){if(Array.isArray(arr))return arr}(arr)||function _iterableToArrayLimit(arr,i){var _i=null==arr?null:"undefined"!=typeof Symbol&&arr[Symbol.iterator]||arr["@@iterator"];if(null==_i)return;var _s,_e,_arr=[],_n=!0,_d=!1;try{for(_i=_i.call(arr);!(_n=(_s=_i.next()).done)&&(_arr.push(_s.value),!i||_arr.length!==i);_n=!0);}catch(err){_d=!0,_e=err}finally{try{_n||null==_i.return||_i.return()}finally{if(_d)throw _e}}return _arr}(arr,i)||function _unsupportedIterableToArray(o,minLen){if(!o)return;if("string"==typeof o)return _arrayLikeToArray(o,minLen);var n=Object.prototype.toString.call(o).slice(8,-1);"Object"===n&&o.constructor&&(n=o.constructor.name);if("Map"===n||"Set"===n)return Array.from(o);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return _arrayLikeToArray(o,minLen)}(arr,i)||function _nonIterableRest(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function _arrayLikeToArray(arr,len){(null==len||len>arr.length)&&(len=arr.length);for(var i=0,arr2=new Array(len);i=0||(o[t]=e[t]);return o}function i(e){var t=Object(react__WEBPACK_IMPORTED_MODULE_27__.useRef)(e),n=Object(react__WEBPACK_IMPORTED_MODULE_27__.useRef)((function(e){t.current&&t.current(e)}));return t.current=e,n.current}var s=function s(e,r,t){return void 0===r&&(r=0),void 0===t&&(t=1),e>t?t:e0:_e.buttons>0)&&m.current?g(d(m.current,_e,b.current)):t(!1)},r=function r(){return t(!1)};function t(t){var n=_.current,o=v(m.current),a=t?o.addEventListener:o.removeEventListener;a(n?"touchmove":"mousemove",e),a(n?"touchend":"mouseup",r)}return[function(e){var r=e.nativeEvent,n=m.current;if(n&&(h(r),!function(e,r){return r&&!f(e)}(r,_.current)&&n)){if(f(r)){_.current=!0;var o=r.changedTouches||[];o.length&&(b.current=o[0].identifier)}n.focus(),g(d(n,r,b.current)),t(!0)}},function(e){var r=e.which||e.keyCode;r<37||r>40||(e.preventDefault(),p({left:39===r?.05:37===r?-.05:0,top:40===r?.05:38===r?-.05:0}))},t]}),[p,g]),C=x[0],E=x[1],H=x[2];return Object(react__WEBPACK_IMPORTED_MODULE_27__.useEffect)((function(){return H}),[H]),react__WEBPACK_IMPORTED_MODULE_27___default.a.createElement("div",u({},s,{onTouchStart:C,onMouseDown:C,className:"react-colorful__interactive",ref:m,onKeyDown:E,tabIndex:0,role:"slider"}))})),g=function g(e){return e.filter(Boolean).join(" ")},p=function p(r){var t=r.color,n=r.left,o=r.top,a=void 0===o?.5:o,l=g(["react-colorful__pointer",r.className]);return react__WEBPACK_IMPORTED_MODULE_27___default.a.createElement("div",{className:l,style:{top:100*a+"%",left:100*n+"%"}},react__WEBPACK_IMPORTED_MODULE_27___default.a.createElement("div",{className:"react-colorful__pointer-fill",style:{backgroundColor:t}}))},b=function b(e,r,t){return void 0===r&&(r=0),void 0===t&&(t=Math.pow(10,r)),Math.round(t*e)/t},_={grad:.9,turn:360,rad:360/(2*Math.PI)},x=function x(e){return"#"===e[0]&&(e=e.substr(1)),e.length<6?{r:parseInt(e[0]+e[0],16),g:parseInt(e[1]+e[1],16),b:parseInt(e[2]+e[2],16),a:1}:{r:parseInt(e.substr(0,2),16),g:parseInt(e.substr(2,2),16),b:parseInt(e.substr(4,2),16),a:1}},C=function C(e,r){return void 0===r&&(r="deg"),Number(e)*(_[r]||1)},M=function M(e){var r=e.s,t=e.l;return{h:e.h,s:(r*=(t<50?t:100-t)/100)>0?2*r/(t+r)*100:0,v:t+r,a:e.a}},N=function N(e){var r=e.s,t=e.v,n=e.a,o=(200-r)*t/100;return{h:b(e.h),s:b(o>0&&o<200?r*t/100/(o<=100?o:200-o)*100:0),l:b(o/2),a:b(n,2)}},w=function w(e){var r=N(e);return"hsl("+r.h+", "+r.s+"%, "+r.l+"%)"},y=function y(e){var r=N(e);return"hsla("+r.h+", "+r.s+"%, "+r.l+"%, "+r.a+")"},q=function q(e){var r=e.h,t=e.s,n=e.v,o=e.a;r=r/360*6,t/=100,n/=100;var a=Math.floor(r),l=n*(1-t),u=n*(1-(r-a)*t),c=n*(1-(1-r+a)*t),i=a%6;return{r:b(255*[n,u,l,l,c,n][i]),g:b(255*[c,n,n,u,l,l][i]),b:b(255*[l,l,c,n,n,u][i]),a:b(o,2)}},z=function z(e){var r=e.toString(16);return r.length<2?"0"+r:r},B=function B(e){var r=e.r,t=e.g,n=e.b,o=e.a,a=Math.max(r,t,n),l=a-Math.min(r,t,n),u=l?a===r?(t-n)/l:a===t?2+(n-r)/l:4+(r-t)/l:0;return{h:b(60*(u<0?u+6:u)),s:b(a?l/a*100:0),v:b(a/255*100),a:o}},K=react__WEBPACK_IMPORTED_MODULE_27___default.a.memo((function(r){var t=r.hue,n=r.onChange,o=g(["react-colorful__hue",r.className]);return react__WEBPACK_IMPORTED_MODULE_27___default.a.createElement("div",{className:o},react__WEBPACK_IMPORTED_MODULE_27___default.a.createElement(m,{onMove:function onMove(e){n({h:360*e.left})},onKey:function onKey(e){n({h:s(t+360*e.left,0,360)})},"aria-label":"Hue","aria-valuetext":b(t)},react__WEBPACK_IMPORTED_MODULE_27___default.a.createElement(p,{className:"react-colorful__hue-pointer",left:t/360,color:w({h:t,s:100,v:100,a:1})})))})),L=react__WEBPACK_IMPORTED_MODULE_27___default.a.memo((function(r){var t=r.hsva,n=r.onChange,o={backgroundColor:w({h:t.h,s:100,v:100,a:1})};return react__WEBPACK_IMPORTED_MODULE_27___default.a.createElement("div",{className:"react-colorful__saturation",style:o},react__WEBPACK_IMPORTED_MODULE_27___default.a.createElement(m,{onMove:function onMove(e){n({s:100*e.left,v:100-100*e.top})},onKey:function onKey(e){n({s:s(t.s+100*e.left,0,100),v:s(t.v-100*e.top,0,100)})},"aria-label":"Color","aria-valuetext":"Saturation "+b(t.s)+"%, Brightness "+b(t.v)+"%"},react__WEBPACK_IMPORTED_MODULE_27___default.a.createElement(p,{className:"react-colorful__saturation-pointer",top:1-t.v/100,left:t.s/100,color:w(t)})))})),A=function A(e,r){if(e===r)return!0;for(var t in e)if(e[t]!==r[t])return!1;return!0},S=function S(e,r){return e.replace(/\s/g,"")===r.replace(/\s/g,"")};function T(e,t,l){var u=i(l),c=Object(react__WEBPACK_IMPORTED_MODULE_27__.useState)((function(){return e.toHsva(t)})),s=c[0],f=c[1],v=Object(react__WEBPACK_IMPORTED_MODULE_27__.useRef)({color:t,hsva:s});Object(react__WEBPACK_IMPORTED_MODULE_27__.useEffect)((function(){if(!e.equal(t,v.current.color)){var r=e.toHsva(t);v.current={hsva:r,color:t},f(r)}}),[t,e]),Object(react__WEBPACK_IMPORTED_MODULE_27__.useEffect)((function(){var r;A(s,v.current.hsva)||e.equal(r=e.fromHsva(s),v.current.color)||(v.current={hsva:s,color:r},u(r))}),[s,e,u]);var d=Object(react__WEBPACK_IMPORTED_MODULE_27__.useCallback)((function(e){f((function(r){return Object.assign({},r,e)}))}),[]);return[s,d]}for(var P="undefined"!=typeof window?react__WEBPACK_IMPORTED_MODULE_27__.useLayoutEffect:react__WEBPACK_IMPORTED_MODULE_27__.useEffect,R=new Map,V=function V(e){P((function(){var r=e.current?e.current.ownerDocument:document;if(void 0!==r&&!R.has(r)){var t=r.createElement("style");t.innerHTML='.react-colorful{position:relative;display:flex;flex-direction:column;width:200px;height:200px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:default}.react-colorful__saturation{position:relative;flex-grow:1;border-color:transparent;border-bottom:12px solid #000;border-radius:8px 8px 0 0;background-image:linear-gradient(0deg,#000,transparent),linear-gradient(90deg,#fff,hsla(0,0%,100%,0))}.react-colorful__alpha-gradient,.react-colorful__pointer-fill{content:"";position:absolute;left:0;top:0;right:0;bottom:0;pointer-events:none;border-radius:inherit}.react-colorful__alpha-gradient,.react-colorful__saturation{box-shadow:inset 0 0 0 1px rgba(0,0,0,.05)}.react-colorful__alpha,.react-colorful__hue{position:relative;height:24px}.react-colorful__hue{background:linear-gradient(90deg,red 0,#ff0 17%,#0f0 33%,#0ff 50%,#00f 67%,#f0f 83%,red)}.react-colorful__last-control{border-radius:0 0 8px 8px}.react-colorful__interactive{position:absolute;left:0;top:0;right:0;bottom:0;border-radius:inherit;outline:none;touch-action:none}.react-colorful__pointer{position:absolute;z-index:1;box-sizing:border-box;width:28px;height:28px;transform:translate(-50%,-50%);background-color:#fff;border:2px solid #fff;border-radius:50%;box-shadow:0 2px 4px rgba(0,0,0,.2)}.react-colorful__interactive:focus .react-colorful__pointer{transform:translate(-50%,-50%) scale(1.1)}.react-colorful__alpha,.react-colorful__alpha-pointer{background-color:#fff;background-image:url(\'data:image/svg+xml;charset=utf-8,\')}.react-colorful__saturation-pointer{z-index:3}.react-colorful__hue-pointer{z-index:2}',R.set(r,t);var n=function X(){return __webpack_require__.nc}();n&&t.setAttribute("nonce",n),r.head.appendChild(t)}}),[])},$=function $(t){var n=t.className,o=t.colorModel,a=t.color,l=void 0===a?o.defaultColor:a,i=t.onChange,s=c(t,["className","colorModel","color","onChange"]),f=Object(react__WEBPACK_IMPORTED_MODULE_27__.useRef)(null);V(f);var v=T(o,l,i),d=v[0],h=v[1],m=g(["react-colorful",n]);return react__WEBPACK_IMPORTED_MODULE_27___default.a.createElement("div",u({},s,{ref:f,className:m}),react__WEBPACK_IMPORTED_MODULE_27___default.a.createElement(L,{hsva:d,onChange:h}),react__WEBPACK_IMPORTED_MODULE_27___default.a.createElement(K,{hue:d.h,onChange:h,className:"react-colorful__last-control"}))},G={defaultColor:"000",toHsva:function toHsva(e){return B(x(e))},fromHsva:function fromHsva(e){return t=(r=q(e)).g,n=r.b,"#"+z(r.r)+z(t)+z(n);var r,t,n},equal:function equal(e,r){return e.toLowerCase()===r.toLowerCase()||A(x(e),x(r))}},Q=function Q(r){var t=r.className,n=r.hsva,o=r.onChange,a={backgroundImage:"linear-gradient(90deg, "+y(Object.assign({},n,{a:0}))+", "+y(Object.assign({},n,{a:1}))+")"},l=g(["react-colorful__alpha",t]);return react__WEBPACK_IMPORTED_MODULE_27___default.a.createElement("div",{className:l},react__WEBPACK_IMPORTED_MODULE_27___default.a.createElement("div",{className:"react-colorful__alpha-gradient",style:a}),react__WEBPACK_IMPORTED_MODULE_27___default.a.createElement(m,{onMove:function onMove(e){o({a:e.left})},onKey:function onKey(e){o({a:s(n.a+e.left)})},"aria-label":"Alpha","aria-valuetext":b(100*n.a)+"%"},react__WEBPACK_IMPORTED_MODULE_27___default.a.createElement(p,{className:"react-colorful__alpha-pointer",left:n.a,color:y(n)})))},U=function U(t){var n=t.className,o=t.colorModel,a=t.color,l=void 0===a?o.defaultColor:a,i=t.onChange,s=c(t,["className","colorModel","color","onChange"]),f=Object(react__WEBPACK_IMPORTED_MODULE_27__.useRef)(null);V(f);var v=T(o,l,i),d=v[0],h=v[1],m=g(["react-colorful",n]);return react__WEBPACK_IMPORTED_MODULE_27___default.a.createElement("div",u({},s,{ref:f,className:m}),react__WEBPACK_IMPORTED_MODULE_27___default.a.createElement(L,{hsva:d,onChange:h}),react__WEBPACK_IMPORTED_MODULE_27___default.a.createElement(K,{hue:d.h,onChange:h}),react__WEBPACK_IMPORTED_MODULE_27___default.a.createElement(Q,{hsva:d,onChange:h,className:"react-colorful__last-control"}))},ee={defaultColor:"hsla(0, 0%, 0%, 1)",toHsva:function E(e){var r=/hsla?\(?\s*(-?\d*\.?\d+)(deg|rad|grad|turn)?[,\s]+(-?\d*\.?\d+)%?[,\s]+(-?\d*\.?\d+)%?,?\s*[/\s]*(-?\d*\.?\d+)?(%)?\s*\)?/i.exec(e);return r?M({h:C(r[1],r[2]),s:Number(r[3]),l:Number(r[4]),a:void 0===r[5]?1:Number(r[5])/(r[6]?100:1)}):{h:0,s:0,v:0,a:1}},fromHsva:y,equal:S},ge={defaultColor:"rgba(0, 0, 0, 1)",toHsva:function I(e){var r=/rgba?\(?\s*(-?\d*\.?\d+)(%)?[,\s]+(-?\d*\.?\d+)(%)?[,\s]+(-?\d*\.?\d+)(%)?,?\s*[/\s]*(-?\d*\.?\d+)?(%)?\s*\)?/i.exec(e);return r?B({r:Number(r[1])/(r[2]?100/255:1),g:Number(r[3])/(r[4]?100/255:1),b:Number(r[5])/(r[6]?100/255:1),a:void 0===r[7]?1:Number(r[7])/(r[8]?100:1)}):{h:0,s:0,v:0,a:1}},fromHsva:function fromHsva(e){var r=q(e);return"rgba("+r.r+", "+r.g+", "+r.b+", "+r.a+")"},equal:S},cssKeywords={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},reverseKeywords={},_i=0,_Object$keys=Object.keys(cssKeywords);_i<_Object$keys.length;_i++){var key=_Object$keys[_i];reverseKeywords[cssKeywords[key]]=key}for(var convert$1={rgb:{channels:3,labels:"rgb"},hsl:{channels:3,labels:"hsl"},hsv:{channels:3,labels:"hsv"},hwb:{channels:3,labels:"hwb"},cmyk:{channels:4,labels:"cmyk"},xyz:{channels:3,labels:"xyz"},lab:{channels:3,labels:"lab"},lch:{channels:3,labels:"lch"},hex:{channels:1,labels:["hex"]},keyword:{channels:1,labels:["keyword"]},ansi16:{channels:1,labels:["ansi16"]},ansi256:{channels:1,labels:["ansi256"]},hcg:{channels:3,labels:["h","c","g"]},apple:{channels:3,labels:["r16","g16","b16"]},gray:{channels:1,labels:["gray"]}},conversions$2=convert$1,_i2=0,_Object$keys2=Object.keys(convert$1);_i2<_Object$keys2.length;_i2++){var model=_Object$keys2[_i2];if(!("channels"in convert$1[model]))throw new Error("missing channels property: "+model);if(!("labels"in convert$1[model]))throw new Error("missing channel labels property: "+model);if(convert$1[model].labels.length!==convert$1[model].channels)throw new Error("channel and label counts mismatch: "+model);var _convert$1$model=convert$1[model],channels=_convert$1$model.channels,labels=_convert$1$model.labels;delete convert$1[model].channels,delete convert$1[model].labels,Object.defineProperty(convert$1[model],"channels",{value:channels}),Object.defineProperty(convert$1[model],"labels",{value:labels})}function comparativeDistance(x,y){return Math.pow(x[0]-y[0],2)+Math.pow(x[1]-y[1],2)+Math.pow(x[2]-y[2],2)}convert$1.rgb.hsl=function(rgb){var h,r=rgb[0]/255,g=rgb[1]/255,b=rgb[2]/255,min=Math.min(r,g,b),max=Math.max(r,g,b),delta=max-min;max===min?h=0:r===max?h=(g-b)/delta:g===max?h=2+(b-r)/delta:b===max&&(h=4+(r-g)/delta),(h=Math.min(60*h,360))<0&&(h+=360);var l=(min+max)/2;return[h,100*(max===min?0:l<=.5?delta/(max+min):delta/(2-max-min)),100*l]},convert$1.rgb.hsv=function(rgb){var rdif,gdif,bdif,h,s,r=rgb[0]/255,g=rgb[1]/255,b=rgb[2]/255,v=Math.max(r,g,b),diff=v-Math.min(r,g,b),diffc=function diffc(c){return(v-c)/6/diff+.5};return 0===diff?(h=0,s=0):(s=diff/v,rdif=diffc(r),gdif=diffc(g),bdif=diffc(b),r===v?h=bdif-gdif:g===v?h=1/3+rdif-bdif:b===v&&(h=2/3+gdif-rdif),h<0?h+=1:h>1&&(h-=1)),[360*h,100*s,100*v]},convert$1.rgb.hwb=function(rgb){var r=rgb[0],g=rgb[1],b=rgb[2];return[convert$1.rgb.hsl(rgb)[0],100*(1/255*Math.min(r,Math.min(g,b))),100*(b=1-1/255*Math.max(r,Math.max(g,b)))]},convert$1.rgb.cmyk=function(rgb){var r=rgb[0]/255,g=rgb[1]/255,b=rgb[2]/255,k=Math.min(1-r,1-g,1-b);return[100*((1-r-k)/(1-k)||0),100*((1-g-k)/(1-k)||0),100*((1-b-k)/(1-k)||0),100*k]},convert$1.rgb.keyword=function(rgb){var reversed=reverseKeywords[rgb];if(reversed)return reversed;for(var currentClosestKeyword,currentClosestDistance=1/0,_i3=0,_Object$keys3=Object.keys(cssKeywords);_i3<_Object$keys3.length;_i3++){var keyword=_Object$keys3[_i3],distance=comparativeDistance(rgb,cssKeywords[keyword]);distance.04045?Math.pow((r+.055)/1.055,2.4):r/12.92)+.3576*(g=g>.04045?Math.pow((g+.055)/1.055,2.4):g/12.92)+.1805*(b=b>.04045?Math.pow((b+.055)/1.055,2.4):b/12.92)),100*(.2126*r+.7152*g+.0722*b),100*(.0193*r+.1192*g+.9505*b)]},convert$1.rgb.lab=function(rgb){var xyz=convert$1.rgb.xyz(rgb),x=xyz[0],y=xyz[1],z=xyz[2];return y/=100,z/=108.883,x=(x/=95.047)>.008856?Math.pow(x,1/3):7.787*x+16/116,[116*(y=y>.008856?Math.pow(y,1/3):7.787*y+16/116)-16,500*(x-y),200*(y-(z=z>.008856?Math.pow(z,1/3):7.787*z+16/116))]},convert$1.hsl.rgb=function(hsl){var t2,t3,val,h=hsl[0]/360,s=hsl[1]/100,l=hsl[2]/100;if(0===s)return[val=255*l,val,val];for(var t1=2*l-(t2=l<.5?l*(1+s):l+s-l*s),rgb=[0,0,0],_i4=0;_i4<3;_i4++)(t3=h+1/3*-(_i4-1))<0&&t3++,t3>1&&t3--,val=6*t3<1?t1+6*(t2-t1)*t3:2*t3<1?t2:3*t3<2?t1+(t2-t1)*(2/3-t3)*6:t1,rgb[_i4]=255*val;return rgb},convert$1.hsl.hsv=function(hsl){var h=hsl[0],s=hsl[1]/100,l=hsl[2]/100,smin=s,lmin=Math.max(l,.01);return s*=(l*=2)<=1?l:2-l,smin*=lmin<=1?lmin:2-lmin,[h,100*(0===l?2*smin/(lmin+smin):2*s/(l+s)),100*((l+s)/2)]},convert$1.hsv.rgb=function(hsv){var h=hsv[0]/60,s=hsv[1]/100,v=hsv[2]/100,hi=Math.floor(h)%6,f=h-Math.floor(h),p=255*v*(1-s),q=255*v*(1-s*f),t=255*v*(1-s*(1-f));switch(v*=255,hi){case 0:return[v,t,p];case 1:return[q,v,p];case 2:return[p,v,t];case 3:return[p,q,v];case 4:return[t,p,v];case 5:return[v,p,q]}},convert$1.hsv.hsl=function(hsv){var sl,l,h=hsv[0],s=hsv[1]/100,v=hsv[2]/100,vmin=Math.max(v,.01);l=(2-s)*v;var lmin=(2-s)*vmin;return sl=s*vmin,[h,100*(sl=(sl/=lmin<=1?lmin:2-lmin)||0),100*(l/=2)]},convert$1.hwb.rgb=function(hwb){var f,h=hwb[0]/360,wh=hwb[1]/100,bl=hwb[2]/100,ratio=wh+bl;ratio>1&&(wh/=ratio,bl/=ratio);var i=Math.floor(6*h),v=1-bl;f=6*h-i,0!=(1&i)&&(f=1-f);var r,g,b,n=wh+f*(v-wh);switch(i){default:case 6:case 0:r=v,g=n,b=wh;break;case 1:r=n,g=v,b=wh;break;case 2:r=wh,g=v,b=n;break;case 3:r=wh,g=n,b=v;break;case 4:r=n,g=wh,b=v;break;case 5:r=v,g=wh,b=n}return[255*r,255*g,255*b]},convert$1.cmyk.rgb=function(cmyk){var c=cmyk[0]/100,m=cmyk[1]/100,y=cmyk[2]/100,k=cmyk[3]/100;return[255*(1-Math.min(1,c*(1-k)+k)),255*(1-Math.min(1,m*(1-k)+k)),255*(1-Math.min(1,y*(1-k)+k))]},convert$1.xyz.rgb=function(xyz){var r,g,b,x=xyz[0]/100,y=xyz[1]/100,z=xyz[2]/100;return g=-.9689*x+1.8758*y+.0415*z,b=.0557*x+-.204*y+1.057*z,r=(r=3.2406*x+-1.5372*y+-.4986*z)>.0031308?1.055*Math.pow(r,1/2.4)-.055:12.92*r,g=g>.0031308?1.055*Math.pow(g,1/2.4)-.055:12.92*g,b=b>.0031308?1.055*Math.pow(b,1/2.4)-.055:12.92*b,[255*(r=Math.min(Math.max(0,r),1)),255*(g=Math.min(Math.max(0,g),1)),255*(b=Math.min(Math.max(0,b),1))]},convert$1.xyz.lab=function(xyz){var x=xyz[0],y=xyz[1],z=xyz[2];return y/=100,z/=108.883,x=(x/=95.047)>.008856?Math.pow(x,1/3):7.787*x+16/116,[116*(y=y>.008856?Math.pow(y,1/3):7.787*y+16/116)-16,500*(x-y),200*(y-(z=z>.008856?Math.pow(z,1/3):7.787*z+16/116))]},convert$1.lab.xyz=function(lab){var x,y,z,l=lab[0];x=lab[1]/500+(y=(l+16)/116),z=y-lab[2]/200;var y2=Math.pow(y,3),x2=Math.pow(x,3),z2=Math.pow(z,3);return y=y2>.008856?y2:(y-16/116)/7.787,x=x2>.008856?x2:(x-16/116)/7.787,z=z2>.008856?z2:(z-16/116)/7.787,[x*=95.047,y*=100,z*=108.883]},convert$1.lab.lch=function(lab){var h,l=lab[0],a=lab[1],b=lab[2];return(h=360*Math.atan2(b,a)/2/Math.PI)<0&&(h+=360),[l,Math.sqrt(a*a+b*b),h]},convert$1.lch.lab=function(lch){var l=lch[0],c=lch[1],hr=lch[2]/360*2*Math.PI;return[l,c*Math.cos(hr),c*Math.sin(hr)]},convert$1.rgb.ansi16=function(args){var saturation=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,_args=_slicedToArray(args,3),r=_args[0],g=_args[1],b=_args[2],value=null===saturation?convert$1.rgb.hsv(args)[2]:saturation;if(0===(value=Math.round(value/50)))return 30;var ansi=30+(Math.round(b/255)<<2|Math.round(g/255)<<1|Math.round(r/255));return 2===value&&(ansi+=60),ansi},convert$1.hsv.ansi16=function(args){return convert$1.rgb.ansi16(convert$1.hsv.rgb(args),args[2])},convert$1.rgb.ansi256=function(args){var r=args[0],g=args[1],b=args[2];return r===g&&g===b?r<8?16:r>248?231:Math.round((r-8)/247*24)+232:16+36*Math.round(r/255*5)+6*Math.round(g/255*5)+Math.round(b/255*5)},convert$1.ansi16.rgb=function(args){var color=args%10;if(0===color||7===color)return args>50&&(color+=3.5),[color=color/10.5*255,color,color];var mult=.5*(1+~~(args>50));return[(1&color)*mult*255,(color>>1&1)*mult*255,(color>>2&1)*mult*255]},convert$1.ansi256.rgb=function(args){if(args>=232){var _c=10*(args-232)+8;return[_c,_c,_c]}var rem;return args-=16,[Math.floor(args/36)/5*255,Math.floor((rem=args%36)/6)/5*255,rem%6/5*255]},convert$1.rgb.hex=function(args){var string=(((255&Math.round(args[0]))<<16)+((255&Math.round(args[1]))<<8)+(255&Math.round(args[2]))).toString(16).toUpperCase();return"000000".substring(string.length)+string},convert$1.hex.rgb=function(args){var match=args.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i);if(!match)return[0,0,0];var colorString=match[0];3===match[0].length&&(colorString=colorString.split("").map((function(char){return char+char})).join(""));var integer=parseInt(colorString,16);return[integer>>16&255,integer>>8&255,255&integer]},convert$1.rgb.hcg=function(rgb){var hue,r=rgb[0]/255,g=rgb[1]/255,b=rgb[2]/255,max=Math.max(Math.max(r,g),b),min=Math.min(Math.min(r,g),b),chroma=max-min;return hue=chroma<=0?0:max===r?(g-b)/chroma%6:max===g?2+(b-r)/chroma:4+(r-g)/chroma,hue/=6,[360*(hue%=1),100*chroma,100*(chroma<1?min/(1-chroma):0)]},convert$1.hsl.hcg=function(hsl){var s=hsl[1]/100,l=hsl[2]/100,c=l<.5?2*s*l:2*s*(1-l),f=0;return c<1&&(f=(l-.5*c)/(1-c)),[hsl[0],100*c,100*f]},convert$1.hsv.hcg=function(hsv){var s=hsv[1]/100,v=hsv[2]/100,c=s*v,f=0;return c<1&&(f=(v-c)/(1-c)),[hsv[0],100*c,100*f]},convert$1.hcg.rgb=function(hcg){var h=hcg[0]/360,c=hcg[1]/100,g=hcg[2]/100;if(0===c)return[255*g,255*g,255*g];var mg,pure=[0,0,0],hi=h%1*6,v=hi%1,w=1-v;switch(Math.floor(hi)){case 0:pure[0]=1,pure[1]=v,pure[2]=0;break;case 1:pure[0]=w,pure[1]=1,pure[2]=0;break;case 2:pure[0]=0,pure[1]=1,pure[2]=v;break;case 3:pure[0]=0,pure[1]=w,pure[2]=1;break;case 4:pure[0]=v,pure[1]=0,pure[2]=1;break;default:pure[0]=1,pure[1]=0,pure[2]=w}return mg=(1-c)*g,[255*(c*pure[0]+mg),255*(c*pure[1]+mg),255*(c*pure[2]+mg)]},convert$1.hcg.hsv=function(hcg){var c=hcg[1]/100,v=c+hcg[2]/100*(1-c),f=0;return v>0&&(f=c/v),[hcg[0],100*f,100*v]},convert$1.hcg.hsl=function(hcg){var c=hcg[1]/100,l=hcg[2]/100*(1-c)+.5*c,s=0;return l>0&&l<.5?s=c/(2*l):l>=.5&&l<1&&(s=c/(2*(1-l))),[hcg[0],100*s,100*l]},convert$1.hcg.hwb=function(hcg){var c=hcg[1]/100,v=c+hcg[2]/100*(1-c);return[hcg[0],100*(v-c),100*(1-v)]},convert$1.hwb.hcg=function(hwb){var w=hwb[1]/100,v=1-hwb[2]/100,c=v-w,g=0;return c<1&&(g=(v-c)/(1-c)),[hwb[0],100*c,100*g]},convert$1.apple.rgb=function(apple){return[apple[0]/65535*255,apple[1]/65535*255,apple[2]/65535*255]},convert$1.rgb.apple=function(rgb){return[rgb[0]/255*65535,rgb[1]/255*65535,rgb[2]/255*65535]},convert$1.gray.rgb=function(args){return[args[0]/100*255,args[0]/100*255,args[0]/100*255]},convert$1.gray.hsl=function(args){return[0,0,args[0]]},convert$1.gray.hsv=convert$1.gray.hsl,convert$1.gray.hwb=function(gray){return[0,100,gray[0]]},convert$1.gray.cmyk=function(gray){return[0,0,0,gray[0]]},convert$1.gray.lab=function(gray){return[gray[0],0,0]},convert$1.gray.hex=function(gray){var val=255&Math.round(gray[0]/100*255),string=((val<<16)+(val<<8)+val).toString(16).toUpperCase();return"000000".substring(string.length)+string},convert$1.rgb.gray=function(rgb){return[(rgb[0]+rgb[1]+rgb[2])/3/255*100]};var conversions$1=conversions$2;function deriveBFS(fromModel){var graph=function buildGraph(){for(var graph={},models=Object.keys(conversions$1),len=models.length,_i5=0;_i51&&(args=arg0);var result=fn(args);if("object"===_typeof(result))for(var len=result.length,_i8=0;_i81&&(args=arg0),fn(args))};return"conversion"in fn&&(wrappedFn.conversion=fn.conversion),wrappedFn}(fn)}))}));var colorConvert=convert,root=_index_681e4b07_js__WEBPACK_IMPORTED_MODULE_26__.V,now_1=function now$1(){return root.Date.now()},reWhitespace=/\s/;var trimmedEndIndex=function trimmedEndIndex$1(string){for(var index=string.length;index--&&reWhitespace.test(string.charAt(index)););return index},reTrimStart=/^\s+/;var baseTrim=function baseTrim$1(string){return string?string.slice(0,trimmedEndIndex(string)+1).replace(reTrimStart,""):string},isObject$2=_index_681e4b07_js__WEBPACK_IMPORTED_MODULE_26__.Y,isSymbol=_index_681e4b07_js__WEBPACK_IMPORTED_MODULE_26__.Z,reIsBadHex=/^[-+]0x[0-9a-f]+$/i,reIsBinary=/^0b[01]+$/i,reIsOctal=/^0o[0-7]+$/i,freeParseInt=parseInt;var toNumber_1=function toNumber$1(value){if("number"==typeof value)return value;if(isSymbol(value))return NaN;if(isObject$2(value)){var other="function"==typeof value.valueOf?value.valueOf():value;value=isObject$2(other)?other+"":other}if("string"!=typeof value)return 0===value?value:+value;value=baseTrim(value);var isBinary=reIsBinary.test(value);return isBinary||reIsOctal.test(value)?freeParseInt(value.slice(2),isBinary?2:8):reIsBadHex.test(value)?NaN:+value},isObject$1=_index_681e4b07_js__WEBPACK_IMPORTED_MODULE_26__.Y,now=now_1,toNumber=toNumber_1,nativeMax=Math.max,nativeMin=Math.min;var debounce_1=function debounce$1(func,wait,options){var lastArgs,lastThis,maxWait,result,timerId,lastCallTime,lastInvokeTime=0,leading=!1,maxing=!1,trailing=!0;if("function"!=typeof func)throw new TypeError("Expected a function");function invokeFunc(time){var args=lastArgs,thisArg=lastThis;return lastArgs=lastThis=void 0,lastInvokeTime=time,result=func.apply(thisArg,args)}function leadingEdge(time){return lastInvokeTime=time,timerId=setTimeout(timerExpired,wait),leading?invokeFunc(time):result}function shouldInvoke(time){var timeSinceLastCall=time-lastCallTime;return void 0===lastCallTime||timeSinceLastCall>=wait||timeSinceLastCall<0||maxing&&time-lastInvokeTime>=maxWait}function timerExpired(){var time=now();if(shouldInvoke(time))return trailingEdge(time);timerId=setTimeout(timerExpired,function remainingWait(time){var timeWaiting=wait-(time-lastCallTime);return maxing?nativeMin(timeWaiting,maxWait-(time-lastInvokeTime)):timeWaiting}(time))}function trailingEdge(time){return timerId=void 0,trailing&&lastArgs?invokeFunc(time):(lastArgs=lastThis=void 0,result)}function debounced(){var time=now(),isInvoking=shouldInvoke(time);if(lastArgs=arguments,lastThis=this,lastCallTime=time,isInvoking){if(void 0===timerId)return leadingEdge(lastCallTime);if(maxing)return clearTimeout(timerId),timerId=setTimeout(timerExpired,wait),invokeFunc(lastCallTime)}return void 0===timerId&&(timerId=setTimeout(timerExpired,wait)),result}return wait=toNumber(wait)||0,isObject$1(options)&&(leading=!!options.leading,maxWait=(maxing="maxWait"in options)?nativeMax(toNumber(options.maxWait)||0,wait):maxWait,trailing="trailing"in options?!!options.trailing:trailing),debounced.cancel=function cancel(){void 0!==timerId&&clearTimeout(timerId),lastInvokeTime=0,lastArgs=lastCallTime=lastThis=timerId=void 0},debounced.flush=function flush(){return void 0===timerId?result:trailingEdge(now())},debounced},debounce=debounce_1,isObject=_index_681e4b07_js__WEBPACK_IMPORTED_MODULE_26__.Y;var ColorSpace,throttle_1=function throttle(func,wait,options){var leading=!0,trailing=!0;if("function"!=typeof func)throw new TypeError("Expected a function");return isObject(options)&&(leading="leading"in options?!!options.leading:leading,trailing="trailing"in options?!!options.trailing:trailing),debounce(func,wait,{leading:leading,maxWait:wait,trailing:trailing})},Wrapper=_storybook_theming__WEBPACK_IMPORTED_MODULE_28__.l.div({position:"relative",maxWidth:250}),PickerTooltip=Object(_storybook_theming__WEBPACK_IMPORTED_MODULE_28__.l)(_index_681e4b07_js__WEBPACK_IMPORTED_MODULE_26__.f)({position:"absolute",zIndex:1,top:4,left:4}),TooltipContent=_storybook_theming__WEBPACK_IMPORTED_MODULE_28__.l.div({width:200,margin:5,".react-colorful__saturation":{borderRadius:"4px 4px 0 0"},".react-colorful__hue":{boxShadow:"inset 0 0 0 1px rgb(0 0 0 / 5%)"},".react-colorful__last-control":{borderRadius:"0 0 4px 4px"}}),Note=Object(_storybook_theming__WEBPACK_IMPORTED_MODULE_28__.l)(_index_681e4b07_js__WEBPACK_IMPORTED_MODULE_26__.e)((function(_ref){return{fontFamily:_ref.theme.typography.fonts.base}})),Swatches=_storybook_theming__WEBPACK_IMPORTED_MODULE_28__.l.div({display:"grid",gridTemplateColumns:"repeat(9, 16px)",gap:6,padding:3,marginTop:5,width:200}),SwatchColor=_storybook_theming__WEBPACK_IMPORTED_MODULE_28__.l.div((function(_ref2){var theme=_ref2.theme;return{width:16,height:16,boxShadow:_ref2.active?"".concat(theme.appBorderColor," 0 0 0 1px inset, ").concat(theme.color.mediumdark,"50 0 0 0 4px"):"".concat(theme.appBorderColor," 0 0 0 1px inset"),borderRadius:theme.appBorderRadius}})),Swatch=function Swatch(_a){var value=_a.value,active=_a.active,onClick=_a.onClick,style=_a.style,props=Object(_index_681e4b07_js__WEBPACK_IMPORTED_MODULE_26__.E)(_a,["value","active","onClick","style"]),backgroundImage="linear-gradient(".concat(value,", ").concat(value,"), ").concat('url(\'data:image/svg+xml;charset=utf-8,\')',", linear-gradient(#fff, #fff)");return react__WEBPACK_IMPORTED_MODULE_27___default.a.createElement(SwatchColor,Object.assign({},props,{active:active,onClick:onClick},{style:Object.assign(Object.assign({},style),{backgroundImage:backgroundImage})}))},Input=Object(_storybook_theming__WEBPACK_IMPORTED_MODULE_28__.l)(_index_681e4b07_js__WEBPACK_IMPORTED_MODULE_26__.b.Input)((function(_ref3){return{width:"100%",paddingLeft:30,paddingRight:30,boxSizing:"border-box",fontFamily:_ref3.theme.typography.fonts.base}})),ToggleIcon=Object(_storybook_theming__WEBPACK_IMPORTED_MODULE_28__.l)(_index_681e4b07_js__WEBPACK_IMPORTED_MODULE_26__.c)((function(_ref4){return{position:"absolute",zIndex:1,top:6,right:7,width:20,height:20,padding:4,boxSizing:"border-box",cursor:"pointer",color:_ref4.theme.input.color}}));!function(ColorSpace){ColorSpace.RGB="rgb",ColorSpace.HSL="hsl",ColorSpace.HEX="hex"}(ColorSpace||(ColorSpace={}));var COLOR_SPACES=Object.values(ColorSpace),COLOR_REGEXP=/\(([0-9]+),\s*([0-9]+)%?,\s*([0-9]+)%?,?\s*([0-9.]+)?\)/,RGB_REGEXP=/^\s*rgba?\(([0-9]+),\s*([0-9]+),\s*([0-9]+),?\s*([0-9.]+)?\)\s*$/i,HSL_REGEXP=/^\s*hsla?\(([0-9]+),\s*([0-9]+)%,\s*([0-9]+)%,?\s*([0-9.]+)?\)\s*$/i,HEX_REGEXP=/^\s*#?([0-9a-f]{3}|[0-9a-f]{6})\s*$/i,SHORTHEX_REGEXP=/^\s*#?([0-9a-f]{3})\s*$/i,ColorPicker=(_defineProperty(_ColorPicker={},ColorSpace.HEX,(function J(r){return react__WEBPACK_IMPORTED_MODULE_27___default.a.createElement($,u({},r,{colorModel:G}))})),_defineProperty(_ColorPicker,ColorSpace.RGB,(function pe(r){return react__WEBPACK_IMPORTED_MODULE_27___default.a.createElement(U,u({},r,{colorModel:ge}))})),_defineProperty(_ColorPicker,ColorSpace.HSL,(function re(r){return react__WEBPACK_IMPORTED_MODULE_27___default.a.createElement(U,u({},r,{colorModel:ee}))})),_ColorPicker),fallbackColor=(_defineProperty(_fallbackColor={},ColorSpace.HEX,"transparent"),_defineProperty(_fallbackColor,ColorSpace.RGB,"rgba(0, 0, 0, 0)"),_defineProperty(_fallbackColor,ColorSpace.HSL,"hsla(0, 0%, 0%, 0)"),_fallbackColor),stringToArgs=function stringToArgs(value){var match=null==value?void 0:value.match(COLOR_REGEXP);if(!match)return[0,0,0,1];var _match=_slicedToArray(match,5),x=_match[1],y=_match[2],z=_match[3],_match$=_match[4];return[x,y,z,void 0===_match$?1:_match$].map(Number)},parseValue=function parseValue(value){var _ref11;if(value){var valid=!0;if(RGB_REGEXP.test(value)){var _ref7,_stringToArgs2=_slicedToArray(stringToArgs(value),4),r=_stringToArgs2[0],_g=_stringToArgs2[1],_b=_stringToArgs2[2],a=_stringToArgs2[3],_ref6=_slicedToArray(colorConvert.rgb.hsl([r,_g,_b])||[0,0,0],3),_h=_ref6[0],_s2=_ref6[1],l=_ref6[2];return _defineProperty(_ref7={valid:valid,value:value,keyword:colorConvert.rgb.keyword([r,_g,_b]),colorSpace:ColorSpace.RGB},ColorSpace.RGB,value),_defineProperty(_ref7,ColorSpace.HSL,"hsla(".concat(_h,", ").concat(_s2,"%, ").concat(l,"%, ").concat(a,")")),_defineProperty(_ref7,ColorSpace.HEX,"#".concat(colorConvert.rgb.hex([r,_g,_b]).toLowerCase())),_ref7}if(HSL_REGEXP.test(value)){var _ref10,_stringToArgs4=_slicedToArray(stringToArgs(value),4),_h2=_stringToArgs4[0],_s3=_stringToArgs4[1],_l=_stringToArgs4[2],_a2=_stringToArgs4[3],_ref9=_slicedToArray(colorConvert.hsl.rgb([_h2,_s3,_l])||[0,0,0],3),_r=_ref9[0],_g2=_ref9[1],_b2=_ref9[2];return _defineProperty(_ref10={valid:valid,value:value,keyword:colorConvert.hsl.keyword([_h2,_s3,_l]),colorSpace:ColorSpace.HSL},ColorSpace.RGB,"rgba(".concat(_r,", ").concat(_g2,", ").concat(_b2,", ").concat(_a2,")")),_defineProperty(_ref10,ColorSpace.HSL,value),_defineProperty(_ref10,ColorSpace.HEX,"#".concat(colorConvert.hsl.hex([_h2,_s3,_l]).toLowerCase())),_ref10}var plain=value.replace("#",""),rgb=colorConvert.keyword.rgb(plain)||colorConvert.hex.rgb(plain),hsl=colorConvert.rgb.hsl(rgb),mapped=value;if(/[^#a-f0-9]/i.test(value)?mapped=plain:HEX_REGEXP.test(value)&&(mapped="#".concat(plain)),mapped.startsWith("#"))valid=HEX_REGEXP.test(mapped);else try{colorConvert.keyword.hex(mapped)}catch(e){valid=!1}return _defineProperty(_ref11={valid:valid,value:mapped,keyword:colorConvert.rgb.keyword(rgb),colorSpace:ColorSpace.HEX},ColorSpace.RGB,"rgba(".concat(rgb[0],", ").concat(rgb[1],", ").concat(rgb[2],", 1)")),_defineProperty(_ref11,ColorSpace.HSL,"hsla(".concat(hsl[0],", ").concat(hsl[1],"%, ").concat(hsl[2],"%, 1)")),_defineProperty(_ref11,ColorSpace.HEX,mapped),_ref11}},useColorInput=function useColorInput(initialValue,onChange){var _useState2=_slicedToArray(Object(react__WEBPACK_IMPORTED_MODULE_27__.useState)(initialValue||""),2),value=_useState2[0],setValue=_useState2[1],_useState4=_slicedToArray(Object(react__WEBPACK_IMPORTED_MODULE_27__.useState)((function(){return parseValue(value)})),2),color=_useState4[0],setColor=_useState4[1],_useState6=_slicedToArray(Object(react__WEBPACK_IMPORTED_MODULE_27__.useState)((null==color?void 0:color.colorSpace)||ColorSpace.HEX),2),colorSpace=_useState6[0],setColorSpace=_useState6[1];Object(react__WEBPACK_IMPORTED_MODULE_27__.useEffect)((function(){void 0===initialValue&&(setValue(""),setColor(void 0),setColorSpace(ColorSpace.HEX))}),[initialValue]);var realValue=Object(react__WEBPACK_IMPORTED_MODULE_27__.useMemo)((function(){return function getRealValue(value,color,colorSpace){if(!value||!(null==color?void 0:color.valid))return fallbackColor[colorSpace];if(colorSpace!==ColorSpace.HEX)return(null==color?void 0:color[colorSpace])||fallbackColor[colorSpace];if(!color.hex.startsWith("#"))try{return"#".concat(colorConvert.keyword.hex(color.hex))}catch(e){return fallbackColor.hex}var short=color.hex.match(SHORTHEX_REGEXP);if(!short)return HEX_REGEXP.test(color.hex)?color.hex:fallbackColor.hex;var _short$1$split2=_slicedToArray(short[1].split(""),3),r=_short$1$split2[0],g=_short$1$split2[1],b=_short$1$split2[2];return"#".concat(r).concat(r).concat(g).concat(g).concat(b).concat(b)}(value,color,colorSpace).toLowerCase()}),[value,color,colorSpace]),updateValue=Object(react__WEBPACK_IMPORTED_MODULE_27__.useCallback)((function(update){var parsed=parseValue(update);setValue((null==parsed?void 0:parsed.value)||update||""),parsed&&(setColor(parsed),setColorSpace(parsed.colorSpace),onChange(parsed.value))}),[onChange]),cycleColorSpace=Object(react__WEBPACK_IMPORTED_MODULE_27__.useCallback)((function(){var next=COLOR_SPACES.indexOf(colorSpace)+1;next>=COLOR_SPACES.length&&(next=0),setColorSpace(COLOR_SPACES[next]);var update=(null==color?void 0:color[COLOR_SPACES[next]])||"";setValue(update),onChange(update)}),[color,colorSpace,onChange]);return{value:value,realValue:realValue,updateValue:updateValue,color:color,colorSpace:colorSpace,cycleColorSpace:cycleColorSpace}},id=function id(value){return value.replace(/\s*/,"").toLowerCase()},ColorControl=function ColorControl(_ref12){var name=_ref12.name,initialValue=_ref12.value,onChange=_ref12.onChange,onFocus=_ref12.onFocus,onBlur=_ref12.onBlur,presetColors=_ref12.presetColors,startOpen=_ref12.startOpen,_useColorInput=useColorInput(initialValue,throttle_1(onChange,200)),value=_useColorInput.value,realValue=_useColorInput.realValue,updateValue=_useColorInput.updateValue,color=_useColorInput.color,colorSpace=_useColorInput.colorSpace,cycleColorSpace=_useColorInput.cycleColorSpace,_usePresets=function usePresets(presetColors,currentColor,colorSpace){var _useState8=_slicedToArray(Object(react__WEBPACK_IMPORTED_MODULE_27__.useState)((null==currentColor?void 0:currentColor.valid)?[currentColor]:[]),2),selectedColors=_useState8[0],setSelectedColors=_useState8[1];Object(react__WEBPACK_IMPORTED_MODULE_27__.useEffect)((function(){void 0===currentColor&&setSelectedColors([])}),[currentColor]);var presets=Object(react__WEBPACK_IMPORTED_MODULE_27__.useMemo)((function(){return(presetColors||[]).map((function(preset){return"string"==typeof preset?parseValue(preset):preset.title?Object.assign(Object.assign({},parseValue(preset.color)),{keyword:preset.title}):parseValue(preset.color)})).concat(selectedColors).filter(Boolean).slice(-27)}),[presetColors,selectedColors]),addPreset=Object(react__WEBPACK_IMPORTED_MODULE_27__.useCallback)((function(color){(null==color?void 0:color.valid)&&(presets.some((function(preset){return id(preset[colorSpace])===id(color[colorSpace])}))||setSelectedColors((function(arr){return arr.concat(color)})))}),[colorSpace,presets]);return{presets:presets,addPreset:addPreset}}(presetColors,color,colorSpace),presets=_usePresets.presets,addPreset=_usePresets.addPreset,Picker=ColorPicker[colorSpace];return react__WEBPACK_IMPORTED_MODULE_27___default.a.createElement(Wrapper,null,react__WEBPACK_IMPORTED_MODULE_27___default.a.createElement(PickerTooltip,{trigger:"click",startOpen:startOpen,closeOnClick:!0,onVisibilityChange:function onVisibilityChange(){return addPreset(color)},tooltip:react__WEBPACK_IMPORTED_MODULE_27___default.a.createElement(TooltipContent,null,react__WEBPACK_IMPORTED_MODULE_27___default.a.createElement(Picker,Object.assign({color:"transparent"===realValue?"#000000":realValue},{onChange:updateValue,onFocus:onFocus,onBlur:onBlur})),presets.length>0&&react__WEBPACK_IMPORTED_MODULE_27___default.a.createElement(Swatches,null,presets.map((function(preset,index){return react__WEBPACK_IMPORTED_MODULE_27___default.a.createElement(_index_681e4b07_js__WEBPACK_IMPORTED_MODULE_26__.f,{key:"".concat(preset.value,"-").concat(index),hasChrome:!1,tooltip:react__WEBPACK_IMPORTED_MODULE_27___default.a.createElement(Note,{note:preset.keyword||preset.value})},react__WEBPACK_IMPORTED_MODULE_27___default.a.createElement(Swatch,{value:preset[colorSpace],active:color&&id(preset[colorSpace])===id(color[colorSpace]),onClick:function onClick(){return updateValue(preset.value)}}))}))))},react__WEBPACK_IMPORTED_MODULE_27___default.a.createElement(Swatch,{value:realValue,style:{margin:4}})),react__WEBPACK_IMPORTED_MODULE_27___default.a.createElement(Input,{id:Object(_index_681e4b07_js__WEBPACK_IMPORTED_MODULE_26__.ab)(name),value:value,onChange:function onChange(e){return updateValue(e.target.value)},onFocus:function onFocus(e){return e.target.select()},placeholder:"Choose color..."}),value?react__WEBPACK_IMPORTED_MODULE_27___default.a.createElement(ToggleIcon,{icon:"markup",onClick:cycleColorSpace}):null)}}}]); -------------------------------------------------------------------------------- /docs/7.e2782ab74fe8fdd3e210.manager.bundle.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[7],{792:function(module,__webpack_exports__,__webpack_require__){"use strict";__webpack_require__.r(__webpack_exports__),__webpack_require__.d(__webpack_exports__,"default",(function(){return GlobalScrollAreaStyles})),__webpack_require__.d(__webpack_exports__,"getScrollAreaStyles",(function(){return getScrollAreaStyles}));__webpack_require__(27),__webpack_require__(57);var _templateObject,react__WEBPACK_IMPORTED_MODULE_2__=__webpack_require__(0),react__WEBPACK_IMPORTED_MODULE_2___default=__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_2__),_storybook_theming__WEBPACK_IMPORTED_MODULE_3__=__webpack_require__(1);var hsResizeObserverDummyAnimation=Object(_storybook_theming__WEBPACK_IMPORTED_MODULE_3__.j)(_templateObject||(_templateObject=function _taggedTemplateLiteral(strings,raw){return raw||(raw=strings.slice(0)),Object.freeze(Object.defineProperties(strings,{raw:{value:Object.freeze(raw)}}))}(["0%{z-index:0}to{z-index:-1}"]))),getScrollAreaStyles=function getScrollAreaStyles(theme){return{"html.os-html, html.os-html>.os-host":{display:"block",overflow:"hidden",boxSizing:"border-box",height:"100%!important",width:"100%!important",minWidth:"100%!important",minHeight:"100%!important",margin:"0!important",position:"absolute!important"},"html.os-html>.os-host>.os-padding":{position:"absolute"},"body.os-dragging, body.os-dragging *":{cursor:"default"},".os-host, .os-host-textarea":{position:"relative",overflow:"visible!important",flexDirection:"column",flexWrap:"nowrap",justifyContent:"flex-start",alignContent:"flex-start",alignItems:"flex-start"},".os-host-flexbox":{overflow:"hidden!important",display:"flex"},".os-host-flexbox>.os-size-auto-observer":{height:"inherit!important"},".os-host-flexbox>.os-content-glue":{flexGrow:1,flexShrink:0},".os-host-flexbox>.os-size-auto-observer, .os-host-flexbox>.os-content-glue":{minHeight:0,minWidth:0,flexGrow:0,flexShrink:1,flexBasis:"auto"},"#os-dummy-scrollbar-size":{position:"fixed",opacity:0,visibility:"hidden",overflow:"scroll",height:500,width:500},"#os-dummy-scrollbar-size>div":{width:"200%",height:"200%",margin:10},"#os-dummy-scrollbar-size, .os-viewport":{},".os-viewport-native-scrollbars-invisible#os-dummy-scrollbar-size, .os-viewport-native-scrollbars-invisible.os-viewport":{scrollbarWidth:"none!important"},".os-viewport-native-scrollbars-invisible#os-dummy-scrollbar-size::-webkit-scrollbar, .os-viewport-native-scrollbars-invisible.os-viewport::-webkit-scrollbar, .os-viewport-native-scrollbars-invisible#os-dummy-scrollbar-size::-webkit-scrollbar-corner, .os-viewport-native-scrollbars-invisible.os-viewport::-webkit-scrollbar-corner":{display:"none!important",width:"0!important",height:"0!important",visibility:"hidden!important",background:"0 0!important"},".os-content-glue":{boxSizing:"inherit",maxHeight:"100%",maxWidth:"100%",width:"100%",pointerEvents:"none"},".os-padding":{boxSizing:"inherit",direction:"inherit",position:"absolute",overflow:"visible",padding:0,margin:0,left:0,top:0,bottom:0,right:0,width:"auto!important",height:"auto!important",zIndex:1},".os-host-overflow>.os-padding":{overflow:"hidden"},".os-viewport":{direction:"inherit!important",boxSizing:"inherit!important",resize:"none!important",outline:"0!important",position:"absolute",overflow:"hidden",top:0,left:0,bottom:0,right:0,padding:0,margin:0},".os-content-arrange":{position:"absolute",zIndex:-1,minHeight:1,minWidth:1,pointerEvents:"none"},".os-content":{direction:"inherit",boxSizing:"border-box!important",position:"relative",display:"block",height:"100%",width:"100%",visibility:"visible"},".os-content:before, .os-content:after":{content:"''",display:"table",width:0,height:0,lineHeight:0,fontSize:0},".os-content>.os-textarea":{boxSizing:"border-box!important",direction:"inherit!important",background:"0 0!important",outline:"0 transparent!important",overflow:"hidden!important",position:"absolute!important",display:"block!important",top:"0!important",left:"0!important",margin:"0!important",borderRadius:"0!important",float:"none!important",filter:"none!important",border:"0!important",resize:"none!important",transform:"none!important",maxWidth:"none!important",maxHeight:"none!important",boxShadow:"none!important",perspective:"none!important",opacity:"1!important",zIndex:"1!important",clip:"auto!important",verticalAlign:"baseline!important",padding:0},".os-host-rtl>.os-padding>.os-viewport>.os-content>.os-textarea":{right:"0!important"},".os-content>.os-textarea-cover":{zIndex:-1,pointerEvents:"none"},".os-content>.os-textarea[wrap=off]":{whiteSpace:"pre!important",margin:"0!important"},".os-text-inherit":{fontFamily:"inherit",fontSize:"inherit",fontWeight:"inherit",fontStyle:"inherit",fontVariant:"inherit",textTransform:"inherit",textDecoration:"inherit",textIndent:"inherit",textAlign:"inherit",textShadow:"inherit",textOverflow:"inherit",letterSpacing:"inherit",wordSpacing:"inherit",lineHeight:"inherit",unicodeBidi:"inherit",direction:"inherit",color:"inherit",cursor:"text"},".os-resize-observer, .os-resize-observer-host":{boxSizing:"inherit",display:"block",opacity:0,position:"absolute",top:0,left:0,height:"100%",width:"100%",overflow:"hidden",pointerEvents:"none",zIndex:-1},".os-resize-observer-host":{padding:"inherit",border:"inherit",borderColor:"transparent",borderStyle:"solid",boxSizing:"border-box"},".os-resize-observer-host:after":{content:"''"},".os-resize-observer-host>.os-resize-observer, .os-resize-observer-host:after":{height:"200%",width:"200%",padding:"inherit",border:"inherit",margin:0,display:"block",boxSizing:"content-box"},".os-resize-observer.observed, object.os-resize-observer":{boxSizing:"border-box!important"},".os-size-auto-observer":{boxSizing:"inherit!important",height:"100%",width:"inherit",maxWidth:1,position:"relative",float:"left",maxHeight:1,overflow:"hidden",zIndex:-1,padding:0,margin:0,pointerEvents:"none",flexGrow:"inherit",flexShrink:0,flexBasis:0},".os-size-auto-observer>.os-resize-observer":{width:"1000%",height:"1000%",minHeight:1,minWidth:1},".os-resize-observer-item":{position:"absolute",top:0,right:0,bottom:0,left:0,overflow:"hidden",zIndex:-1,opacity:0,direction:"ltr!important",flex:"none!important"},".os-resize-observer-item-final":{position:"absolute",left:0,top:0,transition:"none!important",flex:"none!important"},".os-resize-observer":{animationDuration:".001s",animationName:"".concat(hsResizeObserverDummyAnimation)},".os-host-transition>.os-scrollbar, .os-host-transition>.os-scrollbar-corner":{transition:"opacity .3s,visibility .3s,top .3s,right .3s,bottom .3s,left .3s"},"html.os-html>.os-host>.os-scrollbar":{position:"absolute",zIndex:999999},".os-scrollbar, .os-scrollbar-corner":{position:"absolute",opacity:1,zIndex:1},".os-scrollbar-corner":{bottom:0,right:0,height:10,width:10,backgroundColor:"transparent"},".os-scrollbar":{pointerEvents:"none",padding:2,boxSizing:"border-box",background:0},".os-scrollbar-track":{pointerEvents:"auto",position:"relative",height:"100%",width:"100%",padding:"0!important",border:"0!important"},".os-scrollbar-handle":{pointerEvents:"auto",position:"absolute",width:"100%",height:"100%"},".os-scrollbar-handle-off, .os-scrollbar-track-off":{pointerEvents:"none"},".os-scrollbar.os-scrollbar-unusable, .os-scrollbar.os-scrollbar-unusable *":{pointerEvents:"none!important"},".os-scrollbar.os-scrollbar-unusable .os-scrollbar-handle":{opacity:"0!important"},".os-scrollbar-horizontal":{bottom:0,left:0,right:10,height:10},".os-scrollbar-vertical":{top:0,right:0,bottom:10,width:10},".os-host-rtl>.os-scrollbar-horizontal":{right:0},".os-host-rtl>.os-scrollbar-vertical":{right:"auto",left:0},".os-host-rtl>.os-scrollbar-corner":{right:"auto",left:0},".os-scrollbar-auto-hidden, .os-padding+.os-scrollbar-corner, .os-host-resize-disabled.os-host-scrollbar-horizontal-hidden>.os-scrollbar-corner, .os-host-scrollbar-horizontal-hidden>.os-scrollbar-horizontal, .os-host-resize-disabled.os-host-scrollbar-vertical-hidden>.os-scrollbar-corner, .os-host-scrollbar-vertical-hidden>.os-scrollbar-vertical, .os-scrollbar-horizontal.os-scrollbar-auto-hidden+.os-scrollbar-vertical+.os-scrollbar-corner, .os-scrollbar-horizontal+.os-scrollbar-vertical.os-scrollbar-auto-hidden+.os-scrollbar-corner, .os-scrollbar-horizontal.os-scrollbar-auto-hidden+.os-scrollbar-vertical.os-scrollbar-auto-hidden+.os-scrollbar-corner":{opacity:0,visibility:"hidden",pointerEvents:"none"},".os-scrollbar-corner-resize-both":{cursor:"nwse-resize"},".os-host-rtl>.os-scrollbar-corner-resize-both":{cursor:"nesw-resize"},".os-scrollbar-corner-resize-horizontal":{cursor:"ew-resize"},".os-scrollbar-corner-resize-vertical":{cursor:"ns-resize"},".os-dragging .os-scrollbar-corner.os-scrollbar-corner-resize":{cursor:"default"},".os-host-resize-disabled.os-host-scrollbar-horizontal-hidden>.os-scrollbar-vertical":{top:0,bottom:0},".os-host-resize-disabled.os-host-scrollbar-vertical-hidden>.os-scrollbar-horizontal, .os-host-rtl.os-host-resize-disabled.os-host-scrollbar-vertical-hidden>.os-scrollbar-horizontal":{right:0,left:0},".os-scrollbar:hover, .os-scrollbar-corner.os-scrollbar-corner-resize":{opacity:"1!important",visibility:"visible!important"},".os-scrollbar-corner.os-scrollbar-corner-resize":{backgroundImage:"linear-gradient(135deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0) 50%, rgba(0,0,0,0.4) 50%, rgba(0,0,0,0.4) 100%)",backgroundRepeat:"no-repeat",backgroundPosition:"100% 100%",pointerEvents:"auto!important"},".os-host-rtl>.os-scrollbar-corner.os-scrollbar-corner-resize":{transform:"scale(-1,1)"},".os-host-overflow":{overflow:"hidden!important"},".os-theme-dark.os-host-rtl>.os-scrollbar-horizontal":{left:10,right:0},".os-scrollbar.os-scrollbar-unusable":{background:0},".os-scrollbar>.os-scrollbar-track":{background:0},".os-scrollbar-horizontal>.os-scrollbar-track>.os-scrollbar-handle":{minWidth:30},".os-scrollbar-vertical>.os-scrollbar-track>.os-scrollbar-handle":{minHeight:30},".os-theme-dark.os-host-transition>.os-scrollbar>.os-scrollbar-track>.os-scrollbar-handle":{transition:"background-color .3s"},".os-scrollbar>.os-scrollbar-track>.os-scrollbar-handle, .os-scrollbar>.os-scrollbar-track":{borderRadius:10},".os-scrollbar>.os-scrollbar-track>.os-scrollbar-handle":{background:theme.color.mediumdark,opacity:.5},".os-scrollbar:hover>.os-scrollbar-track>.os-scrollbar-handle":{opacity:.6},".os-scrollbar-horizontal .os-scrollbar-handle:before, .os-scrollbar-vertical .os-scrollbar-handle:before":{content:"''",position:"absolute",left:0,right:0,top:0,bottom:0,display:"block"},".os-theme-dark.os-host-scrollbar-horizontal-hidden>.os-scrollbar-horizontal .os-scrollbar-handle:before, .os-theme-dark.os-host-scrollbar-vertical-hidden>.os-scrollbar-vertical .os-scrollbar-handle:before":{display:"none"},".os-scrollbar-horizontal .os-scrollbar-handle:before":{top:-6,bottom:-2},".os-scrollbar-vertical .os-scrollbar-handle:before":{left:-6,right:-2},".os-host-rtl.os-scrollbar-vertical .os-scrollbar-handle:before":{right:-6,left:-2}}},GlobalScrollAreaStyles=function GlobalScrollAreaStyles(){return react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement(_storybook_theming__WEBPACK_IMPORTED_MODULE_3__.b,{styles:getScrollAreaStyles})}}}]); -------------------------------------------------------------------------------- /docs/8.f3dd91517cff71d9e5f0.manager.bundle.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! 2 | * OverlayScrollbars 3 | * https://github.com/KingSora/OverlayScrollbars 4 | * 5 | * Version: 1.13.0 6 | * 7 | * Copyright KingSora | Rene Haas. 8 | * https://github.com/KingSora 9 | * 10 | * Released under the MIT license. 11 | * Date: 02.08.2020 12 | */ 13 | -------------------------------------------------------------------------------- /docs/9.3dc9136cc3d4a723c52c.manager.bundle.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[9],{845:function(module,exports){module.exports=function(e,n){return n=n||{},new Promise((function(t,r){var s=new XMLHttpRequest,o=[],u=[],i={},a=function(){return{ok:2==(s.status/100|0),statusText:s.statusText,status:s.status,url:s.responseURL,text:function(){return Promise.resolve(s.responseText)},json:function(){return Promise.resolve(s.responseText).then(JSON.parse)},blob:function(){return Promise.resolve(new Blob([s.response]))},clone:a,headers:{keys:function(){return o},entries:function(){return u},get:function(e){return i[e.toLowerCase()]},has:function(e){return e.toLowerCase()in i}}}};for(var l in s.open(n.method||"get",e,!0),s.onload=function(){s.getAllResponseHeaders().replace(/^(.*?):[^\S\n]*([\s\S]*?)$/gm,(function(e,n,t){o.push(n=n.toLowerCase()),u.push([n,t]),i[n]=i[n]?i[n]+","+t:t})),t(a())},s.onerror=r,s.withCredentials="include"==n.credentials,n.headers)s.setRequestHeader(l,n.headers[l]);s.send(n.body||null)}))}}}]); -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardscarrott/react-snap-carousel/d6decb451a15693daab28f119f3ecc1d88e2da40/docs/favicon.ico -------------------------------------------------------------------------------- /docs/iframe.html: -------------------------------------------------------------------------------- 1 | Webpack App

    No Preview

    Sorry, but you either have no stories or none are selected somehow.

    • Please check the Storybook config.
    • Try reloading the page.

    If the problem persists, check the browser console, or the terminal you've run Storybook from.

    -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | Webpack App
    -------------------------------------------------------------------------------- /docs/main.3de62775ef9963e7b0fb.manager.bundle.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[2],{430:function(module,exports,__webpack_require__){__webpack_require__(431),__webpack_require__(786),__webpack_require__(789),__webpack_require__(784),__webpack_require__(785),__webpack_require__(787),__webpack_require__(788),module.exports=__webpack_require__(790)},479:function(module,exports){}},[[430,3,4]]]); -------------------------------------------------------------------------------- /docs/project.json: -------------------------------------------------------------------------------- 1 | {"generatedAt":1727117488648,"builder":{"name":"webpack4"},"hasCustomBabel":false,"hasCustomWebpack":false,"hasStaticDirs":false,"hasStorybookEslint":false,"refCount":0,"packageManager":{"type":"npm","version":"8.1.0"},"storybookVersion":"6.5.14","language":"typescript","storybookPackages":{"@storybook/addon-actions":{"version":"6.5.14"},"@storybook/addon-interactions":{"version":"6.5.14"},"@storybook/builder-webpack4":{"version":"6.5.14"},"@storybook/manager-webpack4":{"version":"6.5.14"},"@storybook/react":{"version":"6.5.14"},"@storybook/testing-library":{"version":"0.0.13"}},"framework":{"name":"react"},"addons":{"@storybook/addon-links":{"version":"6.5.14"},"@storybook/addon-essentials":{"options":{"actions":false,"controls":false,"docs":false,"toolbars":false},"version":"6.5.14"},"storybook-css-modules":{"version":"1.0.8"},"@riscarrott/storybook-source-link":{"version":"2.0.5"}}} 2 | -------------------------------------------------------------------------------- /docs/runtime~main.00723566a33df1bda3c0.manager.bundle.js: -------------------------------------------------------------------------------- 1 | !function(modules){function webpackJsonpCallback(data){for(var moduleId,chunkId,chunkIds=data[0],moreModules=data[1],executeModules=data[2],i=0,resolves=[];i 34 | * 35 | * Copyright (c) 2014-2017, Jon Schlinkert. 36 | * Released under the MIT License. 37 | */ 38 | 39 | /*! ***************************************************************************** 40 | Copyright (c) Microsoft Corporation. 41 | 42 | Permission to use, copy, modify, and/or distribute this software for any 43 | purpose with or without fee is hereby granted. 44 | 45 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 46 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 47 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 48 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 49 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 50 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 51 | PERFORMANCE OF THIS SOFTWARE. 52 | ***************************************************************************** */ 53 | 54 | /*! store2 - v2.13.1 - 2021-12-20 55 | * Copyright (c) 2021 Nathan Bubna; Licensed (MIT OR GPL-3.0) */ 56 | 57 | /** 58 | * @license React 59 | * react-dom.production.min.js 60 | * 61 | * Copyright (c) Facebook, Inc. and its affiliates. 62 | * 63 | * This source code is licensed under the MIT license found in the 64 | * LICENSE file in the root directory of this source tree. 65 | */ 66 | 67 | /** 68 | * @license React 69 | * react.production.min.js 70 | * 71 | * Copyright (c) Facebook, Inc. and its affiliates. 72 | * 73 | * This source code is licensed under the MIT license found in the 74 | * LICENSE file in the root directory of this source tree. 75 | */ 76 | 77 | /** 78 | * @license React 79 | * scheduler.production.min.js 80 | * 81 | * Copyright (c) Facebook, Inc. and its affiliates. 82 | * 83 | * This source code is licensed under the MIT license found in the 84 | * LICENSE file in the root directory of this source tree. 85 | */ 86 | 87 | /** 88 | * React Router DOM v6.0.2 89 | * 90 | * Copyright (c) Remix Software Inc. 91 | * 92 | * This source code is licensed under the MIT license found in the 93 | * LICENSE.md file in the root directory of this source tree. 94 | * 95 | * @license MIT 96 | */ 97 | 98 | /** 99 | * React Router v6.0.2 100 | * 101 | * Copyright (c) Remix Software Inc. 102 | * 103 | * This source code is licensed under the MIT license found in the 104 | * LICENSE.md file in the root directory of this source tree. 105 | * 106 | * @license MIT 107 | */ 108 | -------------------------------------------------------------------------------- /docs/vendors~main.e65b8203.iframe.bundle.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! 2 | Copyright (c) 2018 Jed Watson. 3 | Licensed under the MIT License (MIT), see 4 | http://jedwatson.github.io/classnames 5 | */ 6 | 7 | /*! 8 | * https://github.com/es-shims/es5-shim 9 | * @license es5-shim Copyright 2009-2020 by contributors, MIT License 10 | * see https://github.com/es-shims/es5-shim/blob/master/LICENSE 11 | */ 12 | 13 | /*! 14 | * https://github.com/paulmillr/es6-shim 15 | * @license es6-shim Copyright 2013-2016 by Paul Miller (http://paulmillr.com) 16 | * and contributors, MIT License 17 | * es6-shim: v0.35.4 18 | * see https://github.com/paulmillr/es6-shim/blob/0.35.3/LICENSE 19 | * Details and documentation: 20 | * https://github.com/paulmillr/es6-shim/ 21 | */ 22 | 23 | /*! 24 | * isobject 25 | * 26 | * Copyright (c) 2014-2017, Jon Schlinkert. 27 | * Released under the MIT License. 28 | */ 29 | 30 | /** 31 | * @license React 32 | * react-dom.production.min.js 33 | * 34 | * Copyright (c) Facebook, Inc. and its affiliates. 35 | * 36 | * This source code is licensed under the MIT license found in the 37 | * LICENSE file in the root directory of this source tree. 38 | */ 39 | 40 | /** 41 | * @license React 42 | * react-jsx-runtime.production.min.js 43 | * 44 | * Copyright (c) Facebook, Inc. and its affiliates. 45 | * 46 | * This source code is licensed under the MIT license found in the 47 | * LICENSE file in the root directory of this source tree. 48 | */ 49 | 50 | /** 51 | * @license React 52 | * react.production.min.js 53 | * 54 | * Copyright (c) Facebook, Inc. and its affiliates. 55 | * 56 | * This source code is licensed under the MIT license found in the 57 | * LICENSE file in the root directory of this source tree. 58 | */ 59 | 60 | /** 61 | * @license React 62 | * scheduler.production.min.js 63 | * 64 | * Copyright (c) Facebook, Inc. and its affiliates. 65 | * 66 | * This source code is licensed under the MIT license found in the 67 | * LICENSE file in the root directory of this source tree. 68 | */ 69 | 70 | //! stable.js 0.1.8, https://github.com/Two-Screen/stable 71 | 72 | //! © 2018 Angry Bytes and contributors. MIT licensed. 73 | -------------------------------------------------------------------------------- /docs/vendors~main.e65b8203.iframe.bundle.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"vendors~main.e65b8203.iframe.bundle.js","sources":[],"mappings":";A","sourceRoot":""} -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | testPathIgnorePatterns: ['deno_dist'] 5 | }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-snap-carousel", 3 | "version": "0.5.1", 4 | "description": "DOM-first, headless carousel for React, utilizing native browser scrolling & CSS scroll snap points", 5 | "author": { 6 | "name": "Richard Scarrott", 7 | "url": "https://richardscarrott.com" 8 | }, 9 | "sideEffects": false, 10 | "main": "./dist/use-snap-carousel.js", 11 | "exports": { 12 | ".": { 13 | "types": "./dist/use-snap-carousel.d.ts", 14 | "import": "./dist/use-snap-carousel.mjs", 15 | "require": "./dist/use-snap-carousel.js" 16 | } 17 | }, 18 | "homepage": "https://github.com/richardscarrott/react-snap-carousel#readme", 19 | "repository": "https://github.com/richardscarrott/react-snap-carousel", 20 | "keywords": [ 21 | "react", 22 | "carousel", 23 | "responsive", 24 | "headless", 25 | "snap", 26 | "scroll", 27 | "scrolling", 28 | "scroller", 29 | "swipe", 30 | "swiper", 31 | "slide", 32 | "slider", 33 | "slideshow", 34 | "gallery" 35 | ], 36 | "peerDependencies": { 37 | "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" 38 | }, 39 | "devDependencies": { 40 | "@babel/core": "^7.20.5", 41 | "@riscarrott/storybook-source-link": "^2.0.5", 42 | "@storybook/addon-actions": "^6.5.14", 43 | "@storybook/addon-essentials": "^6.5.14", 44 | "@storybook/addon-interactions": "^6.5.14", 45 | "@storybook/addon-links": "^6.5.14", 46 | "@storybook/builder-webpack4": "^6.5.14", 47 | "@storybook/manager-webpack4": "^6.5.14", 48 | "@storybook/react": "^6.5.14", 49 | "@storybook/testing-library": "^0.0.13", 50 | "@testing-library/jest-dom": "^5.16.5", 51 | "@testing-library/react": "^13.4.0", 52 | "@types/jest": "^29.2.2", 53 | "@types/react": "^18.0.25", 54 | "babel-loader": "^8.3.0", 55 | "classnames": "^2.3.2", 56 | "esbuild": "^0.15.7", 57 | "eslint": "^8.40.0", 58 | "eslint-config-react-app": "^7.0.1", 59 | "jest": "^29.0.3", 60 | "jest-environment-jsdom": "^29.3.1", 61 | "prettier": "^2.7.1", 62 | "react": "^18.2.0", 63 | "react-dom": "^18.2.0", 64 | "storybook-css-modules": "^1.0.8", 65 | "ts-jest": "^29.0.3", 66 | "tsup": "^6.2.3", 67 | "typescript": "^4.8.3" 68 | }, 69 | "scripts": { 70 | "build": "tsup src/use-snap-carousel.tsx --format esm,cjs --dts --clean", 71 | "test": "jest", 72 | "lint": "eslint src/** && prettier --check src/**", 73 | "prepublishOnly": "npm run lint && npm run test && npm run build", 74 | "storybook": "start-storybook -p 6006", 75 | "build-storybook": "build-storybook -o docs" 76 | }, 77 | "files": [ 78 | "dist" 79 | ] 80 | } 81 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: true, 3 | trailingComma: 'none' 4 | }; 5 | -------------------------------------------------------------------------------- /react-snap-carousel.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardscarrott/react-snap-carousel/d6decb451a15693daab28f119f3ecc1d88e2da40/react-snap-carousel.gif -------------------------------------------------------------------------------- /src/use-isomorphic-layout-effect.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useLayoutEffect } from 'react'; 2 | 3 | export const useIsomorphicLayoutEffect = 4 | typeof document !== 'undefined' ? useLayoutEffect : useEffect; 5 | -------------------------------------------------------------------------------- /src/use-snap-carousel.test.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | */ 4 | 5 | test('TODO', () => { 6 | expect(true).toBe(true); 7 | }); 8 | -------------------------------------------------------------------------------- /src/use-snap-carousel.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useCallback, useEffect, useMemo } from 'react'; 2 | import { useIsomorphicLayoutEffect } from './use-isomorphic-layout-effect'; 3 | 4 | interface SnapCarouselGoToOptions { 5 | /** 6 | * The scroll behavior of the navigation 7 | * when prompted to navigate to another page. 8 | * 9 | * @default 'smooth' 10 | */ 11 | readonly behavior?: ScrollBehavior; 12 | } 13 | 14 | export interface SnapCarouselResult { 15 | readonly pages: number[][]; 16 | readonly activePageIndex: number; 17 | readonly snapPointIndexes: Set; 18 | readonly hasPrevPage: boolean; 19 | readonly hasNextPage: boolean; 20 | readonly prev: (opts?: SnapCarouselGoToOptions) => void; 21 | readonly next: (opts?: SnapCarouselGoToOptions) => void; 22 | readonly goTo: (pageIndex: number, opts?: SnapCarouselGoToOptions) => void; 23 | readonly refresh: () => void; 24 | readonly scrollRef: (el: HTMLElement | null) => void; 25 | } 26 | 27 | export interface SnapCarouselOptions { 28 | readonly axis?: 'x' | 'y'; 29 | readonly initialPages?: number[][]; 30 | } 31 | 32 | interface SnapCarouselState { 33 | readonly pages: number[][]; 34 | readonly activePageIndex: number; 35 | } 36 | 37 | export const useSnapCarousel = ({ 38 | axis = 'x', 39 | initialPages = [] 40 | }: SnapCarouselOptions = {}): SnapCarouselResult => { 41 | const dimension = axis === 'x' ? 'width' : 'height'; 42 | const scrollDimension = axis === 'x' ? 'scrollWidth' : 'scrollHeight'; 43 | const clientDimension = axis === 'x' ? 'clientWidth' : 'clientHeight'; 44 | const nearSidePos = axis === 'x' ? 'left' : 'top'; 45 | const farSidePos = axis === 'x' ? 'right' : 'bottom'; 46 | const scrollPos = axis === 'x' ? 'scrollLeft' : 'scrollTop'; 47 | 48 | const [scrollEl, setScrollEl] = useState(null); 49 | // NOTE: `pages` & `activePageIndex` are modelled as a single state object 50 | // to ensure they don't become out of sync with one another. (i.e. would rather 51 | // not implicitly rely on set state batching) 52 | const [{ pages, activePageIndex }, setCarouselState] = 53 | useState({ 54 | pages: initialPages, 55 | activePageIndex: 0 56 | }); 57 | 58 | const refreshActivePage = useCallback( 59 | (pages: number[][]) => { 60 | if (!scrollEl) { 61 | return; 62 | } 63 | // https://excalidraw.com/#json=1ztbZ26T3ri14SiJBZlt4,Rqa2mjiaYJesnfPYEiBdPQ 64 | const hasScrolledToEnd = 65 | Math.floor(scrollEl[scrollDimension] - scrollEl[scrollPos]) <= 66 | scrollEl[clientDimension]; 67 | if (hasScrolledToEnd) { 68 | // If scrolled to the end, set page to last as it may not end up with an 69 | // offset of 0 due to scroll capping. 70 | // (it's not quite aligned with how snapping works, but good enough for now) 71 | setCarouselState({ pages, activePageIndex: pages.length - 1 }); 72 | return; 73 | } 74 | const items = Array.from(scrollEl.children); 75 | const scrollPort = scrollEl.getBoundingClientRect(); 76 | const offsets = pages.map((page) => { 77 | const leadIndex = page[0]; 78 | const leadEl = items[leadIndex]; 79 | assert(leadEl instanceof HTMLElement, 'Expected HTMLElement'); 80 | const scrollSpacing = getEffectiveScrollSpacing( 81 | scrollEl, 82 | leadEl, 83 | nearSidePos 84 | ); 85 | const rect = leadEl.getBoundingClientRect(); 86 | const offset = 87 | rect[nearSidePos] - scrollPort[nearSidePos] - scrollSpacing; 88 | return Math.abs(offset); 89 | }); 90 | const minOffset = Math.min(...offsets); 91 | const nextActivePageIndex = offsets.indexOf(minOffset); 92 | setCarouselState({ pages, activePageIndex: nextActivePageIndex }); 93 | }, 94 | [scrollEl, clientDimension, nearSidePos, scrollDimension, scrollPos] 95 | ); 96 | 97 | const refresh = useCallback(() => { 98 | if (!scrollEl) { 99 | return; 100 | } 101 | const items = Array.from(scrollEl.children); 102 | const scrollPort = scrollEl.getBoundingClientRect(); 103 | let currPageStartPos: number; 104 | const pages = items.reduce((acc, item, i) => { 105 | assert(item instanceof HTMLElement, 'Expected HTMLElement'); 106 | const currPage = acc[acc.length - 1]; 107 | const rect = getOffsetRect(item, item.parentElement); 108 | if ( 109 | !currPage || 110 | // We allow items to explicitly mark themselves as snap points via the `data-should-snap` 111 | // attribute. This allows callsites to augment and/or define their own "page" logic. 112 | item.dataset.shouldSnap === 'true' || 113 | // Otherwise, we determine pages via the layout. 114 | rect[farSidePos] - currPageStartPos > Math.ceil(scrollPort[dimension]) 115 | ) { 116 | acc.push([i]); 117 | const scrollSpacing = getEffectiveScrollSpacing( 118 | scrollEl, 119 | item, 120 | nearSidePos 121 | ); 122 | currPageStartPos = rect[nearSidePos] - scrollSpacing; 123 | } else { 124 | currPage.push(i); 125 | } 126 | return acc; 127 | }, []); 128 | refreshActivePage(pages); 129 | }, [refreshActivePage, scrollEl, dimension, farSidePos, nearSidePos]); 130 | 131 | useIsomorphicLayoutEffect(() => { 132 | refresh(); 133 | }, [refresh]); 134 | 135 | // On resize we need to refresh the state 136 | useEffect(() => { 137 | const handle = () => { 138 | // TODO: Consider debouncing / throttling 139 | refresh(); 140 | }; 141 | window.addEventListener('resize', handle); 142 | window.addEventListener('orientationchange', handle); 143 | return () => { 144 | window.removeEventListener('resize', handle); 145 | window.removeEventListener('orientationchange', handle); 146 | }; 147 | }, [refresh]); 148 | 149 | // On scroll we only need to refresh the current page as it won't impact `pages`. 150 | useEffect(() => { 151 | if (!scrollEl) { 152 | return; 153 | } 154 | const handle = () => { 155 | // TODO: Consider debouncing / throttling 156 | refreshActivePage(pages); 157 | }; 158 | scrollEl.addEventListener('scroll', handle); 159 | return () => { 160 | scrollEl.removeEventListener('scroll', handle); 161 | }; 162 | }, [refreshActivePage, pages, scrollEl]); 163 | 164 | const handleGoTo = (index: number, opts?: SnapCarouselGoToOptions) => { 165 | if (!scrollEl) { 166 | return; 167 | } 168 | const page = pages[index]; 169 | if (!page) { 170 | return; 171 | } 172 | const items = Array.from(scrollEl.children); 173 | const leadIndex: number | undefined = page[0]; 174 | const leadEl: Element | undefined = items[leadIndex]; 175 | if (!(leadEl instanceof HTMLElement)) { 176 | return; 177 | } 178 | const scrollSpacing = getEffectiveScrollSpacing( 179 | scrollEl, 180 | leadEl, 181 | nearSidePos 182 | ); 183 | 184 | const behavior = opts?.behavior || 'smooth'; 185 | // NOTE: I've tried `leadEl.scrollIntoView` but it often fails in chrome on Mac OS. 186 | scrollEl.scrollTo({ 187 | behavior, 188 | [nearSidePos]: 189 | getOffsetRect(leadEl, leadEl.parentElement)[nearSidePos] - scrollSpacing 190 | }); 191 | }; 192 | 193 | const handlePrev = (opts?: SnapCarouselGoToOptions) => { 194 | handleGoTo(activePageIndex - 1, opts); 195 | }; 196 | 197 | const handleNext = (opts?: SnapCarouselGoToOptions) => { 198 | handleGoTo(activePageIndex + 1, opts); 199 | }; 200 | 201 | const snapPointIndexes = useMemo( 202 | () => new Set(pages.map((page) => page[0])), 203 | [pages] 204 | ); 205 | 206 | const hasPrevPage = activePageIndex > 0; 207 | const hasNextPage = activePageIndex < pages.length - 1; 208 | 209 | return { 210 | hasPrevPage, 211 | hasNextPage, 212 | prev: handlePrev, 213 | next: handleNext, 214 | goTo: handleGoTo, 215 | refresh, 216 | pages, 217 | activePageIndex, 218 | snapPointIndexes, 219 | scrollRef: setScrollEl 220 | }; 221 | }; 222 | 223 | // Like `el.getBoundingClientRect()` but ignores scroll. 224 | // It's similar to `offsetLeft` / `offsetTop`, but offers some of the virtues of `getBoundingClientRect` 225 | // such as factoring in CSS transforms & handling wrapped inline elements. 226 | // https://codepen.io/riscarrott/pen/ZEjyyay 227 | // https://w3c.github.io/csswg-drafts/cssom-view/#dom-htmlelement-offsetleft 228 | // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetLeft 229 | const getOffsetRect = (el: Element, relativeTo?: Element | null) => { 230 | const rect = _getOffsetRect(el); 231 | if (!relativeTo) { 232 | return rect; 233 | } 234 | const relativeRect = _getOffsetRect(relativeTo); 235 | return { 236 | left: rect.left - relativeRect.left, 237 | top: rect.top - relativeRect.top, 238 | right: rect.right - relativeRect.left, 239 | bottom: rect.bottom - relativeRect.top, 240 | width: rect.width, 241 | height: rect.height 242 | }; 243 | }; 244 | 245 | const _getOffsetRect = (el: Element) => { 246 | const rect = el.getBoundingClientRect(); 247 | let scrollLeft = 0; 248 | let scrollTop = 0; 249 | let parentEl = el.parentElement; 250 | while (parentEl) { 251 | scrollLeft += parentEl.scrollLeft; 252 | scrollTop += parentEl.scrollTop; 253 | parentEl = parentEl.parentElement; 254 | } 255 | const left = rect.left + scrollLeft; 256 | const top = rect.top + scrollTop; 257 | return { 258 | left, 259 | top, 260 | right: left + rect.width, 261 | bottom: top + rect.height, 262 | width: rect.width, 263 | height: rect.height 264 | }; 265 | }; 266 | 267 | // `window.getComputedStyle` gives us the *computed* value for scroll-padding-* so we have 268 | // to convert it to the used value (i.e. px value) ourselves :( 269 | // https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-padding 270 | const getScrollPaddingUsedValue = (el: HTMLElement, pos: 'left' | 'top') => { 271 | const style = window.getComputedStyle(el); 272 | const scrollPadding = 273 | style.getPropertyValue(`scroll-padding-${pos}`) || '0px'; 274 | if (scrollPadding === 'auto') { 275 | return 0; 276 | } 277 | // https://developer.mozilla.org/en-US/docs/Web/CSS/length 278 | // https://www.w3.org/TR/css3-values/#length-value 279 | const invalidMsg = `Unsupported scroll padding value, expected or value, received ${scrollPadding}`; 280 | if (scrollPadding.endsWith('px')) { 281 | const value = parseInt(scrollPadding); 282 | assert(!Number.isNaN(value), invalidMsg); 283 | return value; 284 | } 285 | if (scrollPadding.endsWith('%')) { 286 | const value = parseInt(scrollPadding); 287 | assert(!Number.isNaN(value), invalidMsg); 288 | return (el.clientWidth / 100) * value; 289 | } 290 | // e.g. calc(10% + 10px) // NOTE: We could in theory resolve this... 291 | throw new RSCError(invalidMsg); 292 | }; 293 | 294 | // Unlike scroll-padding, scroll-margin doesn't support values, so we should always 295 | // get back a px value, meaning it's effectively already the *used* value. 296 | // https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-margin 297 | const getScrollMarginUsedValue = (el: HTMLElement, pos: 'left' | 'top') => { 298 | const style = window.getComputedStyle(el); 299 | const scrollMargin = style.getPropertyValue(`scroll-margin-${pos}`) || '0px'; 300 | // https://developer.mozilla.org/en-US/docs/Web/CSS/length 301 | // https://www.w3.org/TR/css3-values/#length-value 302 | const invalidMsg = `Unsupported scroll margin value, expected value, received ${scrollMargin}`; 303 | assert(scrollMargin.endsWith('px'), invalidMsg); // Even scroll-margin: 0 should return "0px" 304 | const value = parseInt(scrollMargin); 305 | assert(!Number.isNaN(value), invalidMsg); 306 | return value; 307 | }; 308 | 309 | // The 'effective' scroll spacing is the actual scroll padding + margin that will be used for a 310 | // given item after factoring in whether there is enough scroll width available. 311 | const getEffectiveScrollSpacing = ( 312 | scrollEl: HTMLElement, 313 | itemEl: HTMLElement, 314 | pos: 'left' | 'top' 315 | ) => { 316 | const scrollPadding = getScrollPaddingUsedValue(scrollEl, pos); 317 | const scrollMargin = getScrollMarginUsedValue(itemEl, pos); 318 | const rect = getOffsetRect(itemEl, itemEl.parentElement); 319 | return Math.min(scrollPadding + scrollMargin, rect[pos]); 320 | }; 321 | 322 | function assert(value: any, message: string): asserts value { 323 | if (value) { 324 | return; 325 | } 326 | throw new RSCError(message); 327 | } 328 | 329 | class RSCError extends Error { 330 | constructor(message: string) { 331 | super(`[react-snap-carousel]: ${message}`); 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /stories/carousel.module.css: -------------------------------------------------------------------------------- 1 | .root { 2 | position: relative; 3 | margin: 0 -1rem; /* bust out of storybook margin (to demonstrate full bleed carousel) */ 4 | } 5 | 6 | .y.root { 7 | margin: -1rem 0; 8 | height: 100vh; 9 | width: 300px; 10 | display: flex; 11 | flex-direction: column; 12 | } 13 | 14 | .scroll { 15 | position: relative; 16 | display: flex; 17 | overflow: auto; 18 | scroll-snap-type: x mandatory; 19 | -ms-overflow-style: none; 20 | scrollbar-width: none; 21 | overscroll-behavior: contain; 22 | scroll-padding: 0 16px; 23 | padding: 0 16px; 24 | } 25 | 26 | .scroll::-webkit-scrollbar { 27 | display: none; 28 | } 29 | 30 | .y .scroll { 31 | display: block; 32 | scroll-snap-type: y mandatory; 33 | scroll-padding: 16px 0; 34 | padding: 16px 0; 35 | } 36 | 37 | .item { 38 | font-family: Futura, Trebuchet MS, Arial, sans-serif; 39 | font-size: 125px; 40 | line-height: 1; 41 | width: 300px; 42 | height: 300px; 43 | max-width: 100%; 44 | flex-shrink: 0; 45 | color: white; 46 | display: flex; 47 | justify-content: end; 48 | align-items: end; 49 | padding: 16px 20px; 50 | text-transform: uppercase; 51 | text-shadow: 6px 6px 0px rgba(0, 0, 0, 0.2); 52 | margin-right: 0.6rem; 53 | overflow: hidden; 54 | } 55 | 56 | .scrollMargin .item:nth-child(9) { 57 | scroll-margin-left: 200px; 58 | background: black !important; 59 | } 60 | 61 | .item:last-child { 62 | margin-right: 0; 63 | } 64 | 65 | .y .item { 66 | margin-right: 0; 67 | margin-bottom: 0.6rem; 68 | } 69 | 70 | .y .item:last-child { 71 | margin-bottom: 0; 72 | } 73 | 74 | .pageIndicator { 75 | font-family: Futura, Trebuchet MS, Arial, sans-serif; 76 | font-weight: bold; 77 | font-size: 14px; 78 | position: absolute; 79 | top: 10px; 80 | right: 10px; 81 | padding: 10px 12px; 82 | background: rgba(255, 255, 255, 0.5); 83 | pointer-events: none; 84 | border-radius: 5px; 85 | color: #374151; 86 | } 87 | 88 | .controls { 89 | margin: 1rem 0; 90 | display: flex; 91 | justify-content: center; 92 | align-items: center; 93 | color: #374151; 94 | padding: 0 1rem; 95 | } 96 | 97 | .prevButton, 98 | .nextButton { 99 | font-size: 18px; 100 | transition: opacity 100ms ease-out; 101 | } 102 | 103 | .prevButton[disabled], 104 | .nextButton[disabled] { 105 | opacity: 0.4; 106 | } 107 | 108 | .pagination { 109 | display: flex; 110 | flex-wrap: wrap; 111 | justify-content: center; 112 | margin: 0 10px; 113 | } 114 | 115 | .paginationItem { 116 | display: flex; 117 | justify-content: center; 118 | } 119 | 120 | .paginationButton { 121 | display: block; 122 | text-indent: -99999px; 123 | overflow: hidden; 124 | background: #374151; 125 | width: 12px; 126 | height: 12px; 127 | border-radius: 50%; 128 | margin: 5px; 129 | transition: opacity 100ms ease-out; 130 | } 131 | 132 | .paginationItemActive .paginationButton { 133 | opacity: 0.3; 134 | } 135 | 136 | @media only screen and (max-width: 480px) { 137 | .item { 138 | width: 280px; 139 | height: 280px; 140 | } 141 | 142 | .pagination { 143 | margin: 0 8px; 144 | } 145 | 146 | .prevButton, 147 | .nextButton { 148 | font-size: 15px; 149 | } 150 | 151 | .paginationButton { 152 | width: 9px; 153 | height: 9px; 154 | margin: 4px; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /stories/carousel.stories.tsx: -------------------------------------------------------------------------------- 1 | import React, { useLayoutEffect, useRef, useState } from 'react'; 2 | import { Carousel, CarouselItem, CarouselRef } from './carousel'; 3 | import { Button } from './lib/button'; 4 | import { Select } from './lib/select'; 5 | 6 | export default { 7 | title: 'Carousel', 8 | component: Carousel 9 | }; 10 | 11 | export const Default = () => { 12 | const items = Array.from({ length: 18 }).map((_, index) => ({ id: index })); 13 | return ( 14 | ( 17 | 22 | {index + 1} 23 | 24 | )} 25 | /> 26 | ); 27 | }; 28 | 29 | export const VariableWidth = () => { 30 | const items = [ 31 | 110, 300, 500, 120, 250, 300, 500, 400, 180, 300, 350, 700, 400, 230, 300 32 | ]; 33 | return ( 34 | ( 37 | 43 | {index + 1} 44 | 45 | )} 46 | /> 47 | ); 48 | }; 49 | 50 | export const VerticalAxis = () => { 51 | const items = Array.from({ length: 18 }).map((_, index) => ({ id: index })); 52 | return ( 53 | ( 57 | 62 | {index + 1} 63 | 64 | )} 65 | /> 66 | ); 67 | }; 68 | 69 | export const ScrollMargin = () => { 70 | const items = Array.from({ length: 18 }).map((_, index) => ({ id: index })); 71 | return ( 72 | ( 75 | 80 | {index + 1} 81 | 82 | )} 83 | scrollMargin 84 | /> 85 | ); 86 | }; 87 | 88 | export const DynamicItems = () => { 89 | const carouselRef = useRef(null); 90 | const [items, setItems] = useState(() => 91 | Array.from({ length: 1 }).map((_, index) => ({ id: index })) 92 | ); 93 | const addItem = () => { 94 | setItems((prev) => [...prev, { id: prev.length }]); 95 | }; 96 | const removeItem = () => { 97 | setItems((prev) => prev.slice(0, -1)); 98 | }; 99 | useLayoutEffect(() => { 100 | if (!carouselRef.current) { 101 | return; 102 | } 103 | carouselRef.current.refresh(); 104 | }, [items]); 105 | return ( 106 | <> 107 |
    108 | 109 | 110 |
    111 | ( 115 | 120 | {index + 1} 121 | 122 | )} 123 | /> 124 | 125 | ); 126 | }; 127 | 128 | export const ScrollBehavior = () => { 129 | const scrollBehaviors: ScrollBehavior[] = ['smooth', 'instant', 'auto']; 130 | const [scrollBehavior, setScrollBehavior] = useState(scrollBehaviors[0]); 131 | const items = Array.from({ length: 18 }).map((_, index) => ({ id: index })); 132 | return ( 133 | <> 134 |
    135 | 147 |
    148 | ( 152 | 157 | {index + 1} 158 | 159 | )} 160 | /> 161 | 162 | ); 163 | }; 164 | 165 | /* Utils */ 166 | 167 | const getColor = (i: number) => { 168 | return `hsl(${i * 12} 100% 50%)`; 169 | }; 170 | -------------------------------------------------------------------------------- /stories/carousel.tsx: -------------------------------------------------------------------------------- 1 | import React, { useImperativeHandle } from 'react'; 2 | import classNames from 'classnames'; 3 | import { useSnapCarousel } from '../src/use-snap-carousel'; 4 | import './reset.css'; 5 | const styles = require('./carousel.module.css'); 6 | 7 | /** 8 | * This is an example Carousel built on top of `useSnapCarousel` 9 | */ 10 | 11 | export interface CarouselProps { 12 | readonly axis?: 'x' | 'y'; 13 | readonly items: T[]; 14 | readonly renderItem: ( 15 | props: CarouselRenderItemProps 16 | ) => React.ReactElement; 17 | readonly scrollMargin?: boolean; 18 | readonly scrollBehavior?: ScrollBehavior; 19 | } 20 | 21 | export interface CarouselRenderItemProps { 22 | readonly item: T; 23 | readonly index: number; 24 | readonly isSnapPoint: boolean; 25 | } 26 | 27 | export interface CarouselRef { 28 | readonly refresh: () => void; 29 | } 30 | 31 | export const Carousel = React.forwardRef>( 32 | ({ axis, items, renderItem, scrollMargin = false, scrollBehavior }, ref) => { 33 | const { 34 | scrollRef, 35 | hasNextPage, 36 | hasPrevPage, 37 | next, 38 | prev, 39 | goTo, 40 | pages, 41 | activePageIndex, 42 | snapPointIndexes, 43 | refresh 44 | } = useSnapCarousel({ axis }); 45 | 46 | useImperativeHandle(ref, () => ({ refresh })); 47 | 48 | return ( 49 |
    56 |
      57 | {items.map((item, index) => 58 | renderItem({ 59 | item, 60 | index, 61 | isSnapPoint: snapPointIndexes.has(index) 62 | }) 63 | )} 64 |
    65 |
    66 | {activePageIndex + 1} / {pages.length} 67 |
    68 |
    69 | 76 |
      77 | {pages.map((_, i) => ( 78 |
    1. 84 | 90 |
    2. 91 | ))} 92 |
    93 | 100 |
    101 |
    102 | ); 103 | } 104 | // https://fettblog.eu/typescript-react-generic-forward-refs/ 105 | ) as ( 106 | props: CarouselProps & { ref?: React.ForwardedRef } 107 | ) => React.ReactElement; 108 | 109 | export interface CarouselItemProps { 110 | readonly isSnapPoint: boolean; 111 | readonly bgColor: string; 112 | readonly width?: number; 113 | readonly children?: React.ReactNode; 114 | } 115 | 116 | export const CarouselItem = ({ 117 | isSnapPoint, 118 | bgColor, 119 | width, 120 | children 121 | }: CarouselItemProps) => { 122 | return ( 123 |
  • 131 | {children} 132 |
  • 133 | ); 134 | }; 135 | -------------------------------------------------------------------------------- /stories/infinite-carousel.module.css: -------------------------------------------------------------------------------- 1 | .root { 2 | position: relative; 3 | margin: 0 -1rem; /* bust out of storybook margin (to demonstrate full bleed carousel) */ 4 | } 5 | 6 | .y.root { 7 | margin: -1rem 0; 8 | height: 100vh; 9 | width: 300px; 10 | display: flex; 11 | flex-direction: column; 12 | } 13 | 14 | .scroll { 15 | position: relative; 16 | display: flex; 17 | overflow: auto; 18 | scroll-snap-type: x mandatory; 19 | -ms-overflow-style: none; 20 | scrollbar-width: none; 21 | overscroll-behavior: contain; 22 | scroll-padding: 0 16px; 23 | padding: 0 16px; 24 | } 25 | 26 | .scroll::-webkit-scrollbar { 27 | display: none; 28 | } 29 | 30 | .y .scroll { 31 | display: block; 32 | scroll-snap-type: y mandatory; 33 | scroll-padding: 16px 0; 34 | padding: 16px 0; 35 | } 36 | 37 | .item { 38 | font-family: Futura, Trebuchet MS, Arial, sans-serif; 39 | font-size: 125px; 40 | line-height: 1; 41 | width: 300px; 42 | height: 300px; 43 | max-width: 100%; 44 | flex-shrink: 0; 45 | color: white; 46 | display: flex; 47 | justify-content: end; 48 | align-items: end; 49 | padding: 16px 20px; 50 | text-transform: uppercase; 51 | text-shadow: 6px 6px 0px rgba(0, 0, 0, 0.2); 52 | margin-right: 0.6rem; 53 | overflow: hidden; 54 | } 55 | 56 | .scrollMargin .item:nth-child(9) { 57 | scroll-margin-left: 200px; 58 | background: black !important; 59 | } 60 | 61 | .item:last-child { 62 | margin-right: 0; 63 | } 64 | 65 | .y .item { 66 | margin-right: 0; 67 | margin-bottom: 0.6rem; 68 | } 69 | 70 | .y .item:last-child { 71 | margin-bottom: 0; 72 | } 73 | 74 | .pageIndicator { 75 | font-family: Futura, Trebuchet MS, Arial, sans-serif; 76 | font-weight: bold; 77 | font-size: 14px; 78 | position: absolute; 79 | top: 10px; 80 | right: 10px; 81 | padding: 10px 12px; 82 | background: rgba(255, 255, 255, 0.5); 83 | pointer-events: none; 84 | border-radius: 5px; 85 | color: #374151; 86 | } 87 | 88 | .controls { 89 | margin: 1rem 0; 90 | display: flex; 91 | justify-content: center; 92 | align-items: center; 93 | color: #374151; 94 | padding: 0 1rem; 95 | } 96 | 97 | .prevButton, 98 | .nextButton { 99 | font-size: 18px; 100 | transition: opacity 100ms ease-out; 101 | } 102 | 103 | .prevButton[disabled], 104 | .nextButton[disabled] { 105 | opacity: 0.4; 106 | } 107 | 108 | .pagination { 109 | display: flex; 110 | flex-wrap: wrap; 111 | justify-content: center; 112 | margin: 0 10px; 113 | } 114 | 115 | .paginationItem { 116 | display: flex; 117 | justify-content: center; 118 | } 119 | 120 | .paginationButton { 121 | display: block; 122 | text-indent: -99999px; 123 | overflow: hidden; 124 | background: #374151; 125 | width: 12px; 126 | height: 12px; 127 | border-radius: 50%; 128 | margin: 5px; 129 | transition: opacity 100ms ease-out; 130 | } 131 | 132 | .paginationItemActive .paginationButton { 133 | opacity: 0.3; 134 | } 135 | 136 | @media only screen and (max-width: 480px) { 137 | .item { 138 | width: 280px; 139 | height: 280px; 140 | } 141 | 142 | .pagination { 143 | margin: 0 8px; 144 | } 145 | 146 | .prevButton, 147 | .nextButton { 148 | font-size: 15px; 149 | } 150 | 151 | .paginationButton { 152 | width: 9px; 153 | height: 9px; 154 | margin: 4px; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /stories/infinite-carousel.stories.tsx: -------------------------------------------------------------------------------- 1 | import React, { useLayoutEffect, useRef, useState } from 'react'; 2 | import { 3 | InfiniteCarousel, 4 | InfiniteCarouselItem, 5 | InfiniteCarouselRef 6 | } from './infinite-carousel'; 7 | import { Button } from './lib/button'; 8 | import { Select } from './lib/select'; 9 | 10 | export default { 11 | title: 'Infinite Carousel', 12 | component: InfiniteCarousel 13 | }; 14 | 15 | export const Default = () => { 16 | const items = Array.from({ length: 18 }).map((_, index) => ({ 17 | id: index, 18 | index 19 | })); 20 | return ( 21 | ( 24 | 30 | {item.index + 1} 31 | 32 | )} 33 | /> 34 | ); 35 | }; 36 | 37 | export const VariableWidth = () => { 38 | const items = [ 39 | 110, 300, 500, 120, 250, 300, 500, 400, 180, 300, 350, 700, 400, 230, 300 40 | ].map((width, index) => ({ id: index, index, width })); 41 | return ( 42 | ( 45 | 52 | {item.index + 1} 53 | 54 | )} 55 | /> 56 | ); 57 | }; 58 | 59 | export const VerticalAxis = () => { 60 | const items = Array.from({ length: 18 }).map((_, index) => ({ 61 | id: index, 62 | index 63 | })); 64 | return ( 65 | ( 69 | 75 | {item.index + 1} 76 | 77 | )} 78 | /> 79 | ); 80 | }; 81 | 82 | export const DynamicItems = () => { 83 | const carouselRef = useRef(null); 84 | const [items, setItems] = useState(() => 85 | Array.from({ length: 6 }).map((_, index) => ({ id: index, index })) 86 | ); 87 | const addItem = () => { 88 | setItems((prev) => [...prev, { id: prev.length, index: prev.length }]); 89 | }; 90 | const removeItem = () => { 91 | setItems((prev) => prev.slice(0, -1)); 92 | }; 93 | useLayoutEffect(() => { 94 | if (!carouselRef.current) { 95 | return; 96 | } 97 | carouselRef.current.refresh(); 98 | }, [items]); 99 | return ( 100 | <> 101 |
    102 | 103 | 104 |
    105 | ( 109 | 115 | {item.index + 1} 116 | 117 | )} 118 | /> 119 | 120 | ); 121 | }; 122 | 123 | export const ScrollBehavior = () => { 124 | const scrollBehaviors: ScrollBehavior[] = ['smooth', 'instant', 'auto']; 125 | const [scrollBehavior, setScrollBehavior] = useState(scrollBehaviors[0]); 126 | const items = Array.from({ length: 18 }).map((_, index) => ({ 127 | id: index, 128 | index 129 | })); 130 | return ( 131 | <> 132 |
    133 | 145 |
    146 | ( 150 | 156 | {item.index + 1} 157 | 158 | )} 159 | /> 160 | 161 | ); 162 | }; 163 | 164 | /* Utils */ 165 | 166 | const getColor = (i: number) => { 167 | return `hsl(-${i * 12} 100% 50%)`; 168 | }; 169 | -------------------------------------------------------------------------------- /stories/infinite-carousel.tsx: -------------------------------------------------------------------------------- 1 | import React, { useImperativeHandle, useMemo, useLayoutEffect } from 'react'; 2 | import classNames from 'classnames'; 3 | import { useSnapCarousel } from '../src/use-snap-carousel'; 4 | import './reset.css'; 5 | const styles = require('./infinite-carousel.module.css'); 6 | 7 | /** 8 | * This is an example of an infinite carousel built on top of `useSnapCarousel` 9 | * 10 | * NOTE: This is not truly infinite, but rather it's as-good-as-infinite because, in order to keep 11 | * complexity low, it merely duplicates items in either direction rather than trying to dynamically 12 | * change the scroll position. 13 | */ 14 | 15 | export interface InfiniteCarouselProps { 16 | readonly axis?: 'x' | 'y'; 17 | readonly items: T[]; 18 | readonly renderItem: ( 19 | props: InfiniteCarouselRenderItemProps 20 | ) => React.ReactElement; 21 | readonly scrollMargin?: boolean; 22 | readonly scrollBehavior?: ScrollBehavior; 23 | } 24 | 25 | export interface InfiniteCarouselRenderItemProps { 26 | readonly item: T; 27 | readonly index: number; 28 | readonly isSnapPoint: boolean; 29 | readonly shouldSnap: boolean; 30 | } 31 | 32 | export interface InfiniteCarouselRef { 33 | readonly refresh: () => void; 34 | } 35 | 36 | export const InfiniteCarousel = React.forwardRef< 37 | InfiniteCarouselRef, 38 | InfiniteCarouselProps 39 | >( 40 | ({ axis, items, renderItem, scrollMargin = false, scrollBehavior }, ref) => { 41 | const { 42 | scrollRef, 43 | hasNextPage, 44 | hasPrevPage, 45 | next, 46 | prev, 47 | goTo, 48 | pages, 49 | activePageIndex, 50 | snapPointIndexes, 51 | refresh 52 | } = useSnapCarousel({ axis }); 53 | 54 | useImperativeHandle(ref, () => ({ refresh })); 55 | 56 | // 1. Duplicate the items to create the illusion of infinity. 57 | const duplicationFactor = items.length ? Math.ceil(250 / items.length) : 0; 58 | const itemsToRender = useMemo( 59 | () => Array.from({ length: duplicationFactor }).flatMap(() => items), 60 | [duplicationFactor, items] 61 | ); 62 | 63 | // 2. Jump to the middle-most "first" page to minimize the chance of scrolling to either end. 64 | useLayoutEffect(() => { 65 | const itemsByPage = pages.map((page) => 66 | page.map((idx) => itemsToRender[idx]) 67 | ); 68 | const allFirstPages = itemsByPage.filter( 69 | (page) => page[0] === itemsToRender[0] 70 | ); 71 | const mid = allFirstPages[Math.floor(allFirstPages.length / 2)]; 72 | goTo(itemsByPage.indexOf(mid), { 73 | behavior: 'instant' 74 | }); 75 | // Need `useEffectEvent` 76 | // eslint-disable-next-line react-hooks/exhaustive-deps 77 | }, [pages, itemsToRender]); 78 | 79 | // 3. Define the logical pages and the active page index to render based on the actual unique `items`. 80 | const pagesToRender = pages.filter((page) => page[0] <= items.length - 1); 81 | const activePageIndexToRender = pagesToRender.length 82 | ? activePageIndex % pagesToRender.length 83 | : -1; 84 | 85 | return ( 86 |
    93 |
      94 | {itemsToRender.map((item, index) => 95 | renderItem({ 96 | item, 97 | index, 98 | isSnapPoint: snapPointIndexes.has(index), 99 | // 4. Force snapping to the first item so that pages are always made up of the equivalent 100 | // items, even if the number of items isn't wholly divisible by the number of pages. 101 | // -- this simplifies the logic for rendering the controls. 102 | shouldSnap: item === itemsToRender[0] 103 | }) 104 | )} 105 |
    106 |
    107 | {activePageIndexToRender + 1} / {pagesToRender.length} 108 |
    109 |
    110 | 117 |
      118 | {pagesToRender.map((_, i) => ( 119 |
    1. 125 | 136 |
    2. 137 | ))} 138 |
    139 | 146 |
    147 |
    148 | ); 149 | } 150 | // https://fettblog.eu/typescript-react-generic-forward-refs/ 151 | ) as ( 152 | props: InfiniteCarouselProps & { 153 | ref?: React.ForwardedRef; 154 | } 155 | ) => React.ReactElement; 156 | 157 | export interface InfiniteCarouselItemProps { 158 | readonly isSnapPoint: boolean; 159 | readonly shouldSnap: boolean; 160 | readonly bgColor: string; 161 | readonly width?: number; 162 | readonly children?: React.ReactNode; 163 | } 164 | 165 | export const InfiniteCarouselItem = ({ 166 | isSnapPoint, 167 | shouldSnap, 168 | bgColor, 169 | width, 170 | children 171 | }: InfiniteCarouselItemProps) => { 172 | return ( 173 |
  • 182 | {children} 183 |
  • 184 | ); 185 | }; 186 | -------------------------------------------------------------------------------- /stories/lib/button.module.css: -------------------------------------------------------------------------------- 1 | .root { 2 | background: #04bfad; 3 | text-transform: uppercase; 4 | font-weight: bold; 5 | color: #fff; 6 | padding: 10px; 7 | font-size: 16px; 8 | border-radius: 5px; 9 | display: inline-block; 10 | } 11 | 12 | .root:hover, 13 | .root:focus-visible { 14 | opacity: 0.7; 15 | } 16 | -------------------------------------------------------------------------------- /stories/lib/button.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import React from 'react'; 3 | const styles = require('./button.module.css'); 4 | 5 | interface Props { 6 | readonly children?: React.ReactNode; 7 | readonly onClick?: () => void; 8 | readonly className?: string; 9 | } 10 | 11 | export const Button = ({ children, onClick, className }: Props) => { 12 | return ( 13 | 16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /stories/lib/select.module.css: -------------------------------------------------------------------------------- 1 | .root { 2 | position: relative; 3 | display: inline-block; 4 | } 5 | 6 | .root::before, 7 | .root::after { 8 | --size: 0.3rem; 9 | position: absolute; 10 | content: ''; 11 | right: 1rem; 12 | pointer-events: none; 13 | z-index: 1; 14 | } 15 | 16 | .root::before { 17 | border-left: var(--size) solid transparent; 18 | border-right: var(--size) solid transparent; 19 | border-bottom: var(--size) solid #fff; 20 | top: 40%; 21 | } 22 | 23 | .root::after { 24 | border-left: var(--size) solid transparent; 25 | border-right: var(--size) solid transparent; 26 | border-top: var(--size) solid #fff; 27 | top: 55%; 28 | } 29 | 30 | .select { 31 | appearance: none; 32 | background: #04bfad; 33 | text-transform: uppercase; 34 | font-weight: bold; 35 | color: #fff; 36 | padding: 10px; 37 | padding-right: 40px; 38 | font-size: 16px; 39 | border-radius: 5px; 40 | display: inline-block; 41 | position: relative; 42 | } 43 | 44 | .select:hover, 45 | .select:focus-visible { 46 | opacity: 0.7; 47 | } 48 | -------------------------------------------------------------------------------- /stories/lib/select.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import React from 'react'; 3 | const styles = require('./select.module.css'); 4 | 5 | interface Props { 6 | readonly children?: React.ReactNode; 7 | readonly value?: string; 8 | readonly onChange?: (event: React.ChangeEvent) => void; 9 | readonly className?: string; 10 | } 11 | 12 | export const Select = ({ children, onChange, value, className }: Props) => { 13 | return ( 14 |
    15 | 18 |
    19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /stories/reset.css: -------------------------------------------------------------------------------- 1 | @import url('https://unpkg.com/modern-normalize@1.1.0/modern-normalize.css'); 2 | @import url('https://unpkg.com/tailwindcss@3.2.4/src/css/preflight.css'); 3 | -------------------------------------------------------------------------------- /stories/slideshow.module.css: -------------------------------------------------------------------------------- 1 | .root { 2 | position: relative; 3 | } 4 | 5 | .scrollPadding { 6 | margin: 0 -1rem; /* bust out of storybook margin (to demonstrate full bleed carousel) */ 7 | } 8 | 9 | .scroll { 10 | position: relative; 11 | display: flex; 12 | overflow: auto; 13 | scroll-snap-type: x mandatory; 14 | -ms-overflow-style: none; 15 | scrollbar-width: none; 16 | box-sizing: border-box; 17 | overscroll-behavior: contain; 18 | } 19 | 20 | .scrollPadding .scroll { 21 | scroll-padding: 0 5%; 22 | padding: 0 5%; 23 | } 24 | 25 | .scroll::-webkit-scrollbar { 26 | display: none; 27 | } 28 | 29 | .item { 30 | width: 100%; 31 | flex-shrink: 0; 32 | position: relative; 33 | padding-bottom: 56.25%; 34 | overflow: hidden; 35 | /* margin: 0 1%; */ 36 | } 37 | 38 | /* .item:first-child, 39 | .item:last-child { 40 | margin: 0; 41 | } */ 42 | 43 | .snapPoint { 44 | scroll-snap-align: start; 45 | } 46 | 47 | .itemText { 48 | position: absolute; 49 | z-index: 100; 50 | top: 0; 51 | left: 0; 52 | width: 100%; 53 | top: 50%; 54 | left: 50%; 55 | transform: translate(-50%, -20%); 56 | color: white; 57 | font-family: Futura, Trebuchet MS, Arial, sans-serif; 58 | text-align: center; 59 | opacity: 0; 60 | transition: all 600ms cubic-bezier(0.25, 1, 0.5, 1); 61 | } 62 | 63 | .itemActive .itemText { 64 | transform: translate(-50%, -65%); 65 | opacity: 1; 66 | } 67 | 68 | .itemTitle { 69 | text-transform: uppercase; 70 | font-size: 8.5vw; 71 | text-shadow: 0.35vw 0.35vw 0vw rgba(0, 0, 0, 0.4); 72 | } 73 | 74 | .itemSubtitle { 75 | text-transform: uppercase; 76 | font-size: 2.5vw; 77 | text-shadow: 0.2vw 0.2vw 0vw rgba(0, 0, 0, 0.4); 78 | opacity: 0; 79 | transition: opacity 500ms cubic-bezier(0.25, 1, 0.5, 1); 80 | transition-delay: 350ms; 81 | } 82 | 83 | .itemActive .itemSubtitle { 84 | opacity: 1; 85 | } 86 | 87 | .itemImage { 88 | position: absolute; 89 | top: 0; 90 | left: 0; 91 | width: 100%; 92 | height: 100%; 93 | } 94 | 95 | .pageIndicator { 96 | font-family: Futura, Trebuchet MS, Arial, sans-serif; 97 | font-weight: bold; 98 | font-size: 14px; 99 | position: absolute; 100 | top: 10px; 101 | right: 10px; 102 | padding: 10px 12px; 103 | background: rgba(255, 255, 255, 0.5); 104 | pointer-events: none; 105 | border-radius: 5px; 106 | color: #374151; 107 | } 108 | 109 | .controls { 110 | margin: 1rem 0; 111 | display: flex; 112 | justify-content: center; 113 | align-items: center; 114 | color: #374151; 115 | padding: 0 1rem; 116 | } 117 | 118 | .prevButton, 119 | .nextButton { 120 | font-size: 18px; 121 | transition: opacity 100ms ease-out; 122 | } 123 | 124 | .prevButton[disabled], 125 | .nextButton[disabled] { 126 | opacity: 0.4; 127 | } 128 | 129 | .pagination { 130 | display: flex; 131 | flex-wrap: wrap; 132 | justify-content: center; 133 | margin: 0 10px; 134 | } 135 | 136 | .paginationItem { 137 | display: flex; 138 | justify-content: center; 139 | } 140 | 141 | .paginationButton { 142 | display: block; 143 | text-indent: -99999px; 144 | overflow: hidden; 145 | background: #374151; 146 | width: 12px; 147 | height: 12px; 148 | border-radius: 50%; 149 | margin: 5px; 150 | transition: opacity 100ms ease-out; 151 | } 152 | 153 | .paginationItemActive .paginationButton { 154 | opacity: 0.3; 155 | } 156 | 157 | @media only screen and (max-width: 480px) { 158 | .pagination { 159 | margin: 0 8px; 160 | } 161 | 162 | .prevButton, 163 | .nextButton { 164 | font-size: 15px; 165 | } 166 | 167 | .paginationButton { 168 | width: 9px; 169 | height: 9px; 170 | margin: 4px; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /stories/slideshow.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SlideShow, SlideShowItem } from './slideshow'; 3 | 4 | export default { 5 | title: 'SlideShow', 6 | component: SlideShow 7 | }; 8 | 9 | const items = [ 10 | { title: 'It’s giving', subtitle: '52.073273, -1.014818' }, 11 | { title: 'Era', subtitle: '47.220244, 14.764848' }, 12 | { title: 'Iykyk', subtitle: '50.435664924, 5.969496122' }, 13 | { title: 'Slay', subtitle: '45.618997524, 9.286998852' }, 14 | { title: 'Fell off', subtitle: '34.83916331, 136.537664516' }, 15 | { title: 'Gatekeep', subtitle: '30.133883, -97.641144' }, 16 | { title: 'Situationship', subtitle: '19.4025167233, -99.0865813203' }, 17 | { title: 'Bad take', subtitle: '-23.702163858, -46.693163894' }, 18 | { title: 'Private not secret', subtitle: '45.503497986, -73.522664576' }, 19 | { title: 'Touch grass', subtitle: '1.2881738473, 103.858484899' }, 20 | { title: 'Rizz', subtitle: '47.573997704, 19.24249903' } 21 | ].map(({ title, subtitle }, i) => ({ 22 | src: `https://picsum.photos/1280/720?idx=${i}`, 23 | title, 24 | subtitle 25 | })); 26 | 27 | export const Default = () => { 28 | return ( 29 | ( 32 | 40 | )} 41 | /> 42 | ); 43 | }; 44 | 45 | export const ScrollPadding = () => { 46 | return ( 47 | ( 51 | 59 | )} 60 | /> 61 | ); 62 | }; 63 | -------------------------------------------------------------------------------- /stories/slideshow.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import classNames from 'classnames'; 3 | import { useSnapCarousel } from '../src/use-snap-carousel'; 4 | import './reset.css'; 5 | const styles = require('./slideshow.module.css'); 6 | 7 | /** 8 | * This is an example Carousel built on top of `useSnapCarousel` 9 | */ 10 | 11 | export interface SlideShowProps { 12 | readonly items: T[]; 13 | readonly renderItem: ( 14 | props: SlideShowRenderItemProps 15 | ) => React.ReactElement; 16 | readonly scrollPadding?: boolean; 17 | } 18 | 19 | export interface SlideShowRenderItemProps { 20 | readonly item: T; 21 | readonly index: number; 22 | readonly isSnapPoint: boolean; 23 | readonly isActive: boolean; 24 | } 25 | 26 | export const SlideShow = ({ 27 | items, 28 | renderItem, 29 | scrollPadding = false 30 | }: SlideShowProps) => { 31 | const { 32 | scrollRef, 33 | hasPrevPage, 34 | hasNextPage, 35 | next, 36 | prev, 37 | goTo, 38 | pages, 39 | activePageIndex, 40 | snapPointIndexes, 41 | refresh 42 | } = useSnapCarousel(); 43 | 44 | useEffect(() => { 45 | const handle = (e: KeyboardEvent) => { 46 | switch (e.key) { 47 | case 'ArrowLeft': 48 | next(); 49 | return; 50 | case 'ArrowRight': 51 | prev(); 52 | return; 53 | default: 54 | return; 55 | } 56 | }; 57 | window.addEventListener('keypress', handle); 58 | return () => { 59 | window.removeEventListener('keypress', handle); 60 | }; 61 | }, [next, prev]); 62 | 63 | return ( 64 |
    69 |
      70 | {items.map((item, index) => 71 | renderItem({ 72 | item, 73 | index, 74 | isSnapPoint: snapPointIndexes.has(index), 75 | isActive: activePageIndex === index 76 | }) 77 | )} 78 |
    79 |
    80 | {activePageIndex + 1} / {pages.length} 81 |
    82 |
    83 | 90 |
      91 | {pages.map((_, i) => ( 92 |
    1. 98 | 104 |
    2. 105 | ))} 106 |
    107 | 114 |
    115 |
    116 | ); 117 | }; 118 | 119 | export interface SlideShowItemProps { 120 | readonly isSnapPoint: boolean; 121 | readonly isActive: boolean; 122 | readonly src: string; 123 | readonly title: string; 124 | readonly subtitle: string; 125 | } 126 | 127 | export const SlideShowItem = ({ 128 | isSnapPoint, 129 | isActive, 130 | src, 131 | title, 132 | subtitle 133 | }: SlideShowItemProps) => { 134 | return ( 135 |
  • 141 |
    142 |

    {title}

    143 |

    {subtitle}

    144 |
    145 | 146 |
  • 147 | ); 148 | }; 149 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2019", 4 | "module": "ES2020", 5 | "moduleResolution": "node", 6 | "strict": true, 7 | "esModuleInterop": true, 8 | "declaration": true, 9 | "declarationDir": "dist", 10 | "outDir": "dist", 11 | "jsx": "react" 12 | }, 13 | "exclude": [ 14 | "src/**/*.test.ts", 15 | "src/**/*.test-d.ts", 16 | "dist", 17 | "examples/**" 18 | ] 19 | } 20 | --------------------------------------------------------------------------------