├── .github └── workflows │ └── main.yml ├── .gitignore ├── .hophoprc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── docs ├── build │ ├── 0.fda9587b.js │ └── bundle.df7bbcc0.js └── index.html ├── examples ├── Examples.md └── examples.scss ├── package-lock.json ├── package.json ├── src ├── ReactPlaceholder.tsx ├── index.ts ├── placeholders │ ├── MediaBlock.tsx │ ├── RectShape.tsx │ ├── RoundShape.tsx │ ├── TextBlock.tsx │ ├── TextRow.tsx │ └── index.ts ├── reactPlaceholder.scss └── utils.ts ├── styleguide.config.js ├── styleguide ├── index.html └── setup.tsx ├── tests ├── ReactPlaceholder.test.tsx ├── __snapshots__ │ ├── ReactPlaceholder.test.tsx.snap │ └── placeholders.test.tsx.snap ├── placeholders.test.tsx └── setup.js ├── tsconfig.json └── webpack.config.js /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | name: Test and Build 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions/setup-node@v1 16 | - run: npm ci 17 | - run: npm run preversion 18 | - run: npm run prepublishOnly 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | *.log 4 | lib 5 | .eslintcache 6 | -------------------------------------------------------------------------------- /.hophoprc: -------------------------------------------------------------------------------- 1 | toggl: n 2 | branchPrefix: n 3 | branchSuffix: y -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | 4 | 5 | ## [v4.1.0](https://github.com/buildo/react-placeholder/tree/v4.1.0) (2021-02-05) 6 | [Full Changelog](https://github.com/buildo/react-placeholder/compare/v4.0.3...v4.1.0) 7 | 8 | #### New features: 9 | 10 | - Problem with React 17 and npm version 7.5.1 on mac osx [#95](https://github.com/buildo/react-placeholder/issues/95) 11 | - [Question] CSS Import [#94](https://github.com/buildo/react-placeholder/issues/94) 12 | - type CustomPlaceholderProps showLoadingAnimation is undefined instead of boolean [#90](https://github.com/buildo/react-placeholder/issues/90) 13 | - v3.0.2 TypeScript error about missing properties [#82](https://github.com/buildo/react-placeholder/issues/82) 14 | - Do `children` and `ready` really have to be required props? [#79](https://github.com/buildo/react-placeholder/issues/79) 15 | 16 | ## [v4.0.3](https://github.com/buildo/react-placeholder/tree/v4.0.3) (2020-06-11) 17 | [Full Changelog](https://github.com/buildo/react-placeholder/compare/v4.0.2...v4.0.3) 18 | 19 | ## [v4.0.2](https://github.com/buildo/react-placeholder/tree/v4.0.2) (2020-06-01) 20 | [Full Changelog](https://github.com/buildo/react-placeholder/compare/v4.0.1...v4.0.2) 21 | 22 | ## [v4.0.1](https://github.com/buildo/react-placeholder/tree/v4.0.1) (2020-03-13) 23 | [Full Changelog](https://github.com/buildo/react-placeholder/compare/v4.0.0...v4.0.1) 24 | 25 | #### New features: 26 | 27 | - TextRow/TextBlock ignores lineSpacing is equal to 0 [#86](https://github.com/buildo/react-placeholder/issues/86) 28 | - Is this package still maintained? [#84](https://github.com/buildo/react-placeholder/issues/84) 29 | 30 | ## [v4.0.0](https://github.com/buildo/react-placeholder/tree/v4.0.0) (2020-02-29) 31 | [Full Changelog](https://github.com/buildo/react-placeholder/compare/v3.0.2...v4.0.0) 32 | 33 | #### Breaking: 34 | 35 | - Warning: componentWillReceiveProps has been renamed, and is not recommended for use. [#83](https://github.com/buildo/react-placeholder/issues/83) 36 | 37 | ## [v3.0.2](https://github.com/buildo/react-placeholder/tree/v3.0.2) (2019-02-21) 38 | [Full Changelog](https://github.com/buildo/react-placeholder/compare/v3.0.1...v3.0.2) 39 | 40 | #### New features: 41 | 42 | - ReactPlaceholder text component doesn't override the width property [#78](https://github.com/buildo/react-placeholder/issues/78) 43 | - [Typings] Can't assign Element to customPlaceholder [#75](https://github.com/buildo/react-placeholder/issues/75) 44 | 45 | ## [v3.0.1](https://github.com/buildo/react-placeholder/tree/v3.0.1) (2018-05-08) 46 | [Full Changelog](https://github.com/buildo/react-placeholder/compare/v3.0.0...v3.0.1) 47 | 48 | #### Fixes (bugs & defects): 49 | 50 | - Removed CSS? [#72](https://github.com/buildo/react-placeholder/issues/72) 51 | 52 | ## [v3.0.0](https://github.com/buildo/react-placeholder/tree/v3.0.0) (2018-05-04) 53 | [Full Changelog](https://github.com/buildo/react-placeholder/compare/v2.0.0...v3.0.0) 54 | 55 | #### Breaking: 56 | 57 | - Refactor in TypeScript [#69](https://github.com/buildo/react-placeholder/issues/69) 58 | 59 | #### New features: 60 | 61 | - index.d.ts missing 'style' property [#67](https://github.com/buildo/react-placeholder/issues/67) 62 | 63 | ## [v2.0.0](https://github.com/buildo/react-placeholder/tree/v2.0.0) (2017-12-20) 64 | [Full Changelog](https://github.com/buildo/react-placeholder/compare/v1.0.10...v2.0.0) 65 | 66 | #### Breaking: 67 | 68 | - Array.apply not support ie8 in TextBlock even if I add the polyfill (again) [#65](https://github.com/buildo/react-placeholder/issues/65) 69 | 70 | ## [v1.0.10](https://github.com/buildo/react-placeholder/tree/v1.0.10) (2017-12-15) 71 | [Full Changelog](https://github.com/buildo/react-placeholder/compare/v1.0.9...v1.0.10) 72 | 73 | #### Fixes (bugs & defects): 74 | 75 | - Array.apply not support ie8 in TextBlock even if I'm add the polyfill [#62](https://github.com/buildo/react-placeholder/issues/62) 76 | 77 | #### New features: 78 | 79 | - TextBlock: make `widths` overridable [#59](https://github.com/buildo/react-placeholder/issues/59) 80 | 81 | ## [v1.0.9](https://github.com/buildo/react-placeholder/tree/v1.0.9) (2017-11-28) 82 | [Full Changelog](https://github.com/buildo/react-placeholder/compare/v1.0.8...v1.0.9) 83 | 84 | #### New features: 85 | 86 | - Bug with delay when the component is already mounted [#54](https://github.com/buildo/react-placeholder/issues/54) 87 | 88 | ## [v1.0.8](https://github.com/buildo/react-placeholder/tree/v1.0.8) (2017-10-25) 89 | [Full Changelog](https://github.com/buildo/react-placeholder/compare/v1.0.7...v1.0.8) 90 | 91 | #### New features: 92 | 93 | - Add "delay" prop to show the placeholder for a minimum time [#49](https://github.com/buildo/react-placeholder/issues/49) 94 | - React 16 warning message [#46](https://github.com/buildo/react-placeholder/issues/46) 95 | 96 | ## [v1.0.7](https://github.com/buildo/react-placeholder/tree/v1.0.7) (2017-10-16) 97 | [Full Changelog](https://github.com/buildo/react-placeholder/compare/v1.0.6...v1.0.7) 98 | 99 | #### New features: 100 | 101 | - error in ready [#44](https://github.com/buildo/react-placeholder/issues/44) 102 | 103 | ## [v1.0.6](https://github.com/buildo/react-placeholder/tree/v1.0.6) (2017-08-03) 104 | [Full Changelog](https://github.com/buildo/react-placeholder/compare/v1.0.5...v1.0.6) 105 | 106 | #### Fixes (bugs & defects): 107 | 108 | - @types/react > 33 complains [#41](https://github.com/buildo/react-placeholder/issues/41) 109 | - Animations not working with custom placeholders. [#34](https://github.com/buildo/react-placeholder/issues/34) 110 | 111 | #### New features: 112 | 113 | - Action required: Greenkeeper could not be activated 🚨 [#39](https://github.com/buildo/react-placeholder/issues/39) 114 | - Action required: Greenkeeper could not be activated 🚨 [#38](https://github.com/buildo/react-placeholder/issues/38) 115 | 116 | ## [v1.0.5](https://github.com/buildo/react-placeholder/tree/v1.0.5) (2017-06-26) 117 | [Full Changelog](https://github.com/buildo/react-placeholder/compare/v1.0.4...v1.0.5) 118 | 119 | #### Fixes (bugs & defects): 120 | 121 | - Typo in CSS selector for react-shape animation [#33](https://github.com/buildo/react-placeholder/issues/33) 122 | 123 | ## [v1.0.4](https://github.com/buildo/react-placeholder/tree/v1.0.4) (2017-06-01) 124 | [Full Changelog](https://github.com/buildo/react-placeholder/compare/v1.0.3...v1.0.4) 125 | 126 | #### Fixes (bugs & defects): 127 | 128 | - Update typings to make lineSpacing prop optional [#32](https://github.com/buildo/react-placeholder/issues/32) 129 | 130 | ## [v1.0.3](https://github.com/buildo/react-placeholder/tree/v1.0.3) (2017-05-31) 131 | [Full Changelog](https://github.com/buildo/react-placeholder/compare/v1.0.2...v1.0.3) 132 | 133 | #### New features: 134 | 135 | - animation support [#27](https://github.com/buildo/react-placeholder/issues/27) 136 | 137 | #### Fixes (bugs & defects): 138 | 139 | - Rectangle placeholder without className sets class to undefined [#24](https://github.com/buildo/react-placeholder/issues/24) 140 | 141 | ## [v1.0.2](https://github.com/buildo/react-placeholder/tree/v1.0.2) (2017-04-25) 142 | [Full Changelog](https://github.com/buildo/react-placeholder/compare/v1.0.1...v1.0.2) 143 | 144 | #### New features: 145 | 146 | - Add React 15.5 compatibility [#22](https://github.com/buildo/react-placeholder/issues/22) 147 | 148 | ## [v1.0.1](https://github.com/buildo/react-placeholder/tree/v1.0.1) (2017-04-03) 149 | [Full Changelog](https://github.com/buildo/react-placeholder/compare/v1.0.0...v1.0.1) 150 | 151 | #### New features: 152 | 153 | - replace `lodash.omit` with ES6 destructuring [#21](https://github.com/buildo/react-placeholder/issues/21) 154 | 155 | ## [v1.0.0](https://github.com/buildo/react-placeholder/tree/v1.0.0) (2017-03-23) 156 | [Full Changelog](https://github.com/buildo/react-placeholder/compare/v0.0.3...v1.0.0) 157 | 158 | #### Fixes (bugs & defects): 159 | 160 | - lodash and classnames should be deps, not devDeps [#13](https://github.com/buildo/react-placeholder/issues/13) 161 | 162 | #### Breaking: 163 | 164 | - Use relative heights [#10](https://github.com/buildo/react-placeholder/issues/10) 165 | 166 | ## [v0.0.3](https://github.com/buildo/react-placeholder/tree/v0.0.3) (2016-12-29) 167 | [Full Changelog](https://github.com/buildo/react-placeholder/compare/v0.0.2...v0.0.3) 168 | 169 | #### Fixes (bugs & defects): 170 | 171 | - package is currently broken [#6](https://github.com/buildo/react-placeholder/issues/6) 172 | 173 | ## [v0.0.2](https://github.com/buildo/react-placeholder/tree/v0.0.2) (2016-11-08) 174 | 175 | 176 | #### New features: 177 | 178 | - Replace react.createClass with ES6 class [#3](https://github.com/buildo/react-placeholder/issues/3) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 buildo 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. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Placeholder 2 | A React component to easily replicate your page with nice placeholders while the content is loading. 3 | You can use a placeholder from the default set, or pass your own! 4 | 5 | ![image](https://cloud.githubusercontent.com/assets/691940/24140211/78406120-0e1f-11e7-9738-af2b2434c50e.png) 6 | 7 | ```jsx 8 | import ReactPlaceholder from 'react-placeholder'; 9 | import "react-placeholder/lib/reactPlaceholder.css"; 10 | 11 | React.renderComponent( 12 |
13 | 14 | 15 | 16 |
, 17 | document.body); 18 | ``` 19 | 20 | [**Live Demo**](http://buildo.github.io/react-placeholder/#!/ReactPlaceholder) 21 | 22 | ### Install 23 | ``` 24 | npm install --save react-placeholder 25 | ``` 26 | 27 | ### Props 28 | 29 | ```tsx 30 | children: ReactNode; 31 | ready: boolean; 32 | delay?: number; 33 | firstLaunchOnly?: boolean; 34 | showLoadingAnimation?: boolean; 35 | type?: 'text' | 'media' | 'textRow' | 'rect' | 'round'; 36 | rows?: number; 37 | color?: string; 38 | customPlaceholder?: ReactElement; 39 | className?: string; 40 | style?: CSSProperties; 41 | ``` 42 | 43 | The default props will render a `text` placeholder with `3` rows and the color `#CDCDCD`. 44 | 45 | ### Customization 46 | If the built-in set of placeholders is not enough, you can pass you own through the prop **"customPlaceholder"** 47 | 48 | ```jsx 49 | }> 50 | 51 | 52 | ``` 53 | 54 | You can also import the built-in placeholders directly. This might be useful to use them to create your own customized placeholder: 55 | 56 | ```jsx 57 | import {TextBlock, MediaBlock, TextRow, RectShape, RoundShape} from 'react-placeholder/lib/placeholders'; 58 | 59 | const awesomePlaceholder = ( 60 |
61 | 62 | 63 |
64 | ); 65 | 66 | 67 | 68 | 69 | ``` 70 | 71 | ### Delay 72 | You can pass an optional `delay` prop which specifies the time (in milliseconds) `react-placeholder` should wait before displaying the placeholder element. This is useful if you want to show a placeholder for slower connections while avoiding a brief "flash" on faster connections. 73 | 74 | Note that this delay will __not__ affect the initial render, only subsequent "ready" state changes. Setting the `firstLaunchOnly` prop to `true` will also disable this feature. 75 | 76 | ### Animation 77 | `react-placeholder` already comes with one default pulse animation to better tell the user that the page is loading. 78 | The animation is defined in a separate CSS file so, in order to enable it, you should import that style in your project like this: 79 | 80 | ```js 81 | import 'react-placeholder/lib/reactPlaceholder.css'; 82 | ``` 83 | 84 | Once you've done this, you can simply pass the boolean prop `showLoadingAnimation` to tell `ReactPlaceholder` to animate itself: 85 | 86 | ```jsx 87 | import 'react-placeholder/lib/reactPlaceholder.css'; 88 | 89 | 90 |

