├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github ├── pull_request_template.md └── workflows │ ├── stale.yml │ └── tests.yml ├── .gitignore ├── .travis.yml ├── CODEOWNERS ├── LICENSE.txt ├── README.md ├── __tests__ ├── .eslintrc.js ├── __snapshots__ │ ├── withWizard.spec.jsx.snap │ └── wizardShape.spec.jsx.snap ├── components │ ├── Step.spec.jsx │ ├── Steps.spec.jsx │ ├── WithWizard.spec.jsx │ ├── Wizard.spec.jsx │ └── __snapshots__ │ │ ├── Step.spec.jsx.snap │ │ ├── Steps.spec.jsx.snap │ │ └── WithWizard.spec.jsx.snap ├── withWizard.spec.jsx └── wizardShape.spec.jsx ├── docs └── index.html ├── examples ├── .eslintrc.js ├── add-animation │ ├── Navigation.js │ ├── exampleAnimation.css │ ├── index.html │ ├── index.js │ └── package.json ├── add-progress-bar │ ├── Navigation.js │ ├── exampleAnimation.css │ ├── index.html │ ├── index.js │ └── package.json ├── add-routing │ ├── Navigation.js │ ├── index.html │ ├── index.js │ └── package.json ├── skip-a-step │ ├── Navigation.js │ ├── exampleAnimation.css │ ├── index.html │ ├── index.js │ └── package.json └── start-simple │ ├── Navigation.js │ ├── index.html │ ├── index.js │ └── package.json ├── index.dev.js ├── index.html ├── package.json ├── src ├── components │ ├── Steps.js │ ├── Wizard.js │ └── createWizardComponent.js ├── index.js ├── utils │ └── renderCallback.js ├── withWizard.js └── wizardShape.js ├── webpack.config.babel.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env", "react", "stage-0"], 3 | "plugins": ["transform-do-expressions"], 4 | "env": { 5 | "development/client": { 6 | "presets": ["react-hmre"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | lib 2 | docs 3 | node_modules 4 | coverage 5 | !**/*.eslintrc.js 6 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['amex', 'prettier', 'prettier/react'], 3 | plugins: ['prettier'], 4 | rules: { 5 | 'prettier/prettier': [ 6 | 'error', 7 | { 8 | singleQuote: true, 9 | trailingComma: 'es5', 10 | bracketSpacing: true, 11 | jsxBracketSameLine: false, 12 | printWidth: 100, 13 | }, 14 | ], 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | 6 | ## Motivation and Context 7 | 8 | 9 | 10 | ## How Has This Been Tested? 11 | 12 | 13 | 14 | 15 | ## Types of Changes 16 | 17 | - [ ] Bug fix (non-breaking change which fixes an issue) 18 | - [ ] New feature (non-breaking change which adds functionality) 19 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 20 | - [ ] Documentation (adding or updating documentation) 21 | - [ ] Dependency update 22 | 23 | ## Checklist: 24 | 25 | 26 | - [ ] My change requires a change to the documentation and I have updated the documentation accordingly. 27 | - [ ] My changes are in sync with the code style of this project. 28 | - [ ] There aren't any other open Pull Requests for the same issue/update. 29 | - [ ] These changes should be applied to a maintenance branch. 30 | - [ ] This change requires cross browser checks. 31 | - [ ] This change adds additional environment variable requirements for react-albus users. 32 | - [ ] I have added the Apache 2.0 license header to any new files created. 33 | 34 | ## What is the Impact to Developers Using react-albus? 35 | 36 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Mark stale issues and pull requests 2 | 3 | on: 4 | schedule: 5 | - cron: "0 0 * * *" 6 | 7 | jobs: 8 | stale: 9 | 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/stale@v3 14 | with: 15 | repo-token: ${{ secrets.GITHUB_TOKEN }} 16 | stale-issue-message: 'This issue is stale because it has been open 30 days with no activity.' 17 | stale-pr-message: 'This pull request is stale because it has been open 30 days with no activity.' 18 | stale-issue-label: 'stale-issue' 19 | exempt-issue-labels: 'enhancement,documentation,good-first-issue,question' 20 | stale-pr-label: 'stale-pr' 21 | exempt-pr-labels: 'work-in-progress' 22 | days-before-stale: 30 23 | days-before-close: -1 24 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | tests: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | node: ['12.x'] 15 | name: Node ${{ matrix.node }} 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v2 19 | with: 20 | fetch-depth: 0 21 | persist-credentials: false 22 | ref: ${{ github.event.pull_request.head.sha }} 23 | - run: | 24 | git remote set-branches --add origin main 25 | git fetch 26 | - name: Setup Node 27 | uses: actions/setup-node@v1 28 | with: 29 | node-version: ${{ matrix.node }} 30 | - name: Install Dependencies 31 | run: yarn 32 | env: 33 | NODE_ENV: development 34 | - name: Run Tests 35 | run: yarn run test 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # node/npm 2 | node_modules 3 | *.log 4 | 5 | # Build 6 | lib 7 | 8 | # OS 9 | .DS_Store 10 | [t|T]humbs.db 11 | 12 | # Testing 13 | test-results 14 | .jest-cache 15 | 16 | # IDE 17 | .idea 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | - "8" 5 | after_success: 6 | - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js 7 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # https://help.github.com/en/articles/about-code-owners 2 | 3 | * @americanexpress/one 4 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2016 American Express Travel Related Services Company, Inc. 190 | 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Albus · [![Build Status](https://img.shields.io/travis/americanexpress/react-albus/main.svg?style=flat)](https://travis-ci.org/americanexpress/react-albus) [![Coverage Status](https://coveralls.io/repos/github/americanexpress/react-albus/badge.svg?branch=main)](https://coveralls.io/github/americanexpress/react-albus?branch=main) [![npm version](https://img.shields.io/npm/v/react-albus.svg?style=flat)](https://www.npmjs.com/package/react-albus) 2 | 3 | > “Let us `` into the night and pursue that flighty temptress, adventure.” 4 | > 5 | > \-- _Albus Dumbledore_ 6 | 7 | ## 🤹‍ What is React Albus? 8 | React Albus is a React component library used to build declarative multi-step journeys (also known as Wizards). You define your step content and ordering and React Albus will manage the journey-related state for you. 9 | 10 | React Albus is otherwise unopinionated and allows you to compose functionality such as routing, animation, and analytics however you see fit. 11 | 12 | ## 💻 Installation 13 | 14 | ``` 15 | npm install react-albus 16 | ``` 17 | 18 | ## 📘 Example 19 | 20 | ```jsx 21 | import React from 'react'; 22 | import { Wizard, Steps, Step } from 'react-albus'; 23 | 24 | const Example = () => ( 25 | 26 | 27 | ( 30 |
31 |

