├── .all-contributorsrc ├── .eslintrc ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .npmrc ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── jest.config.js ├── other ├── CODE_OF_CONDUCT.md ├── MAINTAINING.md ├── TYPESCRIPT_USAGE.md ├── USERS.md ├── jest.config.js ├── logo │ └── react-toggled.png ├── manual-releases.md ├── misc-tests │ ├── __tests__ │ │ ├── build.js │ │ └── preact.js │ └── jest.config.js ├── raf-polyfill.js └── setup-tests.js ├── package.json ├── src ├── __tests__ │ ├── .eslintrc │ ├── __snapshots__ │ │ └── index.js.snap │ └── index.js └── index.js ├── stories ├── .babelrc ├── .eslintrc ├── addons.js ├── config.js ├── examples │ ├── basic.js │ ├── checkbox.js │ └── switch.js └── package.json ├── test └── basic.test.tsx ├── tsconfig.json └── typings └── index.d.ts /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "react-toggled", 3 | "projectOwner": "kentcdodds", 4 | "files": [ 5 | "README.md" 6 | ], 7 | "imageSize": 100, 8 | "commit": false, 9 | "contributors": [ 10 | { 11 | "login": "kentcdodds", 12 | "name": "Kent C. Dodds", 13 | "avatar_url": "https://avatars.githubusercontent.com/u/1500684?v=3", 14 | "profile": "https://kentcdodds.com", 15 | "contributions": [ 16 | "code", 17 | "doc", 18 | "infra", 19 | "test" 20 | ] 21 | }, 22 | { 23 | "login": "tansongyang", 24 | "name": "Frank Tan", 25 | "avatar_url": "https://avatars3.githubusercontent.com/u/9488719?v=4", 26 | "profile": "https://github.com/tansongyang", 27 | "contributions": [ 28 | "code", 29 | "doc", 30 | "test" 31 | ] 32 | }, 33 | { 34 | "login": "oliverjam", 35 | "name": "Oliver", 36 | "avatar_url": "https://avatars1.githubusercontent.com/u/9408641?v=4", 37 | "profile": "http://www.oliverjam.es", 38 | "contributions": [ 39 | "code" 40 | ] 41 | }, 42 | { 43 | "login": "TheFullResolution", 44 | "name": "Jedrzej Lewandowski", 45 | "avatar_url": "https://avatars2.githubusercontent.com/u/11708648?v=4", 46 | "profile": "http://www.thefullresolution.com/", 47 | "contributions": [ 48 | "code" 49 | ] 50 | }, 51 | { 52 | "login": "bslinger", 53 | "name": "Ben Slinger", 54 | "avatar_url": "https://avatars1.githubusercontent.com/u/603386?v=4", 55 | "profile": "https://github.com/bslinger", 56 | "contributions": [ 57 | "code", 58 | "test" 59 | ] 60 | }, 61 | { 62 | "login": "jdorfman", 63 | "name": "Justin Dorfman", 64 | "avatar_url": "https://avatars1.githubusercontent.com/u/398230?v=4", 65 | "profile": "https://stackshare.io/jdorfman/decisions", 66 | "contributions": [ 67 | "fundingFinding" 68 | ] 69 | } 70 | ], 71 | "repoType": "github", 72 | "repoHost": "https://github.com", 73 | "contributorsPerLine": 7 74 | } 75 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/kcd-scripts/eslint.js", 3 | "rules": { 4 | "react/no-deprecated": 0 5 | } 6 | } 7 | 8 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.js text eol=lf 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 13 | 14 | - `react-toggled` version: 15 | - `node` version: 16 | - `npm` (or `yarn`) version: 17 | 18 | Relevant code or config 19 | 20 | ```javascript 21 | 22 | ``` 23 | 24 | What you did: 25 | 26 | 27 | 28 | What happened: 29 | 30 | 31 | 32 | Reproduction repository: 33 | 34 | 38 | 39 | Problem description: 40 | 41 | 42 | 43 | Suggested solution: 44 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | **What**: 19 | 20 | 21 | **Why**: 22 | 23 | 24 | **How**: 25 | 26 | 27 | **Checklist**: 28 | 29 | 30 | - [ ] Documentation 31 | - [ ] Tests 32 | - [ ] Ready to be merged 33 | - [ ] Added myself to contributors table 34 | 35 | 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | dist 4 | .opt-in 5 | .opt-out 6 | .DS_Store 7 | .next 8 | .eslintcache 9 | storybook-static 10 | preact/ 11 | 12 | # these cause more harm than good 13 | # when working with contributors 14 | package-lock.json 15 | yarn.lock 16 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=http://registry.npmjs.org/ 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | cache: 4 | directories: 5 | - ~/.npm 6 | notifications: 7 | email: false 8 | node_js: '8' 9 | install: npm install 10 | before_script: 11 | - cd stories 12 | - npm install 13 | - cd .. 14 | script: npm run validate 15 | after_success: kcd-scripts travis-after-success 16 | branches: 17 | only: master 18 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | The changelog is automatically updated using [semantic-release](https://github.com/semantic-release/semantic-release). 4 | You can see it on the [releases page](../../releases). 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks for being willing to contribute! 4 | 5 | **Working on your first Pull Request?** You can learn how from this *free* series 6 | [How to Contribute to an Open Source Project on GitHub][egghead] 7 | 8 | ## Project setup 9 | 10 | 1. Fork and clone the repo 11 | 2. `$ npm install` to install dependencies 12 | 3. `$ npm run validate` to validate you've got it working 13 | 4. Create a branch for your PR 14 | 15 | > Tip: Keep your `master` branch pointing at the original repository and make 16 | > pull requests from branches on your fork. To do this, run: 17 | > 18 | > ``` 19 | > git remote add upstream https://github.com/kentcdodds/react-toggled.git 20 | > git fetch upstream 21 | > git branch --set-upstream-to=upstream/master master 22 | > ``` 23 | > 24 | > This will add the original repository as a "remote" called "upstream," 25 | > Then fetch the git information from that remote, then set your local `master` 26 | > branch to use the upstream master branch whenever you run `git pull`. 27 | > Then you can make all of your pull request branches based on this `master` 28 | > branch. Whenever you want to update your version of `master`, do a regular 29 | > `git pull`. 30 | 31 | ## Add yourself as a contributor 32 | 33 | This project follows the [all contributors][all-contributors] specification. 34 | To add yourself to the table of contributors on the `README.md`, please use the 35 | automated script as part of your PR: 36 | 37 | ```console 38 | npm run add-contributor 39 | ``` 40 | 41 | Follow the prompt and commit `.all-contributorsrc` and `README.md` in the PR. 42 | If you've already added yourself to the list and are making 43 | a new type of contribution, you can run it again and select the added 44 | contribution type. 45 | 46 | ## Committing and Pushing changes 47 | 48 | Please make sure to run the tests before you commit your changes. You can run 49 | `npm run test:update` which will update any snapshots that need updating. 50 | Make sure to include those changes (if they exist) in your commit. 51 | 52 | ### opt into git hooks 53 | 54 | There are git hooks set up with this project that are automatically installed 55 | when you install dependencies. They're really handy, but are turned off by 56 | default (so as to not hinder new contributors). You can opt into these by 57 | creating a file called `.opt-in` at the root of the project and putting this 58 | inside: 59 | 60 | ``` 61 | pre-commit 62 | ``` 63 | 64 | ## Help needed 65 | 66 | Please checkout the [the open issues][issues] 67 | 68 | Also, please watch the repo and respond to questions/bug reports/feature 69 | requests! Thanks! 70 | 71 | [egghead]: https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github 72 | [all-contributors]: https://github.com/kentcdodds/all-contributors 73 | [issues]: https://github.com/kentcdodds/react-toggled/issues 74 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2017-2018 Kent C. Dodds 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | react-toggled ⚛️ 3 |
4 | react-toggled logo 5 |
6 |