This is a Test.

91 |
92 | ``` 93 | 94 | ### Style 95 | you can style the placeholder by passing **```className```** or **```style```** or by using the built-in classes: 96 | 97 | *"text-block", "media-block", "text-row", "rect-shape", "round-shape".* 98 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/Examples.md: -------------------------------------------------------------------------------- 1 | ## Examples 2 | 3 | ### ReactPlaceholder comes with various built-in types 4 | 5 | `type='text'` 6 | ```js 7 | 8 | 9 | 10 | ``` 11 | 12 | `type='media'` 13 | ```js 14 | 15 | 16 | 17 | ``` 18 | 19 | `type='textRow'` 20 | ```js 21 | 22 | 23 | 24 | ``` 25 | 26 | `type='rect'` 27 | ```js 28 | 29 | 30 | 31 | ``` 32 | 33 | `type='round'` 34 | ```js 35 | 36 | 37 | 38 | ``` 39 | 40 | ### Use a custom placeholder 41 | You can use your own custom placeholder: 42 | 43 | ```js 44 | } ready={false}> 45 | 46 | 47 | ``` 48 | 49 | ### Animation 50 | Pass `showLoadingAnimation={true}` to enable the default loading animation: 51 | 52 | ```js 53 | 54 | 55 | 56 | ``` 57 | 58 | ### firstLaunchOnly 59 | With `firstLaunchOnly={true}` the placeholder will be rendered only the first time `ready` is `false` 60 | 61 | ```js 62 | initialState = { ready: false } 63 | 64 | function toggleReady() { 65 | setState({ ready: !state.ready }) 66 | } 67 | 68 |
69 | 70 | 71 | 72 | 73 | 74 |
75 | ``` 76 | 77 | ### delay 78 | You can delay the switch from "real component" to the placeholder with the prop `delay` 79 | 80 | ```js 81 | initialState = { ready: true } 82 | 83 | function toggleReady() { 84 | setState({ ready: !state.ready }) 85 | } 86 | 87 |
88 | 89 | 90 | 91 | 92 | 93 |
94 | ``` 95 | 96 | ### Use the built-in placeholders directly 97 | 98 | TextBlock 99 | ```js 100 | 101 | ``` 102 | 103 | MediaBlock 104 | ```js 105 | 106 | ``` 107 | 108 | TextRow 109 | ```js 110 | 111 | ``` 112 | 113 | RectShape 114 | ```js 115 | 116 | ``` 117 | 118 | RoundShape 119 | ```js 120 | 121 | ``` 122 | -------------------------------------------------------------------------------- /examples/examples.scss: -------------------------------------------------------------------------------- 1 | button { 2 | margin-bottom: 10px; 3 | } 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-placeholder", 3 | "version": "4.1.0", 4 | "description": "A React component to easily replicate your page with nice placeholders while the content is loading", 5 | "main": "lib", 6 | "scripts": { 7 | "test": "jest", 8 | "build": "rm -rf lib && mkdir lib && node-sass src -o lib && tsc", 9 | "preversion": "npm run typecheck && npm run test", 10 | "prepublishOnly": "npm run build", 11 | "start": "styleguidist server", 12 | "typecheck": "tsc --noEmit", 13 | "release-version": "smooth-release" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git@github.com:buildo/react-placeholder" 18 | }, 19 | "keywords": [ 20 | "react", 21 | "react-component", 22 | "placeholder", 23 | "filler", 24 | "loading", 25 | "paragraph" 26 | ], 27 | "author": "Francesco Cioria ", 28 | "license": "ISC", 29 | "bugs": { 30 | "url": "https://github.com/buildo/react-placeholder/issues" 31 | }, 32 | "files": [ 33 | "lib" 34 | ], 35 | "homepage": "https://github.com/buildo/react-placeholder", 36 | "typings": "lib", 37 | "dependencies": {}, 38 | "devDependencies": { 39 | "@testing-library/jest-dom": "^5.11.9", 40 | "@testing-library/react": "^11.2.5", 41 | "@types/enzyme": "^3.10.8", 42 | "@types/jest": "^26.0.20", 43 | "@types/lodash": "^4.14.168", 44 | "@types/node": "12.12.17", 45 | "@types/react": "^16.9.56", 46 | "babel-core": "^6.26.3", 47 | "babel-loader": "^7.1.5", 48 | "babel-preset-buildo": "^0.1.1", 49 | "css-loader": "^0.28.11", 50 | "enzyme": "^3.11.0", 51 | "enzyme-adapter-react-16": "^1.15.6", 52 | "file-loader": "^1.1.11", 53 | "jest": "^26.6.3", 54 | "lodash": "^4.17.20", 55 | "node-sass": "^4.14.1", 56 | "progress-bar-webpack-plugin": "^1.12.1", 57 | "raf": "^3.4.1", 58 | "raw-loader": "^0.5.1", 59 | "react": "^16.8.0", 60 | "react-docgen-typescript": "^1.16.1", 61 | "react-dom": "^16.8.0", 62 | "react-styleguidist": "^6.0.33", 63 | "react-test-renderer": "^16.8.0", 64 | "sass-loader": "^6.0.7", 65 | "smooth-release": "^8.0.9", 66 | "ts-jest": "^26.5.0", 67 | "ts-loader": "^3.5.0", 68 | "typescript": "~3.9.7", 69 | "webpack": "3.5.5" 70 | }, 71 | "peerDependencies": { 72 | "react": "^16.8.0 || ^17" 73 | }, 74 | "jest": { 75 | "testURL": "http://localhost", 76 | "setupFiles": [ 77 | "raf/polyfill", 78 | "/tests/setup.js" 79 | ], 80 | "transform": { 81 | "^.+\\.tsx?$": "ts-jest" 82 | }, 83 | "testRegex": "(.*[.](test))[.](tsx?)$", 84 | "moduleFileExtensions": [ 85 | "js", 86 | "ts", 87 | "tsx" 88 | ] 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/ReactPlaceholder.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as placeholders from './placeholders'; 3 | import { joinClassNames } from './utils'; 4 | 5 | type CommonProps = { 6 | children?: React.ReactNode; 7 | /** pass `true` when the content is ready and `false` when it's loading */ 8 | ready: boolean; 9 | /** delay in millis to wait when passing from ready to NOT ready */ 10 | delay?: number; 11 | /** if true, the placeholder will never be rendered again once ready becomes true, even if it becomes false again */ 12 | firstLaunchOnly?: boolean; 13 | className?: string; 14 | style?: React.CSSProperties; 15 | }; 16 | 17 | type PlaceholderProps = CommonProps & { 18 | // we have a default color, so we can set this as optional 19 | color?: string; 20 | // we have a default number of rows, so we can set this as optional 21 | rows?: number; 22 | showLoadingAnimation?: boolean; 23 | customPlaceholder?: undefined; 24 | }; 25 | 26 | type CustomPlaceholderProps = CommonProps & { 27 | /** pass any renderable content to be used as placeholder instead of the built-in ones */ 28 | customPlaceholder?: React.ReactElement<{ [k: string]: any }> | null; 29 | type?: undefined; 30 | rows?: undefined; 31 | color?: undefined; 32 | showLoadingAnimation?: boolean; 33 | }; 34 | 35 | type MediaPlaceholderProps = PlaceholderProps & 36 | Omit< 37 | React.ComponentProps, 38 | 'color' | 'rows' | 'children' 39 | > & { 40 | type: 'media'; 41 | }; 42 | 43 | type RectPlaceholderProps = PlaceholderProps & 44 | Omit, 'children'> & { 45 | type: 'rect'; 46 | }; 47 | 48 | type RoundPlaceholderProps = PlaceholderProps & 49 | Omit< 50 | React.ComponentProps, 51 | 'color' | 'children' 52 | > & { 53 | type: 'round'; 54 | }; 55 | 56 | type TextPlaceholderProps = PlaceholderProps & 57 | Omit< 58 | React.ComponentProps, 59 | 'color' | 'rows' | 'children' 60 | > & { 61 | type: 'text'; 62 | }; 63 | 64 | type TextRowPlaceholderProps = PlaceholderProps & 65 | Omit< 66 | React.ComponentProps, 67 | 'color' | 'children' 68 | > & { 69 | type: 'textRow'; 70 | }; 71 | 72 | export type Props = 73 | | MediaPlaceholderProps 74 | | RectPlaceholderProps 75 | | RoundPlaceholderProps 76 | | TextRowPlaceholderProps 77 | | TextPlaceholderProps 78 | | CustomPlaceholderProps; 79 | 80 | const ReactPlaceholder: React.FC = ({ 81 | delay = 0, 82 | type = 'text', 83 | color = '#CDCDCD', 84 | rows = 3, 85 | ready: readyProp, 86 | firstLaunchOnly, 87 | children, 88 | className, 89 | showLoadingAnimation, 90 | customPlaceholder, 91 | ...rest 92 | }) => { 93 | const [ready, setReady] = React.useState(readyProp); 94 | const timeout = React.useRef(null); 95 | 96 | const getFiller = (): React.ReactElement | null => { 97 | const classes = showLoadingAnimation 98 | ? joinClassNames('show-loading-animation', className) 99 | : className; 100 | 101 | if (customPlaceholder && React.isValidElement(customPlaceholder)) { 102 | const mergedCustomClasses = joinClassNames( 103 | customPlaceholder.props.className, 104 | classes 105 | ); 106 | return React.cloneElement(customPlaceholder, { 107 | className: mergedCustomClasses 108 | }); 109 | } else if (customPlaceholder) { 110 | return customPlaceholder; 111 | } 112 | 113 | const Placeholder = placeholders[type]; 114 | 115 | return ( 116 | 117 | ); 118 | }; 119 | 120 | React.useEffect(() => { 121 | if (!firstLaunchOnly && ready && !readyProp) { 122 | if (delay && delay > 0) { 123 | timeout.current = window.setTimeout(() => { 124 | setReady(false); 125 | }, delay); 126 | } else { 127 | setReady(false); 128 | } 129 | } else if (readyProp) { 130 | if (timeout.current) { 131 | window.clearTimeout(timeout.current); 132 | } 133 | 134 | if (!ready) { 135 | setReady(true); 136 | } 137 | } 138 | }, [firstLaunchOnly, ready, readyProp, delay]); 139 | 140 | // clear the timeout when the component unmounts 141 | React.useEffect( 142 | () => () => { 143 | if (timeout.current) { 144 | window.clearTimeout(timeout.current); 145 | } 146 | }, 147 | [] 148 | ); 149 | 150 | // Casting - workaround for DefinitelyTyped/react issue with 151 | // FunctionComponents returning ReactElement, where they should be able to 152 | // return ReactNode. Casting also doesn't introduce another layer in the 153 | // component tree like another `<>children` workaround does. 154 | // 155 | // See https://github.com/DefinitelyTyped/DefinitelyTyped/issues/33006 156 | // and https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18051 157 | return ready ? children as React.ReactElement : getFiller(); 158 | }; 159 | 160 | export default ReactPlaceholder; 161 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import ReactPlaceholder from './ReactPlaceholder'; 2 | export default ReactPlaceholder; 3 | -------------------------------------------------------------------------------- /src/placeholders/MediaBlock.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import TextBlock from './TextBlock'; 3 | import RoundShape from './RoundShape'; 4 | import { joinClassNames } from '../utils'; 5 | 6 | export type Props = { 7 | rows: number; 8 | color: string; 9 | style?: React.CSSProperties; 10 | className?: string; 11 | }; 12 | 13 | const defaultStyles = { 14 | display: 'flex' 15 | }; 16 | 17 | const MediaBlock: React.FC = ({ className, style, color, rows }) => { 18 | return ( 19 |
23 | 27 | 28 |
29 | ); 30 | }; 31 | 32 | export default MediaBlock; 33 | -------------------------------------------------------------------------------- /src/placeholders/RectShape.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { joinClassNames } from '../utils'; 3 | 4 | export type Props = { 5 | color?: string; 6 | className?: string; 7 | style?: React.CSSProperties; 8 | }; 9 | 10 | const RectShape: React.FC = ({ className, style, color }) => { 11 | const defaultStyle = { 12 | backgroundColor: color, 13 | width: '100%', 14 | height: '100%', 15 | marginRight: 10 16 | }; 17 | 18 | return ( 19 |
23 | ); 24 | }; 25 | 26 | export default RectShape; 27 | -------------------------------------------------------------------------------- /src/placeholders/RoundShape.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { joinClassNames } from '../utils'; 3 | 4 | export type Props = { 5 | color: string; 6 | style?: React.CSSProperties; 7 | className?: string; 8 | }; 9 | 10 | const RoundShape: React.FC = ({ className, style, color }) => { 11 | const defaultStyles = { 12 | backgroundColor: color, 13 | borderRadius: '500rem', 14 | width: '100%', 15 | height: '100%' 16 | }; 17 | 18 | return ( 19 |
23 | ); 24 | }; 25 | 26 | export default RoundShape; 27 | -------------------------------------------------------------------------------- /src/placeholders/TextBlock.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import TextRow from './TextRow'; 3 | import { joinClassNames } from '../utils'; 4 | 5 | export type Props = { 6 | rows: number; 7 | color: string; 8 | lineSpacing?: string | number; 9 | widths?: number[]; 10 | style?: React.CSSProperties; 11 | className?: string; 12 | }; 13 | 14 | const defaultStyles = { 15 | width: '100%' 16 | }; 17 | 18 | const defaultWidths = [97, 100, 94, 90, 98, 95, 98, 40]; 19 | 20 | const TextBlock: React.FC = ({ 21 | rows, 22 | lineSpacing, 23 | color, 24 | style, 25 | className, 26 | widths = defaultWidths 27 | }) => { 28 | const getRowStyle = (i: number) => { 29 | return { 30 | maxHeight: `${100 / (rows * 2 - 1)}%`, 31 | width: `${widths[(i + widths.length) % widths.length]}%` 32 | }; 33 | }; 34 | 35 | return ( 36 |
40 | {Array.apply(null, Array(rows)).map((_, i) => ( 41 | 47 | ))} 48 |
49 | ); 50 | }; 51 | 52 | export default TextBlock; 53 | -------------------------------------------------------------------------------- /src/placeholders/TextRow.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { joinClassNames } from '../utils'; 3 | 4 | export type Props = { 5 | maxHeight?: string | number; 6 | className?: string; 7 | color: string; 8 | style?: React.CSSProperties; 9 | lineSpacing?: string | number; 10 | }; 11 | 12 | const TextRow: React.FC = ({ 13 | className, 14 | maxHeight, 15 | color, 16 | lineSpacing= '0.7em', 17 | style 18 | }) => { 19 | const defaultStyles = { 20 | maxHeight, 21 | width: '100%', 22 | height: '1em', 23 | backgroundColor: color, 24 | marginTop: lineSpacing 25 | }; 26 | 27 | return ( 28 |
32 | ); 33 | }; 34 | 35 | export default TextRow; 36 | -------------------------------------------------------------------------------- /src/placeholders/index.ts: -------------------------------------------------------------------------------- 1 | import TextRow from './TextRow'; 2 | import RoundShape from './RoundShape'; 3 | import RectShape from './RectShape'; 4 | import TextBlock from './TextBlock'; 5 | import MediaBlock from './MediaBlock'; 6 | 7 | export { 8 | TextRow, 9 | RoundShape, 10 | RectShape, 11 | TextBlock, 12 | MediaBlock 13 | } 14 | 15 | export const textRow = TextRow; 16 | export const round = RoundShape; 17 | export const rect = RectShape; 18 | export const text = TextBlock; 19 | export const media = MediaBlock; 20 | -------------------------------------------------------------------------------- /src/reactPlaceholder.scss: -------------------------------------------------------------------------------- 1 | .show-loading-animation { 2 | &.rect-shape, 3 | &.round-shape, 4 | &.text-row, 5 | .rect-shape, 6 | .round-shape, 7 | .text-row { 8 | animation: react-placeholder-pulse 1.5s infinite; 9 | } 10 | } 11 | 12 | 13 | @keyframes react-placeholder-pulse { 14 | 0% { 15 | opacity: .6; 16 | } 17 | 50% { 18 | opacity: 1; 19 | } 20 | 100% { 21 | opacity: .6; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | export const joinClassNames = (...classNames: (string | undefined)[]) => 2 | classNames.filter(c => c).join(' '); 3 | -------------------------------------------------------------------------------- /styleguide.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | // build 5 | serverPort: 8080, 6 | styleguideDir: 'docs', // target of the `build` task 7 | 8 | require: [ 9 | // "global" setup + sass imports 10 | path.resolve(__dirname, 'styleguide/setup.tsx') 11 | ], 12 | 13 | // content 14 | title: 'react-placeholder', 15 | // assetsDir: 'styleguide/assets', 16 | template: 'styleguide/index.html', 17 | propsParser: require('react-docgen-typescript').parse, // detect docs using TS information 18 | sections: [{ 19 | name: 'ReactPlaceholder', 20 | components: () => [ 21 | path.resolve(__dirname, 'src/ReactPlaceholder.tsx') 22 | ] 23 | }], 24 | showCode: true, 25 | showUsage: false, // show props by default 26 | getExampleFilename() { 27 | return path.resolve(__dirname, 'examples/Examples.md'); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /styleguide/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /styleguide/setup.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { 3 | TextRow, 4 | RoundShape, 5 | RectShape, 6 | TextBlock, 7 | MediaBlock 8 | } from '../src/placeholders'; 9 | 10 | import '../src/reactPlaceholder.scss'; 11 | import '../examples/examples.scss'; 12 | 13 | (global as any).CustomPlaceholder = () => ( 14 |
15 | I'm a custom placeholder! 16 |
17 | ); 18 | 19 | (global as any).RealComponent = () => ( 20 |
21 | I'm the real component! 22 |
23 | ); 24 | 25 | (global as any).TextRow = TextRow; 26 | (global as any).RoundShape = RoundShape; 27 | (global as any).RectShape = RectShape; 28 | (global as any).TextBlock = TextBlock; 29 | (global as any).MediaBlock = MediaBlock; 30 | -------------------------------------------------------------------------------- /tests/ReactPlaceholder.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { execSync } from 'child_process'; 3 | import * as fs from 'fs'; 4 | import * as path from 'path'; 5 | import { flatten } from 'lodash'; 6 | import ReactPlaceholder from '../src/ReactPlaceholder'; 7 | import { shallow } from 'enzyme'; 8 | import { act, render } from '@testing-library/react'; 9 | import '@testing-library/jest-dom/extend-expect'; 10 | 11 | jest.useFakeTimers(); 12 | 13 | describe('ReactPlaceholder', () => { 14 | it('renders the text placeholder with 3 rows as the default placeholder', () => { 15 | const content =
Some content still loading...
; 16 | const tree = shallow( 17 | 18 | {content} 19 | 20 | ); 21 | expect(tree.contains(content)).toBe(false); 22 | expect(tree.getElements()).toMatchSnapshot(); 23 | }); 24 | 25 | it('renders the text placeholder when the content is not ready', () => { 26 | const content =
Some content still loading...
; 27 | const tree = shallow( 28 | 29 | {content} 30 | 31 | ); 32 | expect(tree.contains(content)).toBe(false); 33 | expect(tree.getElements()).toMatchSnapshot(); 34 | }); 35 | 36 | it('renders the text placeholder with the lineSpacing set', () => { 37 | const content =
Some content still loading...
; 38 | const tree = shallow( 39 | 40 | {content} 41 | 42 | ); 43 | expect(tree.contains(content)).toBe(false); 44 | expect(tree.find('TextBlock').prop('lineSpacing')).toBe(3); 45 | expect(tree.getElements()).toMatchSnapshot(); 46 | }); 47 | 48 | it('renders the media placeholder when the content is not ready', () => { 49 | const content =
Some content still loading...
; 50 | const tree = shallow( 51 | 52 | {content} 53 | 54 | ); 55 | expect(tree.contains(content)).toBe(false); 56 | expect(tree.getElements()).toMatchSnapshot(); 57 | }); 58 | 59 | it('renders the textRow placeholder when the content is not ready', () => { 60 | const content =
Some content still loading...
; 61 | const tree = shallow( 62 | 63 | {content} 64 | 65 | ); 66 | expect(tree.contains(content)).toBe(false); 67 | expect(tree.getElements()).toMatchSnapshot(); 68 | }); 69 | 70 | it('renders the rect placeholder when the content is not ready', () => { 71 | const content =
Some content still loading...
; 72 | const tree = shallow( 73 | 74 | {content} 75 | 76 | ); 77 | expect(tree.contains(content)).toBe(false); 78 | expect(tree.getElements()).toMatchSnapshot(); 79 | }); 80 | 81 | it('renders the round placeholder when the content is not ready', () => { 82 | const content =
Some content still loading...
; 83 | const tree = shallow( 84 | 85 | {content} 86 | 87 | ); 88 | expect(tree.contains(content)).toBe(false); 89 | expect(tree.getElements()).toMatchSnapshot(); 90 | }); 91 | 92 | it('renders the round placeholder with animation when the content is not ready', () => { 93 | const content =
Some content still loading...
; 94 | const tree = shallow( 95 | 96 | {content} 97 | 98 | ); 99 | expect(tree.contains(content)).toBe(false); 100 | expect(tree.getElements()).toMatchSnapshot(); 101 | }); 102 | 103 | it('renders a custom placeholder when the content is not ready', () => { 104 | const content =
Some ready content
; 105 | const customPlaceholder =
Custom Placeholder
; 106 | const tree = shallow( 107 | 108 | {content} 109 | 110 | ); 111 | expect(tree.contains(content)).toBe(false); 112 | // an empty className is injected to the customer placeholder element, 113 | // so we'll check the children for now 114 | expect(tree.text()).toBe(customPlaceholder.props.children); 115 | expect(tree.getElements()).toMatchSnapshot(); 116 | }); 117 | 118 | it('renders the placeholder only after the specified delay', () => { 119 | const content =
Some content still loading...
; 120 | const { container, queryByText, rerender } = render( 121 | 122 | {content} 123 | 124 | ); 125 | expect(queryByText('Some content still loading...')).toBeInTheDocument(); 126 | rerender( 127 | {content} 128 | ); 129 | expect(queryByText('Some content still loading...')).toBeInTheDocument(); 130 | act(() => { 131 | jest.runAllTimers(); 132 | }); 133 | expect(queryByText('Some content still loading...')).not.toBeInTheDocument(); 134 | expect(container.firstChild).toMatchSnapshot(); 135 | }); 136 | 137 | it("renders the content when it's ready", () => { 138 | const content =
Some ready content
; 139 | const tree = shallow( 140 | 141 | {content} 142 | 143 | ); 144 | expect(tree.contains(content)).toBe(true); 145 | expect(tree.getElements()).toMatchSnapshot(); 146 | }); 147 | 148 | it("renders content when it's ready, then a placeholder when it's not ready, and finally content again when it's ready", () => { 149 | const content =
Some ready content
; 150 | const { queryByText, container, rerender } = render( 151 | 152 | {content} 153 | 154 | ); 155 | expect(queryByText('Some ready content')).toBeInTheDocument(); 156 | expect(container.firstChild).toMatchSnapshot(); 157 | 158 | rerender( 159 | {content} 160 | ); 161 | 162 | expect(queryByText('Some ready content')).not.toBeInTheDocument(); 163 | expect(container.firstChild).toMatchSnapshot(); 164 | 165 | rerender( 166 | {content} 167 | ); 168 | 169 | expect(queryByText('Some ready content')).toBeInTheDocument(); 170 | expect(container.firstChild).toMatchSnapshot(); 171 | }); 172 | 173 | it('renders content when firstLaunchOnly is true and ready changes to true, and keeps it rendered when ready changes to false', () => { 174 | const content =
Some ready content
; 175 | const { queryByText, container, rerender } = render( 176 | 177 | {content} 178 | 179 | ); 180 | expect(queryByText('Some ready content')).not.toBeInTheDocument(); 181 | expect(container.firstChild).toMatchSnapshot(); 182 | 183 | rerender( 184 | {content} 185 | ); 186 | 187 | expect(queryByText('Some ready content')).toBeInTheDocument(); 188 | expect(container.firstChild).toMatchSnapshot(); 189 | 190 | rerender( 191 | {content} 192 | ); 193 | 194 | // content is still rendered 195 | expect(queryByText('Some ready content')).toBeInTheDocument(); 196 | expect(container.firstChild).toMatchSnapshot(); 197 | }); 198 | }); 199 | 200 | describe('build', () => { 201 | it('build script generates every needed file', () => { 202 | execSync('npm run build', { stdio: [] }); 203 | 204 | const libPath = path.resolve(__dirname, '../lib'); 205 | 206 | const libFiles = fs.readdirSync(libPath); 207 | 208 | const files = flatten( 209 | libFiles.map(fileInRoot => { 210 | const filePath = path.resolve(libPath, fileInRoot); 211 | if (fs.lstatSync(filePath).isDirectory()) { 212 | return fs 213 | .readdirSync(filePath) 214 | .map(fileInFolder => `${fileInRoot}/${fileInFolder}`); 215 | } 216 | return fileInRoot; 217 | }) 218 | ); 219 | 220 | expect(files).toMatchSnapshot(); 221 | }); 222 | }); 223 | -------------------------------------------------------------------------------- /tests/__snapshots__/ReactPlaceholder.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`ReactPlaceholder renders a custom placeholder when the content is not ready 1`] = ` 4 | Array [ 5 |
8 | Custom Placeholder 9 |
, 10 | ] 11 | `; 12 | 13 | exports[`ReactPlaceholder renders content when firstLaunchOnly is true and ready changes to true, and keeps it rendered when ready changes to false 1`] = ` 14 |
18 | `; 19 | 20 | exports[`ReactPlaceholder renders content when firstLaunchOnly is true and ready changes to true, and keeps it rendered when ready changes to false 2`] = ` 21 |
22 | Some ready content 23 |
24 | `; 25 | 26 | exports[`ReactPlaceholder renders content when firstLaunchOnly is true and ready changes to true, and keeps it rendered when ready changes to false 3`] = ` 27 |
28 | Some ready content 29 |
30 | `; 31 | 32 | exports[`ReactPlaceholder renders content when it's ready, then a placeholder when it's not ready, and finally content again when it's ready 1`] = ` 33 |
34 | Some ready content 35 |
36 | `; 37 | 38 | exports[`ReactPlaceholder renders content when it's ready, then a placeholder when it's not ready, and finally content again when it's ready 2`] = ` 39 |
43 |
47 |
51 |
52 | `; 53 | 54 | exports[`ReactPlaceholder renders content when it's ready, then a placeholder when it's not ready, and finally content again when it's ready 3`] = ` 55 |
56 | Some ready content 57 |
58 | `; 59 | 60 | exports[`ReactPlaceholder renders the content when it's ready 1`] = ` 61 | Array [ 62 |
63 | Some ready content 64 |
, 65 | ] 66 | `; 67 | 68 | exports[`ReactPlaceholder renders the media placeholder when the content is not ready 1`] = ` 69 | Array [ 70 | , 74 | ] 75 | `; 76 | 77 | exports[`ReactPlaceholder renders the placeholder only after the specified delay 1`] = ` 78 |
82 |
86 |
90 |
91 | `; 92 | 93 | exports[`ReactPlaceholder renders the rect placeholder when the content is not ready 1`] = ` 94 | Array [ 95 | , 99 | ] 100 | `; 101 | 102 | exports[`ReactPlaceholder renders the round placeholder when the content is not ready 1`] = ` 103 | Array [ 104 | , 108 | ] 109 | `; 110 | 111 | exports[`ReactPlaceholder renders the round placeholder with animation when the content is not ready 1`] = ` 112 | Array [ 113 | , 118 | ] 119 | `; 120 | 121 | exports[`ReactPlaceholder renders the text placeholder when the content is not ready 1`] = ` 122 | Array [ 123 | , 127 | ] 128 | `; 129 | 130 | exports[`ReactPlaceholder renders the text placeholder with 3 rows as the default placeholder 1`] = ` 131 | Array [ 132 | , 136 | ] 137 | `; 138 | 139 | exports[`ReactPlaceholder renders the text placeholder with the lineSpacing set 1`] = ` 140 | Array [ 141 | , 146 | ] 147 | `; 148 | 149 | exports[`ReactPlaceholder renders the textRow placeholder when the content is not ready 1`] = ` 150 | Array [ 151 | , 155 | ] 156 | `; 157 | 158 | exports[`build build script generates every needed file 1`] = ` 159 | Array [ 160 | "ReactPlaceholder.d.ts", 161 | "ReactPlaceholder.js", 162 | "index.d.ts", 163 | "index.js", 164 | "placeholders/MediaBlock.d.ts", 165 | "placeholders/MediaBlock.js", 166 | "placeholders/RectShape.d.ts", 167 | "placeholders/RectShape.js", 168 | "placeholders/RoundShape.d.ts", 169 | "placeholders/RoundShape.js", 170 | "placeholders/TextBlock.d.ts", 171 | "placeholders/TextBlock.js", 172 | "placeholders/TextRow.d.ts", 173 | "placeholders/TextRow.js", 174 | "placeholders/index.d.ts", 175 | "placeholders/index.js", 176 | "reactPlaceholder.css", 177 | "utils.d.ts", 178 | "utils.js", 179 | ] 180 | `; 181 | -------------------------------------------------------------------------------- /tests/__snapshots__/placeholders.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`placeholder MediaBlock renders a MediaBlock 1`] = ` 4 | Array [ 5 |
13 | 24 | 28 |
, 29 | ] 30 | `; 31 | 32 | exports[`placeholder MediaBlock renders a MediaBlock with custom styles and classNames 1`] = ` 33 | Array [ 34 |
43 | 54 | 58 |
, 59 | ] 60 | `; 61 | 62 | exports[`placeholder RectShape renders a RectShape 1`] = ` 63 | Array [ 64 |
, 75 | ] 76 | `; 77 | 78 | exports[`placeholder RectShape renders a RectShape with custom styles, classNames, and color 1`] = ` 79 | Array [ 80 |
, 91 | ] 92 | `; 93 | 94 | exports[`placeholder RoundShape renders a RoundShape 1`] = ` 95 | Array [ 96 |
, 107 | ] 108 | `; 109 | 110 | exports[`placeholder RoundShape renders a RoundShape with custom styles and classNames 1`] = ` 111 | Array [ 112 |
, 123 | ] 124 | `; 125 | 126 | exports[`placeholder TextBlock renders a TextBlock 1`] = ` 127 | Array [ 128 |
136 | 146 | 155 |
, 156 | ] 157 | `; 158 | 159 | exports[`placeholder TextBlock renders a TextBlock with custom lineSpacing and widths 1`] = ` 160 | Array [ 161 |
169 | 179 | 189 |
, 190 | ] 191 | `; 192 | 193 | exports[`placeholder TextBlock renders a TextBlock with custom styles and classNames 1`] = ` 194 | Array [ 195 |
204 | 214 | 223 |
, 224 | ] 225 | `; 226 | 227 | exports[`placeholder TextRow renders a TextRow 1`] = ` 228 | Array [ 229 |
, 241 | ] 242 | `; 243 | 244 | exports[`placeholder TextRow renders a TextRow with a lineSpacing of 0 1`] = ` 245 | Array [ 246 |
, 258 | ] 259 | `; 260 | 261 | exports[`placeholder TextRow renders a TextRow with custom maxHeight and lineSpacing 1`] = ` 262 | Array [ 263 |
, 275 | ] 276 | `; 277 | 278 | exports[`placeholder TextRow renders a TextRow with custom styles and classNames, overriding set maxHeight and lineSpacing 1`] = ` 279 | Array [ 280 |
, 292 | ] 293 | `; 294 | -------------------------------------------------------------------------------- /tests/placeholders.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import { 4 | MediaBlock, 5 | RectShape, 6 | RoundShape, 7 | TextBlock, 8 | TextRow 9 | } from '../src/placeholders'; 10 | 11 | describe('placeholder', () => { 12 | describe('MediaBlock', () => { 13 | it('renders a MediaBlock', () => { 14 | const tree = shallow(); 15 | 16 | expect(tree.getElements()).toMatchSnapshot(); 17 | }); 18 | 19 | it('renders a MediaBlock with custom styles and classNames', () => { 20 | const tree = shallow( 21 | 30 | ); 31 | 32 | expect(tree.getElements()).toMatchSnapshot(); 33 | }); 34 | }); 35 | 36 | describe('RectShape', () => { 37 | it('renders a RectShape', () => { 38 | const tree = shallow(); 39 | 40 | expect(tree.getElements()).toMatchSnapshot(); 41 | }); 42 | 43 | it('renders a RectShape with custom styles, classNames, and color', () => { 44 | const tree = shallow( 45 | 56 | ); 57 | 58 | expect(tree.getElements()).toMatchSnapshot(); 59 | }); 60 | }); 61 | 62 | describe('RoundShape', () => { 63 | it('renders a RoundShape', () => { 64 | const tree = shallow(); 65 | 66 | expect(tree.getElements()).toMatchSnapshot(); 67 | }); 68 | 69 | it('renders a RoundShape with custom styles and classNames', () => { 70 | const tree = shallow( 71 | 81 | ); 82 | 83 | expect(tree.getElements()).toMatchSnapshot(); 84 | }); 85 | }); 86 | 87 | describe('TextBlock', () => { 88 | it('renders a TextBlock', () => { 89 | const tree = shallow(); 90 | 91 | expect(tree.getElements()).toMatchSnapshot(); 92 | }); 93 | 94 | it('renders a TextBlock with custom lineSpacing and widths', () => { 95 | const tree = shallow( 96 | 102 | ); 103 | 104 | expect(tree.getElements()).toMatchSnapshot(); 105 | }); 106 | 107 | it('renders a TextBlock with custom styles and classNames', () => { 108 | const tree = shallow( 109 | 118 | ); 119 | 120 | expect(tree.getElements()).toMatchSnapshot(); 121 | }); 122 | }); 123 | 124 | describe('TextRow', () => { 125 | it('renders a TextRow', () => { 126 | const tree = shallow(); 127 | 128 | expect(tree.getElements()).toMatchSnapshot(); 129 | }); 130 | 131 | it('renders a TextRow with custom maxHeight and lineSpacing', () => { 132 | const tree = shallow( 133 | 134 | ); 135 | 136 | expect(tree.getElements()).toMatchSnapshot(); 137 | }); 138 | 139 | it('renders a TextRow with a lineSpacing of 0', () => { 140 | const tree = shallow( 141 | 142 | ); 143 | 144 | expect(tree.prop('style').marginTop).toBe(0); 145 | expect(tree.getElements()).toMatchSnapshot(); 146 | }); 147 | 148 | it('renders a TextRow with custom styles and classNames, overriding set maxHeight and lineSpacing', () => { 149 | const tree = shallow( 150 | 163 | ); 164 | 165 | expect(tree.getElements()).toMatchSnapshot(); 166 | }); 167 | }); 168 | }); 169 | -------------------------------------------------------------------------------- /tests/setup.js: -------------------------------------------------------------------------------- 1 | global.requestAnimationFrame = (callback) => { 2 | setTimeout(callback, 0); 3 | }; 4 | 5 | const Enzyme = require('enzyme'); 6 | const Adapter = require('enzyme-adapter-react-16'); 7 | 8 | Enzyme.configure({ adapter: new Adapter() }); 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES3", 4 | "module": "commonjs", 5 | "declaration": true, 6 | "allowSyntheticDefaultImports": false, 7 | "allowJs": false, 8 | "experimentalDecorators": true, 9 | "outDir": "./lib", 10 | "baseUrl": ".", 11 | "suppressImplicitAnyIndexErrors": true, 12 | "noUnusedLocals": true, 13 | "noUnusedParameters": true, 14 | "sourceMap": false, 15 | "strict": true, 16 | "jsx": "react", 17 | "lib": [ 18 | "dom", "es6" 19 | ] 20 | }, 21 | "include": [ 22 | "src/*", 23 | "typings" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const ProgressBarPlugin = require('progress-bar-webpack-plugin'); 3 | 4 | module.exports = { 5 | resolve: { 6 | extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'] 7 | }, 8 | plugins: [ 9 | new ProgressBarPlugin() 10 | ], 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.[jt]sx?$/, 15 | include: [ 16 | path.resolve(__dirname, 'src'), 17 | path.resolve(__dirname, 'styleguide') 18 | ], 19 | use: [ 20 | { 21 | loader: 'babel-loader', 22 | options: { 23 | presets: [['buildo', { env: 'react' }]] 24 | } 25 | }, 26 | { 27 | loader: 'ts-loader', 28 | options: { 29 | configFile: require('path').resolve(__dirname, 'tsconfig.json') 30 | } 31 | } 32 | ] 33 | }, 34 | { 35 | test: /\.css$/, 36 | loader: [ 37 | 'style-loader', 38 | { loader: 'css-loader', options: { modules: true } } 39 | ] 40 | }, 41 | { 42 | test: /\.scss$/, 43 | use: ['style-loader', 'css-loader', 'sass-loader'] 44 | }, 45 | { 46 | test: /\.(png|jpg|gif)$/, 47 | loader: 'file-loader', 48 | options: { 49 | name: '[name].[ext]' 50 | } 51 | } 52 | ] 53 | } 54 | }; 55 | --------------------------------------------------------------------------------