Merlin

32 | 33 |
34 | )} 35 | /> 36 | ( 39 |
40 |

Gandalf

41 | 42 | 43 |
44 | )} 45 | /> 46 | ( 49 |
50 |

Dumbledore

51 | 52 |
53 | )} 54 | /> 55 |
56 |
57 | ); 58 | 59 | export default Example; 60 | ``` 61 | ## ✨ Demo 62 | 63 | Check out the [demo page](https://americanexpress.io/react-albus)! 64 | 65 | ## 🎛️ API 66 | 67 | - [``](#wizard) 68 | - [``](#steps) 69 | - [``](#step) 70 | - [`withWizard`](#withwizard) 71 | - [`wizardShape`](#wizardShape) 72 | - [`context.wizard`](#contextwizard) 73 | 74 | --- 75 | 76 | ### `` 77 | 78 | #### Props 79 | ##### `onNext(wizard)`: function *(optional)* 80 | A function that will be called by `` to determine the next step to proceed to. An action must be taken within `onNext` to navigate to a `Step`. To navigate to the next `Step`, use `push()`. 81 | 82 | ##### Params 83 | 84 | * `wizard` (object): The [`context.wizard`](#contextwizard) object. 85 | 86 | If you do not pass an `onNext` prop, `` will proceed directly to the next step. 87 | 88 | ##### `render(wizard)`: function *(optional)* 89 | A function that will be used as the render function of ``. 90 | 91 | ##### Params 92 | * `wizard` (object): The [`context.wizard`](#contextwizard) object. 93 | 94 | --- 95 | 96 | ### `` 97 | Wraps all of the `` components in your journey. The only direct children of `` should be `` components. 98 | 99 | #### Props 100 | ##### `step`: object ***(optional)*** 101 | An object describing the current step with the structure: `{ id: string }`. Defining a `step` prop will make `` a [controlled component](https://facebook.github.io/react/docs/forms.html). 102 | 103 | ------ 104 | 105 | ### `` 106 | 107 | Wraps all the content that will be conditionally shown when the step is active. 108 | 109 | #### Props 110 | 111 | ##### `id`: string 112 | 113 | Unique key for each step. 114 | 115 | In addition to `id`, any additional props added to `` will be available on each `step` object. This can be used to add names, descriptions, or other metadata to each step. 116 | 117 | `` is an alias for `` that can be used to access [`context.wizard`](#contextwizard) anywhere within the `` tree. 118 | 119 | --- 120 | 121 | ### `withWizard()` 122 | A higher order component that adds [`context.wizard`](#contextwizard) as a `wizard ` prop on the wrapped component. 123 | 124 | --- 125 | 126 | ### `context.wizard` 127 | `` provides an object on context with the following properties: 128 | 129 | * `step` (object): Describes the current step with structure: `{ id: string }`. 130 | * `steps` (array): Array of `step` objects in the order they were declared within ``. 131 | * `history` (object): The backing [`history`](https://github.com/ReactTraining/history#properties) object. 132 | * `preserveQuery` (boolean): Whether or not to preserve the query string when navigating between steps. 133 | * `next()` (function): Moves to the next step in order. 134 | * `previous()` (function): Moves to the previous step in order. 135 | * `go(n)` (function): Moves `n` steps in history. 136 | * `push(id)` (function): Pushes the step with corresponding `id` onto history. If no `id` is provided, the next step will be pushed onto history. 137 | * `replace(id)` (function): Replaces the current step in history with the step with corresponding `id`. 138 | * `set(id)` (function): Move to step `id`. 139 | 140 | ## 📘 Usage with React Router 141 | 142 | Internally, React Albus uses [history](https://github.com/ReactTraining/history) to maintain the ordering of steps. This makes integrating with React Router (or any other router) as easy as providing `` with `history` and `basename` props. 143 | 144 | ```jsx 145 | import React from 'react'; 146 | import { Route } from 'react-router-dom'; 147 | import { Wizard } from 'react-albus'; 148 | 149 | const RoutedWizard = ({ children }) => 150 | 152 | 153 | {children} 154 | } 155 | />; 156 | 157 | export default RoutedWizard; 158 | ``` 159 | 160 | 161 | ## 🏆 Contributing 162 | We welcome Your interest in the American Express Open Source Community on Github. Any Contributor to any Open Source Project managed by the American Express Open Source Community must accept and sign an Agreement indicating agreement to the terms below. Except for the rights granted in this Agreement to American Express and to recipients of software distributed by American Express, You reserve all right, title, and interest, if any, in and to Your Contributions. Please [fill out the Agreement](https://cla-assistant.io/americanexpress/react-albus). 163 | 164 | ## 🗝️ License 165 | Any contributions made under this project will be governed by the [Apache License 2.0](https://github.com/americanexpress/react-albus/blob/master/LICENSE.txt). 166 | 167 | ## 🗣️ Code of Conduct 168 | This project adheres to the [American Express Community Guidelines](https://github.com/americanexpress/react-albus/wiki/Code-of-Conduct). 169 | By participating, you are expected to honor these guidelines. 170 | 171 | [![Analytics](https://ga-beacon.appspot.com/UA-85897603-1/react-albus/README.md?useReferrer)](https://github.com/americanexpress/react-albus) 172 | -------------------------------------------------------------------------------- /__tests__/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['amex/test', '../.eslintrc.js'], 3 | }; 4 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/withWizard.spec.jsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`withWizard should add wizard prop to wrapped component 1`] = ` 4 | 11 | `; 12 | 13 | exports[`withWizard should use component props over context 1`] = ` 14 | 17 | `; 18 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/wizardShape.spec.jsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`wizardShape exports the correct shape 1`] = ` 4 | Object { 5 | "isRequired": Object { 6 | "go": "squawk", 7 | "history": "squawk", 8 | "next": "squawk", 9 | "previous": "squawk", 10 | "push": "squawk", 11 | "replace": "squawk", 12 | "set": "squawk", 13 | "step": Object { 14 | "id": "squawk", 15 | }, 16 | "steps": Object { 17 | "id": "squawk", 18 | }, 19 | }, 20 | } 21 | `; 22 | -------------------------------------------------------------------------------- /__tests__/components/Step.spec.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import React from 'react'; 16 | import { shallow } from 'enzyme'; 17 | import { Step } from '../../src'; 18 | 19 | const context = { wizard: { drinkMore: 'butter beer' } }; 20 | 21 | describe('Step', () => { 22 | it('should render children', () => { 23 | const rendered = shallow( 24 | 25 |
26 | , 27 | { context } 28 | ); 29 | 30 | expect(rendered).toMatchSnapshot(); 31 | }); 32 | 33 | it('should pass wizard to function as child', () => { 34 | shallow( 35 | 36 | {wizard => { 37 | expect(wizard).toEqual(context.wizard); 38 | }} 39 | , 40 | { context } 41 | ); 42 | }); 43 | 44 | it('should pass wizard to render prop', () => { 45 | shallow( 46 | { 48 | expect(wizard).toEqual(context.wizard); 49 | }} 50 | />, 51 | { context } 52 | ); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /__tests__/components/Steps.spec.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import React from 'react'; 16 | import { shallow } from 'enzyme'; 17 | import { Steps } from '../../src'; 18 | 19 | const Step = () => null; 20 | 21 | const context = { 22 | wizard: { 23 | step: { 24 | id: null, 25 | }, 26 | steps: [], 27 | init: jest.fn(), 28 | }, 29 | }; 30 | 31 | describe('Steps', () => { 32 | it('should call init', () => { 33 | shallow( 34 | 35 | 36 | , 37 | { context } 38 | ); 39 | 40 | expect(context.wizard.init).toHaveBeenCalledWith([{ id: 'hogwarts' }]); 41 | }); 42 | 43 | it('should render correct child if controlled', () => { 44 | const rendered = shallow( 45 | 46 | 47 | 48 | , 49 | { context } 50 | ); 51 | 52 | expect(rendered).toMatchSnapshot(); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /__tests__/components/WithWizard.spec.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import React from 'react'; 16 | import { shallow } from 'enzyme'; 17 | import { WithWizard } from '../../src'; 18 | 19 | const context = { wizard: { drinkMore: 'butter beer' } }; 20 | 21 | describe('WithWizard', () => { 22 | it('should render children', () => { 23 | const rendered = shallow( 24 | 25 |
26 | , 27 | { context } 28 | ); 29 | 30 | expect(rendered).toMatchSnapshot(); 31 | }); 32 | 33 | it('should pass wizard to function as child', () => { 34 | shallow( 35 | 36 | {wizard => { 37 | expect(wizard).toEqual(context.wizard); 38 | }} 39 | , 40 | { context } 41 | ); 42 | }); 43 | 44 | it('should pass wizard to render prop', () => { 45 | shallow( 46 | { 48 | expect(wizard).toEqual(context.wizard); 49 | }} 50 | />, 51 | { context } 52 | ); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /__tests__/components/Wizard.spec.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import React from 'react'; 16 | import { mount } from 'enzyme'; 17 | 18 | import { Wizard, Steps, Step, WithWizard } from '../../src'; 19 | 20 | describe('Wizard', () => { 21 | describe('with no props', () => { 22 | let wizard; 23 | let mounted; 24 | beforeEach(() => { 25 | mounted = mount( 26 | 27 | 28 | {prop => { 29 | wizard = prop; 30 | return null; 31 | }} 32 | 33 | 34 | 35 |
36 | 37 | 38 |
39 | 40 | 41 | 42 | ); 43 | }); 44 | 45 | it('should go to the next and previous steps', () => { 46 | const { next, previous } = wizard; 47 | expect(wizard.step).toEqual({ id: 'gryffindor' }); 48 | next(); 49 | expect(wizard.step).toEqual({ id: 'slytherin' }); 50 | previous(); 51 | expect(wizard.step).toEqual({ id: 'gryffindor' }); 52 | }); 53 | 54 | it('should push steps onto the stack', () => { 55 | const { push } = wizard; 56 | expect(wizard.step).toEqual({ id: 'gryffindor' }); 57 | push('slytherin'); 58 | expect(wizard.step).toEqual({ id: 'slytherin' }); 59 | }); 60 | 61 | it('should replace steps in the stack', () => { 62 | const { replace } = wizard; 63 | replace(); 64 | expect(wizard.step).toEqual({ id: 'slytherin' }); 65 | }); 66 | 67 | it('should pull steps off the stack', () => { 68 | const { next, go } = wizard; 69 | expect(wizard.step).toEqual({ id: 'gryffindor' }); 70 | next(); 71 | expect(wizard.step).toEqual({ id: 'slytherin' }); 72 | go(-1); 73 | expect(wizard.step).toEqual({ id: 'gryffindor' }); 74 | }); 75 | 76 | it('should do nothing if an invalid step is pushed', () => { 77 | const { push } = wizard; 78 | push('hufflepuff'); 79 | expect(wizard.step).toEqual({ id: 'gryffindor' }); 80 | }); 81 | 82 | afterEach(() => { 83 | mounted.unmount(); 84 | }); 85 | }); 86 | 87 | describe('with onNext prop', () => { 88 | const onNext = jest.fn(({ push }) => push()); 89 | 90 | let wizard; 91 | let mounted; 92 | beforeEach(() => { 93 | mounted = mount( 94 | 95 | 96 | {prop => { 97 | wizard = prop; 98 | return null; 99 | }} 100 | 101 | 102 | 103 |
104 | 105 | 106 |
107 | 108 | 109 | 110 | ); 111 | }); 112 | 113 | it('call onNext and go to the next step', () => { 114 | const { next } = wizard; 115 | next(); 116 | expect(onNext).toHaveBeenCalled(); 117 | expect(wizard.step).toEqual({ id: 'slytherin' }); 118 | }); 119 | 120 | afterEach(() => { 121 | mounted.unmount(); 122 | }); 123 | }); 124 | 125 | describe('with existing history', () => { 126 | const history = { 127 | replace: () => null, 128 | listen: () => () => null, 129 | location: { 130 | pathname: '/slytherin', 131 | }, 132 | }; 133 | 134 | let wizard; 135 | let mounted; 136 | beforeEach(() => { 137 | mounted = mount( 138 | 139 | 140 | {prop => { 141 | wizard = prop; 142 | return null; 143 | }} 144 | 145 | 146 | 147 |
148 | 149 | 150 |
151 | 152 | 153 | 154 | ); 155 | }); 156 | 157 | it('starts at the step in history', () => { 158 | expect(wizard.step).toEqual({ id: 'slytherin' }); 159 | }); 160 | 161 | afterEach(() => { 162 | mounted.unmount(); 163 | }); 164 | }); 165 | 166 | describe('with existing history and non-strict route matching', () => { 167 | const history = { 168 | replace: () => null, 169 | listen: () => () => null, 170 | location: { 171 | pathname: '/slytherin/snape', 172 | }, 173 | }; 174 | 175 | let wizard; 176 | let mounted; 177 | beforeEach(() => { 178 | mounted = mount( 179 | 180 | 181 | {prop => { 182 | wizard = prop; 183 | return null; 184 | }} 185 | 186 | 187 | 188 |
189 | 190 | 191 |
192 | 193 | 194 | 195 | ); 196 | }); 197 | 198 | it('matches the step', () => { 199 | expect(wizard.step).toEqual({ id: 'slytherin' }); 200 | }); 201 | 202 | afterEach(() => { 203 | mounted.unmount(); 204 | }); 205 | }); 206 | 207 | describe('with existing history and preserving search params', () => { 208 | let wizard; 209 | let mounted; 210 | 211 | const mockReplace = jest.fn(); 212 | const mockPush = jest.fn(); 213 | 214 | beforeEach(() => { 215 | jest.clearAllMocks(); 216 | }); 217 | 218 | describe('initially at /gryffindor with ?foo=bar', () => { 219 | const history = { 220 | replace: mockReplace, 221 | listen: () => () => null, 222 | location: { 223 | pathname: '/gryffindor', 224 | search: '?foo=bar', 225 | }, 226 | }; 227 | 228 | beforeEach(() => { 229 | mounted = mount( 230 | 231 | 232 | {prop => { 233 | wizard = prop; 234 | return null; 235 | }} 236 | 237 | 238 | 239 |
240 | 241 | 242 |
243 | 244 | 245 |
246 | 247 | 248 | 249 | ); 250 | }); 251 | 252 | it('should preserve query when calling next', () => { 253 | wizard.history.push = mockPush; 254 | wizard.next(); 255 | expect(mockPush).toBeCalledWith({ pathname: '/slytherin', search: '?foo=bar' }); 256 | }); 257 | 258 | it('should preserve query when calling replace', () => { 259 | wizard.replace('hufflepuff'); 260 | expect(mockReplace).toBeCalledWith({ pathname: '/hufflepuff', search: '?foo=bar' }); 261 | }); 262 | 263 | it('should produce the correct URL string when preserving search params', () => { 264 | wizard.replace('hufflepuff'); 265 | const callArgs = mockReplace.mock.calls[0][0]; 266 | const actualURL = `${callArgs.pathname}${callArgs.search}`; 267 | expect(actualURL).toBe('/hufflepuff?foo=bar'); 268 | }); 269 | 270 | it('should not add search params if none existed initially when calling push', () => { 271 | history.location.search = ''; 272 | wizard.push('hufflepuff'); 273 | expect(mockPush).toBeCalledWith({ pathname: '/hufflepuff', search: '' }); 274 | }); 275 | }); 276 | 277 | describe('initially at /slytherin with ?quidditch=true', () => { 278 | const history = { 279 | replace: mockReplace, 280 | listen: () => () => null, 281 | location: { 282 | pathname: '/slytherin', 283 | search: '?quidditch=true', 284 | }, 285 | }; 286 | 287 | beforeEach(() => { 288 | mounted = mount( 289 | 290 | 291 | {prop => { 292 | wizard = prop; 293 | return null; 294 | }} 295 | 296 | 297 | 298 |
299 | 300 | 301 |
302 | 303 | 304 |
305 | 306 | 307 | 308 | ); 309 | }); 310 | 311 | it('should preserve query when calling next', () => { 312 | wizard.history.push = jest.fn(); 313 | wizard.next(); 314 | expect(wizard.history.push).toBeCalledWith({ 315 | pathname: '/hufflepuff', 316 | search: '?quidditch=true', 317 | }); 318 | }); 319 | 320 | it('should produce the correct URL string when preserving search params', () => { 321 | wizard.replace('gryffindor'); 322 | const callArgs = mockReplace.mock.calls[0][0]; 323 | const actualURL = `${callArgs.pathname}${callArgs.search}`; 324 | expect(actualURL).toBe('/gryffindor?quidditch=true'); 325 | }); 326 | }); 327 | 328 | afterEach(() => { 329 | mounted.unmount(); 330 | }); 331 | }); 332 | }); 333 | -------------------------------------------------------------------------------- /__tests__/components/__snapshots__/Step.spec.jsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Step should render children 1`] = `
`; 4 | -------------------------------------------------------------------------------- /__tests__/components/__snapshots__/Steps.spec.jsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Steps should render correct child if controlled 1`] = ` 4 | 8 | `; 9 | -------------------------------------------------------------------------------- /__tests__/components/__snapshots__/WithWizard.spec.jsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`WithWizard should render children 1`] = `
`; 4 | -------------------------------------------------------------------------------- /__tests__/withWizard.spec.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import React from 'react'; 16 | import { shallow } from 'enzyme'; 17 | import { withWizard } from '../src'; 18 | 19 | const WrappedComponent = () =>
; 20 | const context = { 21 | wizard: { 22 | hogwarts: 'rules', 23 | }, 24 | }; 25 | 26 | describe('withWizard', () => { 27 | it('should add wizard prop to wrapped component', () => { 28 | const Wrapped = withWizard(WrappedComponent); 29 | const rendered = shallow(, { context }); 30 | expect(rendered).toMatchSnapshot(); 31 | }); 32 | 33 | it('should use component props over context', () => { 34 | const Wrapped = withWizard(WrappedComponent); 35 | const rendered = shallow(, { context }); 36 | expect(rendered).toMatchSnapshot(); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /__tests__/wizardShape.spec.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import { wizardShape } from '../src'; 16 | 17 | jest.mock('prop-types', () => ({ 18 | shape: shape => ({ isRequired: shape }), 19 | arrayOf: item => ({ isRequired: item }), 20 | func: { isRequired: 'squawk' }, 21 | object: { isRequired: 'squawk' }, 22 | string: { isRequired: 'squawk' }, 23 | node: { isRequired: 'squawk' }, 24 | })); 25 | 26 | describe('wizardShape', () => { 27 | it('exports the correct shape', () => { 28 | expect(wizardShape).toMatchSnapshot(); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React Albus Demo 6 | 7 | 8 | 25 | 26 | 27 | 28 |
29 |
30 |
31 |
32 |

React Albus

33 |
34 |
35 | Github 36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |

Start Simple

44 | 46 |
47 |
48 |
49 |
50 |

Add Routing

51 | 53 |
54 |
55 |
56 |
57 |

Add Animation

58 | 60 |
61 |
62 |
63 |
64 |

Add Progress Bar

65 | 67 |
68 |
69 |
70 |
71 |

Skip A Step

72 | 74 |
75 |
76 |
77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /examples/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: '../.eslintrc.js', 3 | rules: { 4 | 'import/no-extraneous-dependencies': 'off', 5 | 'import/no-unresolved': 'off', 6 | 'import/extensions': 'off', 7 | 'react/jsx-filename-extension': 'off', 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /examples/add-animation/Navigation.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { WithWizard } from 'react-albus'; 3 | 4 | const Navigation = () => ( 5 | ( 7 |
8 | {steps.indexOf(step) < steps.length - 1 && ( 9 | 12 | )} 13 | 14 | {steps.indexOf(step) > 0 && ( 15 | 18 | )} 19 |
20 | )} 21 | /> 22 | ); 23 | 24 | export default Navigation; 25 | -------------------------------------------------------------------------------- /examples/add-animation/exampleAnimation.css: -------------------------------------------------------------------------------- 1 | .example-steps { 2 | position: absolute; 3 | } 4 | 5 | .example-buttons { 6 | padding-top: 75px; 7 | } 8 | 9 | .example-enter { 10 | opacity: 0.01; 11 | } 12 | 13 | .example-enter.example-enter-active { 14 | opacity: 1; 15 | transition: opacity 500ms ease-in; 16 | } 17 | 18 | .example-exit { 19 | opacity: 1; 20 | } 21 | 22 | .example-exit.example-exit-active { 23 | opacity: 0.01; 24 | transition: opacity 300ms ease-in; 25 | } -------------------------------------------------------------------------------- /examples/add-animation/index.html: -------------------------------------------------------------------------------- 1 | 2 |
-------------------------------------------------------------------------------- /examples/add-animation/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import { BrowserRouter, Route } from 'react-router-dom'; 4 | import { TransitionGroup, CSSTransition } from 'react-transition-group'; 5 | import { Wizard, Steps, Step } from 'react-albus'; 6 | import Navigation from './Navigation'; 7 | import './exampleAnimation.css'; 8 | 9 | const AddAnimation = () => ( 10 | 11 |
12 |
13 | ( 15 | ( 18 |
19 | 20 | 25 |
26 | 27 | 28 |

Gandalf

29 |
30 | 31 |

Dumbledore

32 |
33 | 34 |

Ice King

35 |
36 |
37 |
38 |
39 |
40 | 41 |
42 | )} 43 | /> 44 | )} 45 | /> 46 |
47 |
48 |
49 | ); 50 | 51 | render(, document.getElementById('add-animation')); 52 | -------------------------------------------------------------------------------- /examples/add-animation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Add Animation", 3 | "version": "1.0.0", 4 | "private": true, 5 | "main": "index.js", 6 | "dependencies": { 7 | "prop-types": "15.6.0", 8 | "react": "16.2.0", 9 | "react-albus": "2.0.0", 10 | "react-dom": "16.2.0", 11 | "react-router-dom": "4.1.2", 12 | "react-transition-group": "2.2.1" 13 | } 14 | } -------------------------------------------------------------------------------- /examples/add-progress-bar/Navigation.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { WithWizard } from 'react-albus'; 3 | 4 | const Navigation = () => ( 5 | ( 7 |
8 | {steps.indexOf(step) < steps.length - 1 && ( 9 | 12 | )} 13 | 14 | {steps.indexOf(step) > 0 && ( 15 | 18 | )} 19 |
20 | )} 21 | /> 22 | ); 23 | 24 | export default Navigation; 25 | -------------------------------------------------------------------------------- /examples/add-progress-bar/exampleAnimation.css: -------------------------------------------------------------------------------- 1 | .example-steps { 2 | position: absolute; 3 | } 4 | 5 | .example-buttons { 6 | padding-top: 75px; 7 | } 8 | 9 | .example-enter { 10 | opacity: 0.01; 11 | } 12 | 13 | .example-enter.example-enter-active { 14 | opacity: 1; 15 | transition: opacity 500ms ease-in; 16 | } 17 | 18 | .example-exit { 19 | opacity: 1; 20 | } 21 | 22 | .example-exit.example-exit-active { 23 | opacity: 0.01; 24 | transition: opacity 300ms ease-in; 25 | } -------------------------------------------------------------------------------- /examples/add-progress-bar/index.html: -------------------------------------------------------------------------------- 1 | 2 |
-------------------------------------------------------------------------------- /examples/add-progress-bar/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import { BrowserRouter, Route } from 'react-router-dom'; 4 | import { TransitionGroup, CSSTransition } from 'react-transition-group'; 5 | import { Wizard, Steps, Step } from 'react-albus'; 6 | import { Line } from 'rc-progress'; 7 | import Navigation from './Navigation'; 8 | import './exampleAnimation.css'; 9 | 10 | const AddProgressBar = () => ( 11 | 12 |
13 |
14 | ( 16 | ( 19 |
20 | 24 | 25 | 30 |
31 | 32 | 33 |

Gandalf

34 |
35 | 36 |

Dumbledore

37 |
38 | 39 |

Ice King

40 |
41 |
42 |
43 |
44 |
45 | 46 |
47 | )} 48 | /> 49 | )} 50 | /> 51 |
52 |
53 |
54 | ); 55 | 56 | render(, document.getElementById('add-progress-bar')); 57 | -------------------------------------------------------------------------------- /examples/add-progress-bar/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Add Progress Bar", 3 | "version": "1.0.0", 4 | "private": true, 5 | "main": "index.js", 6 | "dependencies": { 7 | "prop-types": "15.6.0", 8 | "rc-progress": "2.2.1", 9 | "react": "16.2.0", 10 | "react-albus": "2.0.0", 11 | "react-dom": "16.2.0", 12 | "react-router-dom": "4.2.2", 13 | "react-transition-group": "2.2.1" 14 | } 15 | } -------------------------------------------------------------------------------- /examples/add-routing/Navigation.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { WithWizard } from 'react-albus'; 3 | 4 | const Navigation = () => ( 5 | ( 7 |
8 | {steps.indexOf(step) < steps.length - 1 && ( 9 | 12 | )} 13 | 14 | {steps.indexOf(step) > 0 && ( 15 | 18 | )} 19 |
20 | )} 21 | /> 22 | ); 23 | 24 | export default Navigation; 25 | -------------------------------------------------------------------------------- /examples/add-routing/index.html: -------------------------------------------------------------------------------- 1 | 2 |
-------------------------------------------------------------------------------- /examples/add-routing/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import { BrowserRouter, Route } from 'react-router-dom'; 4 | import { Wizard, Steps, Step } from 'react-albus'; 5 | import Navigation from './Navigation'; 6 | 7 | const AddRouting = () => ( 8 | 9 |
10 |
11 | ( 13 | 14 | 15 | 16 |

Gandalf

17 |
18 | 19 |

Dumbledore

20 |
21 | 22 |

Ice King

23 |
24 |
25 | 26 |
27 | )} 28 | /> 29 |
30 |
31 |
32 | ); 33 | 34 | render(, document.getElementById('add-routing')); 35 | -------------------------------------------------------------------------------- /examples/add-routing/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Add Routing", 3 | "version": "1.0.0", 4 | "private": true, 5 | "main": "index.js", 6 | "dependencies": { 7 | "prop-types": "15.6.0", 8 | "react": "16.2.0", 9 | "react-albus": "2.0.0", 10 | "react-dom": "16.2.0", 11 | "react-router-dom": "4.2.2" 12 | } 13 | } -------------------------------------------------------------------------------- /examples/skip-a-step/Navigation.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { WithWizard } from 'react-albus'; 3 | 4 | const Navigation = () => ( 5 | ( 7 |
8 | {steps.indexOf(step) < steps.length - 1 && ( 9 | 12 | )} 13 | 14 | {steps.indexOf(step) > 0 && ( 15 | 18 | )} 19 |
20 | )} 21 | /> 22 | ); 23 | 24 | export default Navigation; 25 | -------------------------------------------------------------------------------- /examples/skip-a-step/exampleAnimation.css: -------------------------------------------------------------------------------- 1 | .example-steps { 2 | position: absolute; 3 | } 4 | 5 | .example-buttons { 6 | padding-top: 75px; 7 | } 8 | 9 | .example-enter { 10 | opacity: 0.01; 11 | } 12 | 13 | .example-enter.example-enter-active { 14 | opacity: 1; 15 | transition: opacity 500ms ease-in; 16 | } 17 | 18 | .example-exit { 19 | opacity: 1; 20 | } 21 | 22 | .example-exit.example-exit-active { 23 | opacity: 0.01; 24 | transition: opacity 300ms ease-in; 25 | } -------------------------------------------------------------------------------- /examples/skip-a-step/index.html: -------------------------------------------------------------------------------- 1 | 2 |
-------------------------------------------------------------------------------- /examples/skip-a-step/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import { BrowserRouter, Route } from 'react-router-dom'; 4 | import { TransitionGroup, CSSTransition } from 'react-transition-group'; 5 | import { Wizard, Steps, Step } from 'react-albus'; 6 | import { Line } from 'rc-progress'; 7 | import Navigation from './Navigation'; 8 | import './exampleAnimation.css'; 9 | 10 | const skip = ({ step, push }) => { 11 | switch (step.id) { 12 | case 'gandalf': { 13 | push('ice-king'); 14 | break; 15 | } 16 | default: 17 | push(); 18 | } 19 | }; 20 | 21 | const SkipAStep = () => ( 22 | 23 |
24 |
25 | ( 27 | ( 31 |
32 | 36 | 37 | 42 |
43 | 44 | 45 |

Gandalf

46 |
47 | 48 |

Dumbledore

49 |
50 | 51 |

Ice King

52 |
53 |
54 |
55 |
56 |
57 | 58 |
59 | )} 60 | /> 61 | )} 62 | /> 63 |
64 |
65 |
66 | ); 67 | 68 | render(, document.getElementById('skip-a-step')); 69 | -------------------------------------------------------------------------------- /examples/skip-a-step/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Skip a Step", 3 | "version": "1.0.0", 4 | "private": true, 5 | "main": "index.js", 6 | "dependencies": { 7 | "prop-types": "15.6.0", 8 | "rc-progress": "2.2.1", 9 | "react": "16.2.0", 10 | "react-albus": "2.0.0", 11 | "react-dom": "16.2.0", 12 | "react-router-dom": "4.2.2", 13 | "react-transition-group": "2.2.1" 14 | } 15 | } -------------------------------------------------------------------------------- /examples/start-simple/Navigation.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { WithWizard } from 'react-albus'; 3 | 4 | const Navigation = () => ( 5 | ( 7 |
8 | {steps.indexOf(step) < steps.length - 1 && ( 9 | 12 | )} 13 | 14 | {steps.indexOf(step) > 0 && ( 15 | 18 | )} 19 |
20 | )} 21 | /> 22 | ); 23 | 24 | export default Navigation; 25 | -------------------------------------------------------------------------------- /examples/start-simple/index.html: -------------------------------------------------------------------------------- 1 | 2 |
-------------------------------------------------------------------------------- /examples/start-simple/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import { Wizard, Steps, Step } from 'react-albus'; 4 | import Navigation from './Navigation'; 5 | 6 | const StartSimple = () => ( 7 |
8 |
9 | 10 | 11 | 12 |

Gandalf

13 |
14 | 15 |

Dumbledore

16 |
17 | 18 |

Ice King

19 |
20 |
21 | 22 |
23 |
24 |
25 | ); 26 | 27 | render(, document.getElementById('start-simple')); 28 | -------------------------------------------------------------------------------- /examples/start-simple/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Start Simple", 3 | "version": "1.0.0", 4 | "private": true, 5 | "main": "index.js", 6 | "dependencies": { 7 | "prop-types": "15.6.0", 8 | "react": "16.2.0", 9 | "react-albus": "2.0.0", 10 | "react-dom": "16.2.0" 11 | } 12 | } -------------------------------------------------------------------------------- /index.dev.js: -------------------------------------------------------------------------------- 1 | import './examples/start-simple'; 2 | import './examples/add-routing'; 3 | import './examples/add-animation'; 4 | import './examples/add-progress-bar'; 5 | import './examples/skip-a-step'; 6 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React Albus 6 | 7 | 8 | 9 | 10 |
11 |
12 |
13 |
14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-albus", 3 | "version": "2.0.0", 4 | "description": "React component library for building declarative multi-step flows.", 5 | "files": [ 6 | "lib" 7 | ], 8 | "main": "lib", 9 | "scripts": { 10 | "clean": "rimraf lib", 11 | "start": "webpack-dev-server --port 3000 --inline --hot --open", 12 | "test": "jest", 13 | "lint": "eslint --ignore-path .eslintignore --ext .js --ext .jsx ./", 14 | "prebuild": "npm run clean && npm run lint", 15 | "build": "babel src -d lib --copy-files", 16 | "posttest": "npm run lint", 17 | "prepublish": "npm run build" 18 | }, 19 | "repository": "americanexpress/react-albus", 20 | "keywords": [ 21 | "react", 22 | "react-component", 23 | "wizard", 24 | "step", 25 | "multistep", 26 | "routing" 27 | ], 28 | "authors": [ 29 | "Jack Cross (https://github.com/jayjaycross)", 30 | "Nathan Force (https://github.com/nathanforce)" 31 | ], 32 | "license": "Apache-2.0", 33 | "jest": { 34 | "preset": "amex-jest-preset-react", 35 | "testPathIgnorePatterns": [ 36 | ".eslintrc.js" 37 | ] 38 | }, 39 | "dependencies": { 40 | "history": "^4.6.0", 41 | "hoist-non-react-statics": "^2.3.1", 42 | "prop-types": "^15.5.8" 43 | }, 44 | "devDependencies": { 45 | "amex-jest-preset-react": "^4.0.0", 46 | "babel-cli": "^6.6.5", 47 | "babel-core": "^6.5.2", 48 | "babel-eslint": "^6.0.0", 49 | "babel-loader": "^6.2.2", 50 | "babel-preset-env": "^1.6.0", 51 | "babel-preset-react": "^6.5.0", 52 | "babel-preset-react-hmre": "^1.1.0", 53 | "babel-preset-stage-0": "^6.5.0", 54 | "coveralls": "^2.13.1", 55 | "css-loader": "^0.26.2", 56 | "enzyme": "^3.1.0", 57 | "enzyme-to-json": "^3.1.4", 58 | "eslint": "^3.19.0", 59 | "eslint-config-amex": "^6.0.0", 60 | "eslint-config-prettier": "^2.2.0", 61 | "eslint-plugin-prettier": "^2.1.2", 62 | "import-glob-loader": "^1.1.0", 63 | "jest": "^21.2.1", 64 | "node-sass": "^4.5.0", 65 | "prettier": "^1.6.1", 66 | "rc-progress": "^2.2.5", 67 | "react": "^16.0.0", 68 | "react-dom": "^16.0.0", 69 | "react-router-dom": "^4.0.0", 70 | "react-test-renderer": "^16.0.0", 71 | "react-transition-group": "^2.2.1", 72 | "rimraf": "^2.5.2", 73 | "sass-loader": "^6.0.2", 74 | "style-loader": "^0.13.2", 75 | "webpack": "^2.0.0", 76 | "webpack-dev-server": "^2.0.0" 77 | }, 78 | "peerDependencies": { 79 | "react": "^15.0.0 || ^16.0.0" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/components/Steps.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import React, { Component } from 'react'; 16 | import PropTypes from 'prop-types'; 17 | 18 | class Steps extends Component { 19 | componentDidMount() { 20 | const steps = React.Children.map( 21 | this.props.children, 22 | ({ props: { children, render, ...config } }) => config 23 | ); 24 | this.context.wizard.init(steps); 25 | } 26 | 27 | render() { 28 | const { id: activeId } = this.props.step || this.context.wizard.step; 29 | const [child = null] = React.Children.toArray(this.props.children).filter( 30 | ({ props: { id } }) => id === activeId 31 | ); 32 | return child; 33 | } 34 | } 35 | 36 | Steps.propTypes = { 37 | children: PropTypes.node.isRequired, 38 | step: PropTypes.shape({ 39 | id: PropTypes.string.isRequired, 40 | }), 41 | }; 42 | 43 | Steps.defaultProps = { 44 | step: null, 45 | }; 46 | 47 | Steps.contextTypes = { 48 | // disabling due to lost context 49 | // eslint-disable-next-line react/forbid-prop-types 50 | wizard: PropTypes.object, 51 | }; 52 | 53 | export default Steps; 54 | -------------------------------------------------------------------------------- /src/components/Wizard.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import { Component } from 'react'; 16 | import PropTypes from 'prop-types'; 17 | import { createMemoryHistory } from 'history'; 18 | import renderCallback from '../utils/renderCallback'; 19 | 20 | // TODO: fix below 21 | /* eslint-disable no-undef */ 22 | class Wizard extends Component { 23 | state = { 24 | step: { 25 | id: null, 26 | }, 27 | steps: [], 28 | }; 29 | 30 | getChildContext() { 31 | return { 32 | wizard: { 33 | go: this.history.go, 34 | set: this.set, 35 | history: this.history, 36 | init: this.init, 37 | next: this.next, 38 | previous: this.previous, 39 | push: this.push, 40 | replace: this.replace, 41 | ...this.state, 42 | }, 43 | }; 44 | } 45 | 46 | componentDidMount() { 47 | this.unlisten = this.history.listen(({ pathname }) => 48 | this.setState({ step: this.pathToStep(pathname) }) 49 | ); 50 | 51 | if (this.props.onNext) { 52 | const { init, ...wizard } = this.getChildContext().wizard; 53 | this.props.onNext(wizard); 54 | } 55 | } 56 | 57 | componentWillUnmount() { 58 | this.unlisten(); 59 | } 60 | 61 | get basename() { 62 | return `${this.props.basename}/`; 63 | } 64 | 65 | get ids() { 66 | return this.state.steps.map(s => s.id); 67 | } 68 | 69 | get nextStep() { 70 | return this.ids[this.ids.indexOf(this.state.step.id) + 1]; 71 | } 72 | 73 | get previousStep() { 74 | return this.ids[this.ids.indexOf(this.state.step.id) - 1]; 75 | } 76 | 77 | history = this.props.history || createMemoryHistory(); 78 | steps = []; 79 | 80 | pathToStep = pathname => { 81 | const id = pathname.replace(this.basename, ''); 82 | const [step] = this.state.steps.filter(s => 83 | this.props.exactMatch ? s.id === id : id.startsWith(s.id) 84 | ); 85 | 86 | return step || this.state.step; 87 | }; 88 | 89 | init = steps => { 90 | this.setState({ steps }, () => { 91 | const step = this.pathToStep(this.history.location.pathname); 92 | if (step.id) { 93 | this.setState({ step }); 94 | } else { 95 | this.history.replace(`${this.basename}${this.ids[0]}`); 96 | } 97 | }); 98 | }; 99 | 100 | constructPath = step => { 101 | if (this.props.preserveQuery) { 102 | return { 103 | ...this.history.location, 104 | pathname: `${this.basename}${step}`, 105 | }; 106 | } 107 | return `${this.basename}${step}`; 108 | }; 109 | 110 | push = (step = this.nextStep) => this.set(step); 111 | set = step => this.history.push(this.constructPath(step)); 112 | replace = (step = this.nextStep) => this.history.replace(this.constructPath(step)); 113 | pushPrevious = (step = this.previousStep) => this.set(step); 114 | 115 | next = () => { 116 | if (this.props.onNext) { 117 | this.props.onNext(this.getChildContext().wizard); 118 | } else { 119 | this.push(); 120 | } 121 | }; 122 | 123 | previous = () => { 124 | this.pushPrevious(); 125 | }; 126 | 127 | render() { 128 | const { init, ...wizard } = this.getChildContext().wizard; 129 | return renderCallback(this.props, wizard); 130 | } 131 | } 132 | 133 | Wizard.propTypes = { 134 | basename: PropTypes.string, 135 | preserveQuery: PropTypes.bool, 136 | history: PropTypes.shape({ 137 | // disabling due to lost context 138 | // eslint-disable-next-line react/forbid-prop-types 139 | entries: PropTypes.array, 140 | go: PropTypes.func, 141 | goBack: PropTypes.func, 142 | listen: PropTypes.func, 143 | // disabling due to lost context 144 | // eslint-disable-next-line react/forbid-prop-types 145 | location: PropTypes.object, 146 | push: PropTypes.func, 147 | replace: PropTypes.func, 148 | }), 149 | onNext: PropTypes.func, 150 | exactMatch: PropTypes.bool, 151 | }; 152 | 153 | Wizard.defaultProps = { 154 | basename: '', 155 | preserveQuery: false, 156 | history: null, 157 | onNext: null, 158 | render: null, 159 | exactMatch: true, 160 | }; 161 | 162 | Wizard.childContextTypes = { 163 | // disabling due to lost context 164 | // eslint-disable-next-line react/forbid-prop-types 165 | wizard: PropTypes.object, 166 | }; 167 | 168 | export default Wizard; 169 | -------------------------------------------------------------------------------- /src/components/createWizardComponent.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import PropTypes from 'prop-types'; 16 | import renderCallback from '../utils/renderCallback'; 17 | 18 | const createWizardComponent = name => { 19 | const WizardComponent = (props, { wizard: { init, ...wizard } }) => renderCallback(props, wizard); 20 | 21 | WizardComponent.contextTypes = { 22 | // disabling due to lost context 23 | // eslint-disable-next-line react/forbid-prop-types 24 | wizard: PropTypes.object, 25 | }; 26 | 27 | WizardComponent.displayName = name; 28 | 29 | return WizardComponent; 30 | }; 31 | 32 | export default createWizardComponent; 33 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import createWizardComponent from './components/createWizardComponent'; 16 | 17 | const Step = createWizardComponent('Step'); 18 | const WithWizard = createWizardComponent('WithWizard'); 19 | 20 | export { Step, WithWizard }; 21 | export Wizard from './components/Wizard'; 22 | export Steps from './components/Steps'; 23 | export withWizard from './withWizard'; 24 | export wizardShape from './wizardShape'; 25 | -------------------------------------------------------------------------------- /src/utils/renderCallback.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | const renderCallback = ({ render, children }, wizard) => { 16 | if (render) { 17 | return render(wizard); 18 | } else if (typeof children === 'function') { 19 | return children(wizard); 20 | } 21 | return children; 22 | }; 23 | 24 | export default renderCallback; 25 | -------------------------------------------------------------------------------- /src/withWizard.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import React from 'react'; 16 | import PropTypes from 'prop-types'; 17 | import hoistStatics from 'hoist-non-react-statics'; 18 | 19 | const withWizard = Component => { 20 | const WithWizard = (props, { wizard: { init, ...wizard } }) => 21 | React.createElement(Component, { 22 | wizard, 23 | ...props, 24 | }); 25 | 26 | WithWizard.contextTypes = { 27 | // disabling due to lost context 28 | // eslint-disable-next-line react/forbid-prop-types 29 | wizard: PropTypes.object, 30 | }; 31 | 32 | WithWizard.displayName = `withWizard(${Component.displayName || Component.name})`; 33 | WithWizard.WrappedComponent = Component; 34 | 35 | return hoistStatics(WithWizard, Component); 36 | }; 37 | 38 | export default withWizard; 39 | -------------------------------------------------------------------------------- /src/wizardShape.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | import PropTypes from 'prop-types'; 16 | 17 | export default PropTypes.shape({ 18 | go: PropTypes.func.isRequired, 19 | set: PropTypes.func.isRequired, 20 | // disabling due to lost context 21 | // eslint-disable-next-line react/forbid-prop-types 22 | history: PropTypes.object.isRequired, 23 | next: PropTypes.func.isRequired, 24 | previous: PropTypes.func.isRequired, 25 | push: PropTypes.func.isRequired, 26 | replace: PropTypes.func.isRequired, 27 | step: PropTypes.shape({ 28 | id: PropTypes.string.isRequired, 29 | }).isRequired, 30 | steps: PropTypes.arrayOf( 31 | PropTypes.shape({ 32 | id: PropTypes.string.isRequired, 33 | }).isRequired 34 | ).isRequired, 35 | }); 36 | -------------------------------------------------------------------------------- /webpack.config.babel.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | module.exports = { 4 | entry: './index.dev.js', 5 | module: { 6 | rules: [ 7 | { 8 | test: /\.jsx?$/, 9 | loader: 'babel-loader', 10 | exclude: /node_modules/, 11 | }, 12 | { 13 | test: /\.css$/, 14 | loader: 'style-loader!css-loader', 15 | exclude: /node_modules/, 16 | }, 17 | ], 18 | }, 19 | devServer: { 20 | historyApiFallback: true, 21 | }, 22 | resolve: { 23 | extensions: ['.js', '.jsx', '.scss'], 24 | alias: { 25 | 'react-albus': path.resolve(__dirname, 'src'), 26 | }, 27 | }, 28 | }; 29 | --------------------------------------------------------------------------------