├── .gitignore ├── LICENSE ├── README.md ├── example ├── .npmignore ├── index.html ├── index.tsx ├── package.json └── tsconfig.json ├── package.json ├── src └── index.tsx ├── test └── disabled.test.tsx ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | .cache 5 | .rts2_cache_cjs 6 | .rts2_cache_esm 7 | .rts2_cache_umd 8 | .rts2_cache_system 9 | dist 10 | .idea 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Kristijan Ristovski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🙅‍♂️ react-disable 2 | 3 | ### Other projects by [@thekitze](https://twitter.com/thekitze) 4 | 5 | - 💻 [Sizzy](https://sizzy.co) - A browser for developers and designers 6 | - 🏫 [React Academy](https://reactacademy.io) - Interactive React and GraphQL workshops 7 | - 💌 [Twizzy](https://twizzy.app) - A standalone app for Twitter DM 8 | - 🤖 [JSUI](https://github.com/kitze/JSUI) - A powerful UI toolkit for managing JavaScript apps 9 | 10 | --- 11 | 12 | ## Demo 13 | ![demo](https://i.imgur.com/9D6Xeps.gif) 14 | 15 | ## Usage 16 | 17 | `yarn add react-disable` 18 | 19 | Just wrap any children with the `Disable` component in order to disable the section. 20 | The disabled sections are also aware if a parent is disabled, so they will be disabled, but the styles won't be duplicated (the opacity won't be multiplied, etc.) 21 | 22 | ```jsx 23 | import { Disable } from 'react-disable'; 24 | 25 | const App = () => { 26 | const [disableForm, setDisableForm] = React.useState(false); 27 | const toggle = () => setDisableForm(d => !d); 28 | 29 | return ( 30 |
31 |

Hello world

32 | 33 | 34 | 35 |

Login form

36 | 37 | 38 | 39 |
40 | 41 |
42 | ); 43 | }; 44 | ``` 45 | 46 | 47 | -------------------------------------------------------------------------------- /example/.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .cache 3 | dist -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Playground 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/index.tsx: -------------------------------------------------------------------------------- 1 | import 'react-app-polyfill/ie11'; 2 | import * as React from 'react'; 3 | import * as ReactDOM from 'react-dom'; 4 | 5 | import { Disable } from '../.'; 6 | 7 | const App = () => { 8 | const [disableForm, setDisableForm] = React.useState(false); 9 | const toggle = () => setDisableForm(d => !d); 10 | 11 | return ( 12 |
13 |

Hello world

14 | 15 | 16 |

Login form

17 | 18 | 19 | 20 |
21 |
22 | ); 23 | }; 24 | 25 | ReactDOM.render(, document.getElementById('root')); 26 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "start": "parcel index.html", 8 | "build": "parcel build index.html" 9 | }, 10 | "dependencies": { 11 | "react-app-polyfill": "^1.0.0" 12 | }, 13 | "alias": { 14 | "react": "../node_modules/react", 15 | "react-dom": "../node_modules/react-dom/profiling", 16 | "scheduler/tracing": "../node_modules/scheduler/tracing-profiling" 17 | }, 18 | "devDependencies": { 19 | "@types/react": "^16.9.11", 20 | "@types/react-dom": "^16.8.4", 21 | "parcel": "^1.12.3", 22 | "typescript": "^3.4.5" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": false, 4 | "target": "es5", 5 | "module": "commonjs", 6 | "jsx": "react", 7 | "moduleResolution": "node", 8 | "noImplicitAny": false, 9 | "noUnusedLocals": false, 10 | "noUnusedParameters": false, 11 | "removeComments": true, 12 | "strictNullChecks": true, 13 | "preserveConstEnums": true, 14 | "sourceMap": true, 15 | "lib": ["es2015", "es2016", "dom"], 16 | "baseUrl": ".", 17 | "types": ["node"] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-disable", 3 | "version": "0.1.1", 4 | "license": "MIT", 5 | "author": "Kristijan Ristovski", 6 | "main": "dist/index.js", 7 | "module": "dist/react-disable.esm.js", 8 | "typings": "dist/index.d.ts", 9 | "files": [ 10 | "dist" 11 | ], 12 | "scripts": { 13 | "start": "tsdx watch", 14 | "build": "tsdx build", 15 | "test": "tsdx test --env=jsdom", 16 | "lint": "tsdx lint" 17 | }, 18 | "peerDependencies": { 19 | "react": ">=16" 20 | }, 21 | "husky": { 22 | "hooks": { 23 | "pre-commit": "tsdx lint" 24 | } 25 | }, 26 | "prettier": { 27 | "printWidth": 80, 28 | "semi": true, 29 | "singleQuote": true, 30 | "trailingComma": "es5" 31 | }, 32 | "devDependencies": { 33 | "@testing-library/jest-dom": "^4.2.4", 34 | "@testing-library/react": "^9.3.2", 35 | "@types/jest": "^24.0.23", 36 | "@types/react": "^16.9.11", 37 | "@types/react-dom": "^16.9.4", 38 | "husky": "^3.1.0", 39 | "react": "^16.12.0", 40 | "react-dom": "^16.12.0", 41 | "tsdx": "^0.11.0", 42 | "tslib": "^1.10.0", 43 | "typescript": "^3.7.2" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export const DisabledContext = React.createContext(false); 4 | 5 | export const Disable: React.FC<{ 6 | as?: string; 7 | disabled: boolean; 8 | disabledProps?: any; 9 | disabledStyles?: React.CSSProperties; 10 | disabledOpacity?: number; 11 | children: React.ReactNode; 12 | }> = ({ 13 | as = 'div', 14 | children, 15 | disabled, 16 | disabledProps, 17 | disabledStyles, 18 | disabledOpacity = 0.3, 19 | ...rest 20 | }) => { 21 | const isParentDisabled = React.useContext(DisabledContext); 22 | const shouldDisable = isParentDisabled ? false : disabled; 23 | const disableEvent = React.useCallback( 24 | (e: React.SyntheticEvent) => { 25 | e.persist(); 26 | 27 | if (shouldDisable) { 28 | e.preventDefault(); 29 | e.stopPropagation(); 30 | } 31 | }, 32 | [shouldDisable] 33 | ); 34 | 35 | return ( 36 | 37 | {React.createElement( 38 | as, 39 | { 40 | style: { 41 | transition: 'all 100ms linear', 42 | ...(shouldDisable && { 43 | opacity: disabledOpacity, 44 | pointerEvents: 'none', 45 | userSelect: 'none', 46 | ...disabledStyles, 47 | }), 48 | }, 49 | onClick: disableEvent, 50 | onKeyDown: disableEvent, 51 | ...(shouldDisable && { 52 | tabIndex: -1, 53 | 'aria-hidden': 'true', 54 | ...disabledProps, 55 | }), 56 | ...rest, 57 | }, 58 | children 59 | )} 60 | 61 | ); 62 | }; 63 | -------------------------------------------------------------------------------- /test/disabled.test.tsx: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom/extend-expect'; 2 | 3 | import * as React from 'react'; 4 | import { render, fireEvent } from '@testing-library/react'; 5 | 6 | import { Disable } from './'; 7 | 8 | describe('Disabled', () => { 9 | it('disable children events when disabled prop is true', () => { 10 | const handleClick = jest.fn(); 11 | 12 | const { getByTestId } = render( 13 | 14 |