7 |

Component to build simple, flexible, and accessible toggle components

8 | 9 |
10 | 11 | [![Build Status][build-badge]][build] 12 | [![Code Coverage][coverage-badge]][coverage] 13 | [![downloads][downloads-badge]][npmcharts] 14 | [![version][version-badge]][package] 15 | [![MIT License][license-badge]][license] 16 | 17 | [![All Contributors](https://img.shields.io/badge/all_contributors-6-orange.svg?style=flat-square)](#contributors) 18 | [![PRs Welcome][prs-badge]][prs] 19 | [![Chat][chat-badge]][chat] 20 | [![Code of Conduct][coc-badge]][coc] 21 | 22 | [![Supports React and Preact][react-badge]][react] 23 | [![size][size-badge]][unpkg-dist] 24 | [![gzip size][gzip-badge]][unpkg-dist] 25 | [![module formats: umd, cjs, and es][module-formats-badge]][unpkg-dist] 26 | 27 | [![Watch on GitHub][github-watch-badge]][github-watch] 28 | [![Star on GitHub][github-star-badge]][github-star] 29 | [![Tweet][twitter-badge]][twitter] 30 | 31 | ## The problem 32 | 33 | You want a toggle component that's simple and gives you complete control over 34 | rendering and state. 35 | 36 | ## This solution 37 | 38 | This follows the patterns in [`downshift`][downshift] to expose an API that 39 | renders nothing and simply encapsulates the logic of a toggle component. 40 | 41 | ## Table of Contents 42 | 43 | 44 | 45 | 46 | 47 | * [Installation](#installation) 48 | * [Usage](#usage) 49 | * [Props:](#props) 50 | * [defaultOn](#defaulton) 51 | * [onToggle](#ontoggle) 52 | * [on](#on) 53 | * [children](#children) 54 | * [Examples](#examples) 55 | * [Inspiration](#inspiration) 56 | * [Other Solutions](#other-solutions) 57 | * [Contributors](#contributors) 58 | * [LICENSE](#license) 59 | 60 | 61 | 62 | ## Installation 63 | 64 | This module is distributed via [npm][npm] which is bundled with [node][node] and 65 | should be installed as one of your project's `dependencies`: 66 | 67 | ``` 68 | npm install --save react-toggled 69 | ``` 70 | 71 | > This package also depends on `react` and `prop-types`. Please make sure you 72 | > have those installed as well. 73 | 74 | > Note also this library supports `preact` out of the box. If you are using 75 | > `preact` then look in the `preact/` folder and use the module you want. 76 | > You should be able to simply do: `import Toggle from 'react-toggled/preact'` 77 | 78 | ## Usage 79 | 80 | ```jsx 81 | import React from 'react' 82 | import {render} from 'react-dom' 83 | import Toggle from 'react-toggled' 84 | 85 | render( 86 | 87 | {({on, getTogglerProps}) => ( 88 |
89 | 90 |
{on ? 'Toggled On' : 'Toggled Off'}
91 |
92 | )} 93 |
, 94 | document.getElementById('root'), 95 | ) 96 | ``` 97 | 98 | `react-toggled` is the only component. It doesn't render anything itself, it just 99 | calls the child function and renders that. Wrap everything in 100 | `{/* your function here! */}`. 101 | 102 | ## Props: 103 | 104 | ### defaultOn 105 | 106 | > `boolean` | defaults to `false` 107 | 108 | The initial `on` state. 109 | 110 | ### onToggle 111 | 112 | > `function(on: boolean, object: TogglerStateAndHelpers)` | optional, no useful default 113 | 114 | Called when the toggler is clicked. 115 | 116 | * `on`: The new on state 117 | * `TogglerStateAndHelpers`: the exact same thing you get in your child render 118 | prop function. 119 | 120 | ### on 121 | 122 | > `boolean` | **control prop** 123 | 124 | react-toggled manages its own state internally and calls your `onToggle` 125 | handler whenever the `on` state changes. Your child render prop function 126 | (read more below) can be used to manipulate this state from within the render 127 | function and can likely support many of your use cases. 128 | 129 | However, if more control is needed, you can pass the `on` state as a prop 130 | and that state becomes controlled. As soon as 131 | `this.props.on !== undefined`, internally, `react-toggled` will determine 132 | its state based on your prop's value rather than its own internal state. You 133 | will be required to keep the state up to date (this is where `onToggle` comes in 134 | really handy), but you can also control the state from anywhere, be 135 | that state from other components, `redux`, `react-router`, or anywhere else. 136 | 137 | > Note: This is very similar to how normal controlled components work elsewhere 138 | > in react (like ``). If you want to learn more about this concept, you 139 | > can learn about that from this the 140 | > ["Controlled Components" lecture][controlled-components-lecture] and 141 | > exercises from [React Training's][react-training] > [Advanced React][advanced-react] course. 142 | 143 | ### children 144 | 145 | > `function({})` | _required_ 146 | 147 | This is called with an object. 148 | 149 | This is where you render whatever you want to based on the state of `react-toggled`. 150 | The function is passed as the child prop: 151 | `{/* right here*/}` 152 | 153 | 154 | 155 | | property | category | type | description | 156 | | ------------------------ | ----------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | 157 | | `on` | state | `boolean` | The current `on` state of toggle | 158 | | `getTogglerProps` | prop getter | `function(props: object)` | returns the props you should apply to the button element you render. Includes `aria-` attributes | 159 | | `getInputTogglerProps` | prop getter | `function(props: object)` | returns the props you should apply to the input (checkbox) element you render. Includes `aria-` attributes | 160 | | `getElementTogglerProps` | prop getter | `function(props: object)` | returns the props you should apply to the element you render. Use this if you are not using a button or input—for example, a span. Includes `aria-` attributes | 161 | | `setOn` | action | `function()` | Sets the `on` state to `true` | 162 | | `setOff` | action | `function()` | Sets the `on` state to `false` | 163 | | `toggle` | action | `function()` | Toggles the `on` state (i.e. if it's currently `true`, will set to `false`) | 164 | 165 | ## Examples 166 | 167 | Examples exist on [codesandbox.io][examples]: 168 | 169 | * [Bare bones toggle](https://codesandbox.io/s/m38674w9vy) 170 | 171 | If you would like to add an example, follow these steps: 172 | 173 | 1. Fork [this codesandbox](https://codesandbox.io/s/m38674w9vy) 174 | 2. Make sure your version (under dependencies) is the latest available version. 175 | 3. Update the title and description 176 | 4. Update the code for your example (add some form of documentation to explain what it is) 177 | 5. Add the tag: `react-toggled:example` 178 | 179 | You'll find other examples in the `stories/examples` folder of the repo. 180 | And you'll find 181 | [a live version of those examples here](https://react-toggled.netlify.com) 182 | 183 | ## Inspiration 184 | 185 | I built this while building [an example of using `glamorous` with `next.js`][glamorous-with-next] 186 | 187 | ## Other Solutions 188 | 189 | You can implement any of the other solutions using `react-toggled`, but if 190 | you'd prefer to use these out of the box solutions, then that's fine too. There 191 | are tons of them, so just 192 | [checkout npm](https://www.npmjs.com/search?q=react%20toggle). 193 | 194 | ## Contributors 195 | 196 | Thanks goes to these people ([emoji key][emojis]): 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 |
Kent C. Dodds
Kent C. Dodds

💻 📖 🚇 ⚠️
Frank Tan
Frank Tan

💻 📖 ⚠️
Oliver
Oliver

💻
Jedrzej Lewandowski
Jedrzej Lewandowski

💻
Ben Slinger
Ben Slinger

💻 ⚠️
Justin Dorfman
Justin Dorfman

🔍
210 | 211 | 212 | 213 | This project follows the [all-contributors][all-contributors] specification. 214 | Contributions of any kind welcome! 215 | 216 | ## LICENSE 217 | 218 | MIT 219 | 220 | [npm]: https://www.npmjs.com/ 221 | [node]: https://nodejs.org 222 | [build-badge]: https://img.shields.io/travis/kentcdodds/react-toggled.svg?style=flat-square 223 | [build]: https://travis-ci.org/kentcdodds/react-toggled 224 | [coverage-badge]: https://img.shields.io/codecov/c/github/kentcdodds/react-toggled.svg?style=flat-square 225 | [coverage]: https://codecov.io/github/kentcdodds/react-toggled 226 | [version-badge]: https://img.shields.io/npm/v/react-toggled.svg?style=flat-square 227 | [package]: https://www.npmjs.com/package/react-toggled 228 | [downloads-badge]: https://img.shields.io/npm/dm/react-toggled.svg?style=flat-square 229 | [npmcharts]: http://npmcharts.com/compare/react-toggled 230 | [license-badge]: https://img.shields.io/npm/l/react-toggled.svg?style=flat-square 231 | [license]: https://github.com/kentcdodds/react-toggled/blob/master/LICENSE 232 | [prs-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square 233 | [prs]: http://makeapullrequest.com 234 | [chat]: https://gitter.im/kentcdodds/react-toggled 235 | [chat-badge]: https://img.shields.io/gitter/room/kentcdodds/react-toggled.svg?style=flat-square 236 | [coc-badge]: https://img.shields.io/badge/code%20of-conduct-ff69b4.svg?style=flat-square 237 | [coc]: https://github.com/kentcdodds/react-toggled/blob/master/other/CODE_OF_CONDUCT.md 238 | [react-badge]: https://img.shields.io/badge/%E2%9A%9B%EF%B8%8F-(p)react-00d8ff.svg?style=flat-square 239 | [react]: https://facebook.github.io/react/ 240 | [gzip-badge]: http://img.badgesize.io/https://unpkg.com/react-toggled/dist/react-toggled.umd.min.js?compression=gzip&label=gzip%20size&style=flat-square 241 | [size-badge]: http://img.badgesize.io/https://unpkg.com/react-toggled/dist/react-toggled.umd.min.js?label=size&style=flat-square 242 | [unpkg-dist]: https://unpkg.com/react-toggled/dist/ 243 | [module-formats-badge]: https://img.shields.io/badge/module%20formats-umd%2C%20cjs%2C%20es-green.svg?style=flat-square 244 | [github-watch-badge]: https://img.shields.io/github/watchers/kentcdodds/react-toggled.svg?style=social 245 | [github-watch]: https://github.com/kentcdodds/react-toggled/watchers 246 | [github-star-badge]: https://img.shields.io/github/stars/kentcdodds/react-toggled.svg?style=social 247 | [github-star]: https://github.com/kentcdodds/react-toggled/stargazers 248 | [twitter]: https://twitter.com/intent/tweet?text=Check%20out%20react-toggled%20by%20%40kentcdodds%20https%3A%2F%2Fgithub.com%2Fkentcdodds%2Freact-toggled%20%F0%9F%91%8D 249 | [twitter-badge]: https://img.shields.io/twitter/url/https/github.com/kentcdodds/react-toggled.svg?style=social 250 | [emojis]: https://github.com/kentcdodds/all-contributors#emoji-key 251 | [all-contributors]: https://github.com/kentcdodds/all-contributors 252 | [ryan]: https://github.com/ryanflorence 253 | [compound-components-lecture]: https://courses.reacttraining.com/courses/advanced-react/lectures/3060560 254 | [examples]: https://codesandbox.io/search?refinementList%5Btags%5D%5B0%5D=react-toggled%3Aexample&page=1 255 | [controlled-components-lecture]: https://courses.reacttraining.com/courses/advanced-react/lectures/3172720 256 | [react-training]: https://reacttraining.com/ 257 | [advanced-react]: https://courses.reacttraining.com/courses/enrolled/200086 258 | [fac]: https://medium.com/merrickchristensen/function-as-child-components-5f3920a9ace9 259 | [glamorous-with-next]: https://github.com/kentcdodds/glamorous-with-next 260 | [downshift]: https://github.com/paypal/downshift 261 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const jestConfig = require('kcd-scripts/config').jest 2 | 3 | jestConfig.snapshotSerializers = jestConfig.snapshotSerializers || [] 4 | jestConfig.snapshotSerializers.push( 5 | 'jest-serializer-html', 6 | 'enzyme-to-json/serializer', 7 | ) 8 | jestConfig.setupFiles = jestConfig.setupFiles || [] 9 | jestConfig.setupFiles.push('/other/setup-tests.js') 10 | 11 | module.exports = jestConfig 12 | -------------------------------------------------------------------------------- /other/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | 4 | 5 | **Table of Contents** 6 | 7 | - [Our Pledge](#our-pledge) 8 | - [Our Standards](#our-standards) 9 | - [Our Responsibilities](#our-responsibilities) 10 | - [Scope](#scope) 11 | - [Enforcement](#enforcement) 12 | - [Attribution](#attribution) 13 | 14 | 15 | 16 | ## Our Pledge 17 | 18 | In the interest of fostering an open and welcoming environment, we as 19 | contributors and maintainers pledge to making participation in our project and 20 | our community a harassment-free experience for everyone, regardless of age, body 21 | size, disability, ethnicity, gender identity and expression, level of experience, 22 | nationality, personal appearance, race, religion, or sexual identity and 23 | orientation. 24 | 25 | ## Our Standards 26 | 27 | Examples of behavior that contributes to creating a positive environment 28 | include: 29 | 30 | * Using welcoming and inclusive language 31 | * Being respectful of differing viewpoints and experiences 32 | * Gracefully accepting constructive criticism 33 | * Focusing on what is best for the community 34 | * Showing empathy towards other community members 35 | 36 | Examples of unacceptable behavior by participants include: 37 | 38 | * The use of sexualized language or imagery and unwelcome sexual attention or 39 | advances 40 | * Trolling, insulting/derogatory comments, and personal or political attacks 41 | * Public or private harassment 42 | * Publishing others' private information, such as a physical or electronic 43 | address, without explicit permission 44 | * Other conduct which could reasonably be considered inappropriate in a 45 | professional setting 46 | 47 | ## Our Responsibilities 48 | 49 | Project maintainers are responsible for clarifying the standards of acceptable 50 | behavior and are expected to take appropriate and fair corrective action in 51 | response to any instances of unacceptable behavior. 52 | 53 | Project maintainers have the right and responsibility to remove, edit, or 54 | reject comments, commits, code, wiki edits, issues, and other contributions 55 | that are not aligned to this Code of Conduct, or to ban temporarily or 56 | permanently any contributor for other behaviors that they deem inappropriate, 57 | threatening, offensive, or harmful. 58 | 59 | ## Scope 60 | 61 | This Code of Conduct applies both within project spaces and in public spaces 62 | when an individual is representing the project or its community. Examples of 63 | representing a project or community include using an official project e-mail 64 | address, posting via an official social media account, or acting as an appointed 65 | representative at an online or offline event. Representation of a project may be 66 | further defined and clarified by project maintainers. 67 | 68 | ## Enforcement 69 | 70 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 71 | reported by contacting the project team at kent+coc@doddsfamily.us. All 72 | complaints will be reviewed and investigated and will result in a response that 73 | is deemed necessary and appropriate to the circumstances. The project team is 74 | obligated to maintain confidentiality with regard to the reporter of an incident. 75 | Further details of specific enforcement policies may be posted separately. 76 | 77 | Project maintainers who do not follow or enforce the Code of Conduct in good 78 | faith may face temporary or permanent repercussions as determined by other 79 | members of the project's leadership. 80 | 81 | ## Attribution 82 | 83 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 84 | available at [http://contributor-covenant.org/version/1/4][version] 85 | 86 | [homepage]: http://contributor-covenant.org 87 | [version]: http://contributor-covenant.org/version/1/4/ 88 | -------------------------------------------------------------------------------- /other/MAINTAINING.md: -------------------------------------------------------------------------------- 1 | # Maintaining 2 | 3 | 4 | 5 | **Table of Contents** 6 | 7 | - [Code of Conduct](#code-of-conduct) 8 | - [Issues](#issues) 9 | - [Pull Requests](#pull-requests) 10 | - [Release](#release) 11 | - [Thanks!](#thanks) 12 | 13 | 14 | 15 | This is documentation for maintainers of this project. 16 | 17 | ## Code of Conduct 18 | 19 | Please review, understand, and be an example of it. Violations of the code of conduct are 20 | taken seriously, even (especially) for maintainers. 21 | 22 | ## Issues 23 | 24 | We want to support and build the community. We do that best by helping people learn to solve 25 | their own problems. We have an issue template and hopefully most folks follow it. If it's 26 | not clear what the issue is, invite them to create a minimal reproduction of what they're trying 27 | to accomplish or the bug they think they've found. 28 | 29 | Once it's determined that a code change is necessary, point people to 30 | [makeapullrequest.com](http://makeapullrequest.com) and invite them to make a pull request. 31 | If they're the one who needs the feature, they're the one who can build it. If they need 32 | some hand holding and you have time to lend a hand, please do so. It's an investment into 33 | another human being, and an investment into a potential maintainer. 34 | 35 | Remember that this is open source, so the code is not yours, it's ours. If someone needs a change 36 | in the codebase, you don't have to make it happen yourself. Commit as much time to the project 37 | as you want/need to. Nobody can ask any more of you than that. 38 | 39 | ## Pull Requests 40 | 41 | As a maintainer, you're fine to make your branches on the main repo or on your own fork. Either 42 | way is fine. 43 | 44 | When we receive a pull request, a travis build is kicked off automatically (see the `.travis.yml` 45 | for what runs in the travis build). We avoid merging anything that breaks the travis build. 46 | 47 | Please review PRs and focus on the code rather than the individual. You never know when this is 48 | someone's first ever PR and we want their experience to be as positive as possible, so be 49 | uplifting and constructive. 50 | 51 | When you merge the pull request, 99% of the time you should use the 52 | [Squash and merge](https://help.github.com/articles/merging-a-pull-request/) feature. This keeps 53 | our git history clean, but more importantly, this allows us to make any necessary changes to the 54 | commit message so we release what we want to release. See the next section on Releases for more 55 | about that. 56 | 57 | ## Release 58 | 59 | Our releases are automatic. They happen whenever code lands into `master`. A travis build gets 60 | kicked off and if it's successful, a tool called 61 | [`semantic-release`](https://github.com/semantic-release/semantic-release) is used to 62 | automatically publish a new release to npm as well as a changelog to GitHub. It is only able to 63 | determine the version and whether a release is necessary by the git commit messages. With this 64 | in mind, **please brush up on [the commit message convention][commit] which drives our releases.** 65 | 66 | > One important note about this: Please make sure that commit messages do NOT contain the words 67 | > "BREAKING CHANGE" in them unless we want to push a major version. I've been burned by this 68 | > more than once where someone will include "BREAKING CHANGE: None" and it will end up releasing 69 | > a new major version. Not a huge deal honestly, but kind of annoying... 70 | 71 | ## Thanks! 72 | 73 | Thank you so much for helping to maintain this project! 74 | 75 | [commit]: https://github.com/conventional-changelog-archived-repos/conventional-changelog-angular/blob/ed32559941719a130bb0327f886d6a32a8cbc2ba/convention.md 76 | -------------------------------------------------------------------------------- /other/TYPESCRIPT_USAGE.md: -------------------------------------------------------------------------------- 1 | # Typescript Usage 2 | 3 | The current bundled Typescript definitions are incomplete and based around the 4 | needs of the developers who contributed them. 5 | 6 | Pull requests to improve them are welcome and appreciated. If you've never contributed to open source before, then you may find [this free video course](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github) helpful. 7 | -------------------------------------------------------------------------------- /other/USERS.md: -------------------------------------------------------------------------------- 1 | # Users 2 | 3 | 4 | 5 | 6 | 7 | If you or your company uses this project, add your name to this list! Eventually 8 | we may have a website to showcase these (wanna build it!?) 9 | 10 | > No users have been added yet! 11 | 12 | 17 | -------------------------------------------------------------------------------- /other/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ['.'], 3 | testEnvironment: 'jsdom', 4 | testPathIgnorePatterns: ['/node_modules/'], 5 | } 6 | -------------------------------------------------------------------------------- /other/logo/react-toggled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/react-toggled/36b406ab7f0c79f4cbf9c55e7c574e2b05318fd5/other/logo/react-toggled.png -------------------------------------------------------------------------------- /other/manual-releases.md: -------------------------------------------------------------------------------- 1 | # manual-releases 2 | 3 | 4 | 5 | 6 | 7 | This project has an automated release set up. So things are only released when there are 8 | useful changes in the code that justify a release. But sometimes things get messed up one way or another 9 | and we need to trigger the release ourselves. When this happens, simply bump the number below and commit 10 | that with the following commit message based on your needs: 11 | 12 | **Major** 13 | 14 | ``` 15 | fix(release): manually release a major version 16 | 17 | There was an issue with a major release, so this manual-releases.md 18 | change is to release a new major version. 19 | 20 | Reference: # 21 | 22 | BREAKING CHANGE: 23 | ``` 24 | 25 | **Minor** 26 | 27 | ``` 28 | feat(release): manually release a minor version 29 | 30 | There was an issue with a minor release, so this manual-releases.md 31 | change is to release a new minor version. 32 | 33 | Reference: # 34 | ``` 35 | 36 | **Patch** 37 | 38 | ``` 39 | fix(release): manually release a patch version 40 | 41 | There was an issue with a patch release, so this manual-releases.md 42 | change is to release a new patch version. 43 | 44 | Reference: # 45 | ``` 46 | 47 | The number of times we've had to do a manual release is: 0 48 | -------------------------------------------------------------------------------- /other/misc-tests/__tests__/build.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is here to validate that the built version 3 | * of the library exposes the module in the way that we 4 | * want it to. Specifically that the ES6 module import can 5 | * get the react-toggled function via default import. Also that 6 | * the CommonJS require returns the react-toggled function 7 | * (rather than an object that has the react-toggled as a 8 | * `default` property). 9 | * 10 | * This file is unable to validate the global export. 11 | */ 12 | import assert from 'assert' 13 | 14 | import esImport from '../../../dist/react-toggled.esm' 15 | 16 | import cjsImport from '../../../' // picks up the main from package.json 17 | 18 | import umdImport from '../../../dist/react-toggled.umd' 19 | 20 | // intentionally left out because you shouldn't ever 21 | // try to require the ES file in CommonJS 22 | // const esRequire = require('../../../dist/react-toggled.es') 23 | const cjsRequire = require('../../../') // picks up the main from package.json 24 | const umdRequire = require('../../../dist/react-toggled.umd') 25 | 26 | test('stuff is good', () => { 27 | assert(isToggleComponent(esImport), 'ES build has a problem with ES Modules') 28 | 29 | assert( 30 | isToggleComponent(cjsImport), 31 | 'CJS build has a problem with ES Modules', 32 | ) 33 | 34 | assert(isToggleComponent(cjsRequire), 'CJS build has a problem with CJS') 35 | 36 | assert( 37 | isToggleComponent(umdImport), 38 | 'UMD build has a problem with ES Modules', 39 | ) 40 | 41 | assert(isToggleComponent(umdRequire), 'UMD build has a problem with CJS') 42 | 43 | // TODO: how could we validate the global export? 44 | }) 45 | 46 | function isToggleComponent(thing) { 47 | if (typeof thing !== 'function') { 48 | console.error( 49 | `react-toggled thing should be a function. It's a ${typeof thing} with the properties of: ${Object.keys( 50 | thing, 51 | ).join(', ')}`, 52 | ) 53 | return false 54 | } 55 | return true 56 | } 57 | 58 | /* 59 | eslint 60 | no-console: 0, 61 | import/extensions: 0, 62 | import/no-unresolved: 0, 63 | import/no-duplicates: 0, 64 | no-duplicate-imports: 0, 65 | */ 66 | -------------------------------------------------------------------------------- /other/misc-tests/__tests__/preact.js: -------------------------------------------------------------------------------- 1 | // Tell Babel to transform JSX into preact.h() calls: 2 | /** @jsx preact.h */ 3 | /* 4 | eslint-disable 5 | react/prop-types, 6 | no-console, 7 | react/display-name, 8 | import/extensions, 9 | import/no-unresolved 10 | */ 11 | 12 | /* 13 | Testing the preact version is a tiny bit complicated because 14 | we need the preact build (the one that imports 'preact' rather 15 | than 'react') otherwise things don't work very well. 16 | So there's a script `test.build` which will run the cjs build 17 | for preact before running this test. 18 | */ 19 | 20 | import preact from 'preact' 21 | import render from 'preact-render-to-string' 22 | import Toggle from '../../../preact/dist/react-toggled.esm' 23 | 24 | test('works with preact', () => { 25 | const childSpy = jest.fn(({on, getTogglerProps}) => ( 26 |
27 | 28 |
{on ? 'Toggled On' : 'Toggled Off'}
29 |
30 | )) 31 | render({childSpy}) 32 | expect(childSpy).toHaveBeenCalledWith( 33 | expect.objectContaining({ 34 | on: false, 35 | }), 36 | ) 37 | }) 38 | -------------------------------------------------------------------------------- /other/misc-tests/jest.config.js: -------------------------------------------------------------------------------- 1 | const jestConfig = require('kcd-scripts/config').jest 2 | 3 | module.exports = Object.assign(jestConfig, { 4 | roots: ['.'], 5 | testEnvironment: 'jsdom', 6 | }) 7 | -------------------------------------------------------------------------------- /other/raf-polyfill.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * requestAnimationFrame polyfill from https://gist.github.com/paulirish/1579671 4 | * http://paulirish.com/2011/requestanimationframe-for-smart-animating/ 5 | * http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating 6 | * requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel 7 | * MIT license 8 | */ 9 | 10 | var lastTime = 0 11 | var vendors = ['ms', 'moz', 'webkit', 'o'] 12 | for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { 13 | window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'] 14 | window.cancelAnimationFrame = 15 | window[vendors[x] + 'CancelAnimationFrame'] || 16 | window[vendors[x] + 'CancelRequestAnimationFrame'] 17 | } 18 | 19 | if (!window.requestAnimationFrame) { 20 | window.requestAnimationFrame = function(callback, element) { 21 | var currTime = new Date().getTime() 22 | var timeToCall = Math.max(0, 16 - (currTime - lastTime)) 23 | var id = window.setTimeout(function() { 24 | // eslint-disable-next-line consumerweb/no-callback-literal 25 | callback(currTime + timeToCall) 26 | }, timeToCall) 27 | lastTime = currTime + timeToCall 28 | return id 29 | } 30 | global.requestAnimationFrame = window.requestAnimationFrame 31 | } 32 | 33 | if (!window.cancelAnimationFrame) { 34 | window.cancelAnimationFrame = function(id) { 35 | clearTimeout(id) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /other/setup-tests.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-unassigned-import 2 | import './raf-polyfill' 3 | import Enzyme from 'enzyme' 4 | import Adapter from 'enzyme-adapter-react-16' 5 | 6 | Enzyme.configure({adapter: new Adapter()}) 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-toggled", 3 | "version": "0.0.0-semantically-released", 4 | "description": "Component to build simple, flexible, and accessible toggle components.", 5 | "main": "dist/react-toggled.cjs.js", 6 | "jsnext:main": "dist/react-toggled.esm.js", 7 | "module": "dist/react-toggled.esm.js", 8 | "typings": "typings/index.d.ts", 9 | "scripts": { 10 | "add-contributor": "kcd-scripts contributors add", 11 | "build": "kcd-scripts build --bundle --p-react", 12 | "lint": "kcd-scripts lint", 13 | "test": "kcd-scripts test", 14 | "test:cover": "kcd-scripts test --coverage", 15 | "test:ts": "tsc --noEmit -p ./tsconfig.json", 16 | "test:update": "npm run test:cover -s -- --updateSnapshot", 17 | "test:build": "kcd-scripts test --config other/misc-tests/jest.config.js --no-watch", 18 | "build-and-test": "npm run build -s && npm run test:build -s", 19 | "storybook": "start-storybook -p 6006 -c stories", 20 | "storybook:build": "cd stories && npm install && cd .. && build-storybook -c stories", 21 | "validate": "kcd-scripts validate lint,build-and-test,test:cover,test:ts", 22 | "precommit": "kcd-scripts precommit" 23 | }, 24 | "files": [ 25 | "dist", 26 | "typings", 27 | "preact" 28 | ], 29 | "keywords": [ 30 | "toggle", 31 | "react", 32 | "accessibility", 33 | "WAI-ARIA" 34 | ], 35 | "author": "Kent C. Dodds (http://kentcdodds.com/)", 36 | "license": "MIT", 37 | "peerDependencies": { 38 | "react": ">=15", 39 | "prop-types": ">=15" 40 | }, 41 | "devDependencies": { 42 | "@storybook/react": "^3.2.3", 43 | "enzyme": "^3.1.0", 44 | "enzyme-adapter-react-16": "^1.0.1", 45 | "enzyme-to-json": "^3.1.2", 46 | "jest-serializer-html": "^4.0.0", 47 | "kcd-scripts": "^0.39.1", 48 | "preact": "^8.2.1", 49 | "preact-render-to-string": "^3.6.3", 50 | "prop-types": "^15.5.10", 51 | "react": "^16.0.0", 52 | "react-dom": "^16.0.0", 53 | "react-test-renderer": "^16.0.0", 54 | "typescript": "^2.6.2" 55 | }, 56 | "eslintConfig": { 57 | "extends": "./node_modules/kcd-scripts/eslint.js" 58 | }, 59 | "eslintIgnore": [ 60 | "node_modules", 61 | "coverage", 62 | "dist", 63 | "storybook-static", 64 | "typings" 65 | ], 66 | "repository": { 67 | "type": "git", 68 | "url": "https://github.com/kentcdodds/react-toggled.git" 69 | }, 70 | "bugs": { 71 | "url": "https://github.com/kentcdodds/react-toggled/issues" 72 | }, 73 | "homepage": "https://github.com/kentcdodds/react-toggled#readme", 74 | "dependencies": {} 75 | } 76 | -------------------------------------------------------------------------------- /src/__tests__/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "react/prop-types": "off", 4 | "react/display-name": "off", 5 | "react/no-deprecated": "off", 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/__tests__/__snapshots__/index.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`getTogglerProps for \`on\` false 1`] = ` 4 | Object { 5 | "aria-expanded": false, 6 | "id": "extra", 7 | "onClick": [Function], 8 | "tabIndex": 0, 9 | } 10 | `; 11 | 12 | exports[`getTogglerProps for \`on\` true 1`] = ` 13 | Object { 14 | "aria-expanded": true, 15 | "id": "extra", 16 | "onClick": [Function], 17 | "tabIndex": 0, 18 | } 19 | `; 20 | -------------------------------------------------------------------------------- /src/__tests__/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {mount} from 'enzyme' 3 | import Toggler from '../' 4 | 5 | test('on is defaulted to false', () => { 6 | const {on} = setup() 7 | expect(on).toBe(false) 8 | }) 9 | 10 | test('on is defaulted to true when defaultOn is true', () => { 11 | const {on} = setup({defaultOn: true}) 12 | expect(on).toBe(true) 13 | }) 14 | 15 | test('on can be controlled', () => { 16 | const {on, setOff} = setup({on: true}) 17 | expect(on).toBe(true) 18 | setOff() 19 | expect(on).toBe(true) 20 | }) 21 | 22 | test('setOff sets `on` to false', () => { 23 | const {childSpy, setOff} = setup({defaultOn: true}) 24 | setOff() 25 | expect(childSpy).toHaveBeenLastCalledWith( 26 | expect.objectContaining({on: false}), 27 | ) 28 | }) 29 | 30 | test('setOn sets `on` to true', () => { 31 | const {childSpy, setOn} = setup() 32 | setOn() 33 | expect(childSpy).toHaveBeenLastCalledWith(expect.objectContaining({on: true})) 34 | }) 35 | 36 | test('toggle changes the `on` state', () => { 37 | const {childSpy, toggle} = setup() 38 | toggle() 39 | expect(childSpy).toHaveBeenLastCalledWith(expect.objectContaining({on: true})) 40 | toggle() 41 | expect(childSpy).toHaveBeenLastCalledWith( 42 | expect.objectContaining({on: false}), 43 | ) 44 | }) 45 | 46 | test('getTogglerProps for `on` false', () => { 47 | const {getTogglerProps} = setup({on: false}) 48 | expect(getTogglerProps({id: 'extra'})).toMatchSnapshot() 49 | }) 50 | 51 | test('getTogglerProps for `on` true', () => { 52 | const {getTogglerProps} = setup({on: true}) 53 | expect(getTogglerProps({id: 'extra'})).toMatchSnapshot() 54 | }) 55 | 56 | test('getTogglerProps does not blow up without an argument', () => { 57 | const {getTogglerProps} = setup({on: true}) 58 | expect(() => getTogglerProps()).not.toThrow() 59 | }) 60 | 61 | test('getTogglerProps sets a tabIndex if not present', () => { 62 | const {getTogglerProps} = setup() 63 | expect(getTogglerProps()).toHaveProperty('tabIndex', 0) 64 | }) 65 | 66 | test('getTogglerProps passes existing tabIndex', () => { 67 | const {getTogglerProps} = setup() 68 | expect(getTogglerProps({tabIndex: 1})).toHaveProperty('tabIndex', 1) 69 | }) 70 | 71 | test('getTogglerProps returns an onClick that calls the given onClick', () => { 72 | const {childSpy, getTogglerProps} = setup() 73 | const mockClick = jest.fn() 74 | const {onClick} = getTogglerProps({onClick: mockClick}) 75 | const fakeEvent = {target: null} 76 | onClick(fakeEvent) 77 | expect(mockClick).toHaveBeenCalledTimes(1) 78 | expect(mockClick).toHaveBeenCalledWith(fakeEvent) 79 | expect(childSpy).toHaveBeenLastCalledWith(expect.objectContaining({on: true})) 80 | }) 81 | 82 | test('getElementTogglerProps returns an onClick that calls the given onClick', () => { 83 | const {childSpy, getElementTogglerProps} = setup() 84 | const mockClick = jest.fn() 85 | const {onClick} = getElementTogglerProps({onClick: mockClick}) 86 | const fakeEvent = {target: null} 87 | onClick(fakeEvent) 88 | expect(mockClick).toHaveBeenCalledTimes(1) 89 | expect(mockClick).toHaveBeenCalledWith(fakeEvent) 90 | expect(childSpy).toHaveBeenLastCalledWith(expect.objectContaining({on: true})) 91 | }) 92 | 93 | test('getInputTogglerProps returns an onClick that calls the given onClick', () => { 94 | const {childSpy, getInputTogglerProps} = setup() 95 | const mockClick = jest.fn() 96 | const {onClick} = getInputTogglerProps({onClick: mockClick}) 97 | const fakeEvent = {target: null} 98 | onClick(fakeEvent) 99 | expect(mockClick).toHaveBeenCalledTimes(1) 100 | expect(mockClick).toHaveBeenCalledWith(fakeEvent) 101 | expect(childSpy).toHaveBeenLastCalledWith(expect.objectContaining({on: true})) 102 | }) 103 | 104 | test('getElementTogglerProps returns an onKeyUp that toggles on enter', () => { 105 | const {childSpy, getElementTogglerProps} = setup() 106 | const {onKeyUp} = getElementTogglerProps() 107 | const fakeEvent = {target: null, key: 'Enter'} 108 | onKeyUp(fakeEvent) 109 | expect(childSpy).toHaveBeenLastCalledWith(expect.objectContaining({on: true})) 110 | }) 111 | 112 | test('getElementTogglerProps returns an onKeyUp that toggles on spacebar', () => { 113 | const {childSpy, getElementTogglerProps} = setup() 114 | const {onKeyUp} = getElementTogglerProps() 115 | const fakeEvent = {target: null, key: ' '} 116 | onKeyUp(fakeEvent) 117 | expect(childSpy).toHaveBeenLastCalledWith(expect.objectContaining({on: true})) 118 | }) 119 | 120 | test('getElementTogglerProps returns an onKeyUp that does not toggle on other keys', () => { 121 | const {childSpy, getElementTogglerProps} = setup() 122 | const {onKeyUp} = getElementTogglerProps() 123 | const fakeEvent = {target: null} 124 | onKeyUp(fakeEvent) 125 | expect(childSpy).toHaveBeenLastCalledWith( 126 | expect.objectContaining({on: false}), 127 | ) 128 | }) 129 | 130 | test('getInputTogglerProps returns an onKeyUp that toggles on enter', () => { 131 | const {childSpy, getInputTogglerProps} = setup() 132 | const {onKeyUp} = getInputTogglerProps() 133 | const fakeEvent = {target: null, key: 'Enter'} 134 | onKeyUp(fakeEvent) 135 | expect(childSpy).toHaveBeenLastCalledWith(expect.objectContaining({on: true})) 136 | }) 137 | 138 | test('getInputTogglerProps returns an onKeyUp that does not toggle on spacebar', () => { 139 | const {childSpy, getInputTogglerProps} = setup() 140 | const {onKeyUp} = getInputTogglerProps() 141 | const fakeEvent = {target: null} 142 | onKeyUp(fakeEvent) 143 | expect(childSpy).toHaveBeenLastCalledWith( 144 | expect.objectContaining({on: false}), 145 | ) 146 | }) 147 | 148 | test('children can be an array (for preact support)', () => { 149 | const childSpy = jest.fn(() =>
) 150 | mount({[childSpy]}) 151 | expect(childSpy).toHaveBeenLastCalledWith( 152 | expect.objectContaining({on: false}), 153 | ) 154 | }) 155 | 156 | test('onToggle gets called in controlled prop scenario', () => { 157 | const spy = jest.fn() 158 | const {wrapper} = setup({on: false, onToggle: spy}) 159 | expect(spy).not.toHaveBeenCalled() 160 | wrapper.setProps({on: true}) 161 | expect(spy).toHaveBeenCalled() 162 | expect(spy.mock.calls.length).toBe(1) 163 | }) 164 | 165 | test('onToggle gets called with fresh state in controlled prop scenario', () => { 166 | const spy = jest.fn() 167 | const {wrapper} = setup({on: false, onToggle: spy}) 168 | wrapper.setProps({on: true}) 169 | expect(spy).toHaveBeenLastCalledWith(true, expect.anything()) 170 | expect(spy.mock.calls.length).toBe(1) 171 | }) 172 | 173 | test('onToggle gets called on internal state change in controlled prop scenario', () => { 174 | const spy = jest.fn() 175 | const {setOn, setOff, wrapper} = setup({on: false, onToggle: spy}) 176 | setOff() 177 | expect(spy).not.toHaveBeenCalled() 178 | setOn() 179 | expect(spy).toHaveBeenLastCalledWith(true, expect.anything()) 180 | wrapper.setProps({on: true}) 181 | expect(spy.mock.calls.length).toBe(1) 182 | }) 183 | 184 | function setup({children = () =>
, ...props} = {}) { 185 | let renderArg 186 | const childSpy = jest.fn(controllerArg => { 187 | renderArg = controllerArg 188 | return children(controllerArg) 189 | }) 190 | const wrapper = mount({childSpy}) 191 | return {childSpy, wrapper, ...renderArg} 192 | } 193 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import {Component} from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | const callAll = (...fns) => (...args) => fns.forEach(fn => fn && fn(...args)) 5 | const noop = () => {} 6 | 7 | class Toggle extends Component { 8 | static propTypes = { 9 | defaultOn: PropTypes.bool, 10 | on: PropTypes.bool, 11 | onToggle: PropTypes.func, 12 | children: PropTypes.oneOfType([PropTypes.func, PropTypes.array]).isRequired, 13 | } 14 | static defaultProps = { 15 | defaultOn: false, 16 | onToggle: noop, 17 | } 18 | state = { 19 | on: this.getOn({on: this.props.defaultOn}), 20 | } 21 | 22 | getOn(state = this.state, props = this.props) { 23 | return this.isOnControlled() ? props.on : state.on 24 | } 25 | 26 | isOnControlled() { 27 | return this.props.on !== undefined 28 | } 29 | 30 | getTogglerProps = (props = {}) => ({ 31 | 'aria-expanded': Boolean(this.getOn()), 32 | tabIndex: 0, 33 | ...props, 34 | onClick: callAll(props.onClick, this.toggle), 35 | }) 36 | 37 | toggleKeys = ['Enter', ' '] // This matches 10 | 11 | 12 | 13 |
{on ? 'Toggled On' : 'Toggled Off'}
14 |
15 | )} 16 | 17 | ) 18 | } 19 | 20 | export default Basic 21 | -------------------------------------------------------------------------------- /stories/examples/checkbox.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Toggle from '../../src' 3 | 4 | function Checkbox() { 5 | return ( 6 | 7 | {({on, getInputTogglerProps}) => ( 8 | 16 | 28 | 39 | 40 | )} 41 | 42 | ) 43 | } 44 | 45 | export default Checkbox 46 | -------------------------------------------------------------------------------- /stories/examples/switch.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Toggle from '../../src' 3 | 4 | function Switch() { 5 | return ( 6 | 7 | {({on, getElementTogglerProps}) => ( 8 | 22 | 30 | 41 | 42 | )} 43 | 44 | ) 45 | } 46 | 47 | export default Switch 48 | -------------------------------------------------------------------------------- /stories/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stories", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "Kent C. Dodds (http://kentcdodds.com/)", 11 | "license": "MIT", 12 | "dependencies": { 13 | "axios": "^0.16.2", 14 | "glamor": "^2.20.37", 15 | "glamorous": "^3.24.0", 16 | "match-sorter": "^1.8.0", 17 | "react": "^15.6.1", 18 | "react-apollo": "^1.4.8", 19 | "react-dom": "^15.6.1", 20 | "react-instantsearch": "^4.0.9", 21 | "react-popper": "^0.7.2", 22 | "react-tiny-virtual-list": "^2.0.6", 23 | "react-virtualized": "^9.9.0", 24 | "throttle-debounce": "^1.0.1" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/basic.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import Toggle from '../' 3 | 4 | interface Props {} 5 | 6 | interface State {} 7 | 8 | export default class App extends React.Component { 9 | 10 | onClickCall = () => console.log('hello') 11 | 12 | render() { 13 | return ( 14 | 15 | {({ 16 | on, 17 | getTogglerProps, 18 | toggle, 19 | setOn, 20 | setOff, 21 | getInputTogglerProps, 22 | getElementTogglerProps 23 | }) => ( 24 |
25 | 26 | 27 | 28 | 29 | 34 | ToggleSpan 35 |
{on ? 'Toggled On' : 'Toggled Off'}
36 |
37 | )} 38 |
39 | ) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react", 4 | "noUnusedLocals": true 5 | }, 6 | "include": ["test/**/*.tsx"] 7 | } 8 | -------------------------------------------------------------------------------- /typings/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | export interface GetElementPropsOptions extends React.HTMLProps {} 4 | 5 | export interface GetInputPropsOptions 6 | extends React.HTMLProps {} 7 | 8 | export interface GetButtonPropsOptions 9 | extends React.HTMLProps {} 10 | 11 | export interface TogglerStateAndHelpers { 12 | readonly on: boolean 13 | readonly getTogglerProps: (options?: GetButtonPropsOptions) => any 14 | readonly getInputTogglerProps: (options?: GetButtonPropsOptions) => any 15 | readonly getElementTogglerProps: (options?: GetElementPropsOptions) => any 16 | readonly setOn: () => void 17 | readonly setOff: () => void 18 | readonly toggle: () => void 19 | } 20 | 21 | export type ChildrenFunction = ( 22 | options: TogglerStateAndHelpers, 23 | ) => React.ReactNode 24 | 25 | export interface ReactToggledProps { 26 | readonly defaultOn?: boolean 27 | readonly onToggle?: (on: boolean, object: TogglerStateAndHelpers) => void 28 | readonly on?: boolean 29 | readonly children: ChildrenFunction 30 | } 31 | 32 | export type ReactToggledInterface = React.ComponentClass 33 | 34 | declare const Toggle: ReactToggledInterface 35 | export default Toggle 36 | --------------------------------------------------------------------------------