├── .eslintrc.js
├── .github
└── CODEOWNERS
├── .gitignore
├── .storybook
├── addons.js
├── config.js
└── webpack.config.js
├── .travis.yml
├── LICENSE
├── README.md
├── babel.config.js
├── demo-public
└── index.html
├── jest.config.js
├── package.json
├── src
├── __snapshots__
│ └── storybook.test.js.snap
├── components
│ ├── alert.js
│ ├── alert.stories.js
│ ├── alert.test.js
│ ├── chip.js
│ ├── chip.stories.js
│ ├── chip.test.js
│ ├── codeSample.js
│ ├── codeSample.stories.js
│ ├── codeSample.test.js
│ ├── copyInput.js
│ ├── copyInput.stories.js
│ ├── copyInput.test.js
│ ├── copyNotification
│ │ ├── copyNotification.js
│ │ ├── copyNotification.stories.js
│ │ ├── copyNotification.test.js
│ │ └── snackbarContent.js
│ ├── iconButton.js
│ ├── iconButton.stories.js
│ ├── panel.js
│ ├── panel.stories.js
│ ├── roundedButton.js
│ ├── roundedButton.stories.js
│ ├── roundedButton.test.js
│ ├── searchBar
│ │ ├── overrides
│ │ │ ├── input.js
│ │ │ ├── searchInput.js
│ │ │ ├── textField.js
│ │ │ └── utils
│ │ │ │ └── autosuggest-highlight.js
│ │ ├── searchBar.js
│ │ ├── searchBar.stories.js
│ │ ├── searchBar.test.js
│ │ └── searchBarJest.js
│ ├── select.js
│ ├── select.stories.js
│ ├── tabs.js
│ ├── tabs.stories.js
│ ├── tabs.test.js
│ ├── twoPanelLayout.js
│ └── twoPanelLayout.stories.js
├── globalStyles
│ ├── index.js
│ └── lato
│ │ ├── LatoLatin-Black.eot
│ │ ├── LatoLatin-Black.ttf
│ │ ├── LatoLatin-Black.woff
│ │ ├── LatoLatin-Black.woff2
│ │ ├── LatoLatin-BlackItalic.eot
│ │ ├── LatoLatin-BlackItalic.ttf
│ │ ├── LatoLatin-BlackItalic.woff
│ │ ├── LatoLatin-BlackItalic.woff2
│ │ ├── LatoLatin-Bold.eot
│ │ ├── LatoLatin-Bold.ttf
│ │ ├── LatoLatin-Bold.woff
│ │ ├── LatoLatin-Bold.woff2
│ │ ├── LatoLatin-BoldItalic.eot
│ │ ├── LatoLatin-BoldItalic.ttf
│ │ ├── LatoLatin-BoldItalic.woff
│ │ ├── LatoLatin-BoldItalic.woff2
│ │ ├── LatoLatin-Hairline.eot
│ │ ├── LatoLatin-Hairline.ttf
│ │ ├── LatoLatin-Hairline.woff
│ │ ├── LatoLatin-Hairline.woff2
│ │ ├── LatoLatin-HairlineItalic.eot
│ │ ├── LatoLatin-HairlineItalic.ttf
│ │ ├── LatoLatin-HairlineItalic.woff
│ │ ├── LatoLatin-HairlineItalic.woff2
│ │ ├── LatoLatin-Heavy.eot
│ │ ├── LatoLatin-Heavy.ttf
│ │ ├── LatoLatin-Heavy.woff
│ │ ├── LatoLatin-Heavy.woff2
│ │ ├── LatoLatin-HeavyItalic.eot
│ │ ├── LatoLatin-HeavyItalic.ttf
│ │ ├── LatoLatin-HeavyItalic.woff
│ │ ├── LatoLatin-HeavyItalic.woff2
│ │ ├── LatoLatin-Light.eot
│ │ ├── LatoLatin-Light.ttf
│ │ ├── LatoLatin-Light.woff
│ │ ├── LatoLatin-Light.woff2
│ │ ├── LatoLatin-LightItalic.eot
│ │ ├── LatoLatin-LightItalic.ttf
│ │ ├── LatoLatin-LightItalic.woff
│ │ ├── LatoLatin-LightItalic.woff2
│ │ ├── LatoLatin-Medium.eot
│ │ ├── LatoLatin-Medium.ttf
│ │ ├── LatoLatin-Medium.woff
│ │ ├── LatoLatin-Medium.woff2
│ │ ├── LatoLatin-MediumItalic.eot
│ │ ├── LatoLatin-MediumItalic.ttf
│ │ ├── LatoLatin-MediumItalic.woff
│ │ ├── LatoLatin-MediumItalic.woff2
│ │ ├── LatoLatin-Regular.eot
│ │ ├── LatoLatin-Regular.ttf
│ │ ├── LatoLatin-Regular.woff
│ │ ├── LatoLatin-Regular.woff2
│ │ ├── LatoLatin-RegularItalic.eot
│ │ ├── LatoLatin-RegularItalic.ttf
│ │ ├── LatoLatin-RegularItalic.woff
│ │ ├── LatoLatin-RegularItalic.woff2
│ │ ├── LatoLatin-Semibold.eot
│ │ ├── LatoLatin-Semibold.ttf
│ │ ├── LatoLatin-Semibold.woff
│ │ ├── LatoLatin-Semibold.woff2
│ │ ├── LatoLatin-SemiboldItalic.eot
│ │ ├── LatoLatin-SemiboldItalic.ttf
│ │ ├── LatoLatin-SemiboldItalic.woff
│ │ ├── LatoLatin-SemiboldItalic.woff2
│ │ ├── LatoLatin-Thin.eot
│ │ ├── LatoLatin-Thin.ttf
│ │ ├── LatoLatin-Thin.woff
│ │ ├── LatoLatin-Thin.woff2
│ │ ├── LatoLatin-ThinItalic.eot
│ │ ├── LatoLatin-ThinItalic.ttf
│ │ ├── LatoLatin-ThinItalic.woff
│ │ └── LatoLatin-ThinItalic.woff2
├── icons
│ ├── alert.js
│ ├── algo.js
│ ├── book.js
│ ├── check.js
│ ├── clear.js
│ ├── clipboard.js
│ ├── collapse.js
│ ├── copyDrop.js
│ ├── copySimple.js
│ ├── dataset.js
│ ├── downloadDrop.js
│ ├── downloadSimple.js
│ ├── expand.js
│ ├── filterUp.js
│ ├── folder.js
│ ├── icons.stories.js
│ ├── index.js
│ ├── model.js
│ ├── moreVertical.js
│ ├── owkestraLogo.js
│ ├── permission.js
│ ├── search.js
│ └── substraLogo.js
├── index.js
├── storybook.test.js
├── utils
│ └── propTypes.js
└── variables
│ ├── colors.js
│ ├── font.js
│ └── spacing.js
├── test
├── mocks
│ ├── fileMock.js
│ ├── prismMock.js
│ └── styleMock.js
└── setup.js
└── yarn.lock
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "extends": "airbnb",
3 | "parser": "babel-eslint",
4 | "rules": {
5 | "indent": [0],
6 | "react/destructuring-assignment": [0],
7 | "react/jsx-indent": [2, 4],
8 | "react/jsx-indent-props": [2, 4],
9 | "react/jsx-filename-extension": [0],
10 | "react/jsx-fragments": [2, "element"],
11 | "react/jsx-closing-tag-location": [0],
12 | "react/jsx-props-no-spreading": [0],
13 | "react/no-unescaped-entities": [0],
14 | "react/no-danger": [0],
15 | "react/no-array-index-key": [0],
16 | "react/sort-comp": [0],
17 | "import/no-extraneous-dependencies": [0],
18 | "import/no-dynamic-require": [0],
19 | "import/extensions": [0],
20 | "camelcase": [0],
21 | "one-var": [0],
22 | "no-nested-ternary": [0],
23 | "brace-style": [2, 'stroustrup'],
24 | "no-confusing-arrow": [0],
25 | "object-property-newline": [2, {"allowMultiplePropertiesPerLine": true}],
26 | "no-shadow": [0],
27 | "global-require": [0],
28 | "no-console": [0, {"allow": ["warn", "error"]}],
29 | "no-lonely-if": [0],
30 | "no-restricted-syntax": [0],
31 | "no-underscore-dangle": [0],
32 | "import/no-webpack-loader-syntax": [0],
33 | "import/no-unresolved": [0],
34 | "import/no-useless-path-segments": [0], // for react-universal-component lazy loading
35 | "arrow-parens": [1, "as-needed"],
36 | "max-len": [0],
37 | "no-unused-vars": [2, {"args": "none"}],
38 | "consistent-return": [0],
39 | "no-bitwise": [0],
40 | "function-paren-newline": [0],
41 | "jsx-a11y/label-has-for": [0],
42 | "jsx-a11y/href-no-hash": "off",
43 | "jsx-a11y/anchor-is-valid": ["warn", { "aspects": ["invalidHref"] }],
44 | "jsx-a11y/click-events-have-key-events": [0],
45 | "jsx-a11y/no-static-element-interactions": [0],
46 | "no-multi-assign": [0],
47 | "no-mixed-operators": [0],
48 | "prefer-destructuring": [0],
49 |
50 | // helper for fixing common lint errors
51 | "func-names": [2],
52 | "import/first": [2],
53 | "import/newline-after-import": [2],
54 | "import/no-mutable-exports": [2],
55 | "import/prefer-default-export": [2],
56 | "react/forbid-prop-types": [2],
57 | "react/jsx-closing-bracket-location": [2],
58 | "react/jsx-no-bind": [2],
59 | "react/no-string-refs": [2],
60 | "react/no-unused-prop-types": [2],
61 | "react/prefer-es6-class": [2],
62 | "react/prefer-stateless-function": [2],
63 | "react/prop-types": [2],
64 | "no-param-reassign": [2],
65 | "no-undef": [2],
66 |
67 | // babel
68 | "object-curly-spacing": [0],
69 | "babel/object-curly-spacing": [2, 'never'],
70 | },
71 | "plugins": [
72 | "babel",
73 | "react",
74 | "jsx-a11y",
75 | "import",
76 | "jest",
77 | ],
78 | "env": {
79 | "jest/globals": true,
80 | },
81 | "overrides": [
82 | {
83 | "files": "**/*.spec.js",
84 | "rules": {
85 | "no-unused-expressions": [0],
86 | }
87 | }
88 | ]
89 | };
90 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # These owners will be the default owners for everything in
2 | # the repo. Unless a later match takes precedence,
3 | # @global-owner1 and @global-owner2 will be requested for
4 | # review when someone opens a pull request.
5 | * @jmorel
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | *.log*
3 | .idea/
4 | es/
5 | cjs/
6 | yarn-error.log
7 |
--------------------------------------------------------------------------------
/.storybook/addons.js:
--------------------------------------------------------------------------------
1 | import '@storybook/addon-storysource/register';
2 | import '@storybook/addon-knobs/register';
3 | import '@storybook/addon-actions/register';
4 | import '@storybook/addon-links/register';
5 |
--------------------------------------------------------------------------------
/.storybook/config.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {configure, addDecorator} from '@storybook/react';
3 | import requireContext from 'require-context.macro';
4 | import {GlobalStyles} from "../src";
5 |
6 | // automatically import all files ending in *.stories.js
7 | const req = requireContext('../src', true, /\.stories\.js$/);
8 |
9 | function loadStories() {
10 | req.keys().forEach(filename => req(filename));
11 | }
12 |
13 | const withGlobalStyles = (cb) => (
14 |
15 |
16 | {cb()}
17 |
18 | );
19 |
20 | addDecorator(withGlobalStyles);
21 |
22 |
23 | configure(loadStories, module);
24 |
--------------------------------------------------------------------------------
/.storybook/webpack.config.js:
--------------------------------------------------------------------------------
1 | // you can use this file to add your custom webpack plugins, loaders and anything you like.
2 | // This is just the basic way to add additional webpack configurations.
3 | // For more information refer the docs: https://storybook.js.org/configurations/custom-webpack-config
4 |
5 | // IMPORTANT
6 | // When you add this file, we won't add the default configurations which is similar
7 | // to "React Create App". This only has babel loader to load JavaScript.
8 |
9 | module.exports = ({config}) => {
10 | config.module.rules.push({
11 | test: /\.stories\.jsx?$/,
12 | loaders: [require.resolve('@storybook/addon-storysource/loader')],
13 | enforce: 'pre',
14 | });
15 |
16 | return config;
17 | };
18 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - lts/*
4 |
5 | deploy:
6 | provider: npm
7 | email: clement@gautier.im
8 | skip_cleanup: true
9 | api_key:
10 | secure: aTHnlgYOnpZ0HVYByrRaXH06sFhe7bPYye6YQG4Rh4gJj7YMmuw/c1SaAUj7fw5/ruuaV8zuD8aEZFV8E3icO2VI4YRP7c48JAEotizQVoj1QicPtEHev0y5SvsKiTWv6c1+PPAhItC3PChbV3W4L3JqQc6QkNlrmimWVDXjbobWjIKReM5gggTN3N2F1QA3B6WE+eXFENLORdiH1+MUaVnsJQ6ID8ZUo/Fi1uB0fG4PmepIoQKVwU1sA7kOumnKcvlibP2CHvOIP6Gb1inaO+/qNPWg/MOanWOHveRmYJvlBQghQwM0BbBGe6cwwOzzu/1KIcQEf8pctJwfbWtQAl6YHCv7lT6CJz19adonkCIaH7QI0ITfhnzsztDZ5cMFzDTt1eI2GvZIDQFgP5O/EZADB6OTKY7VvhL77g06q2ZOhhBrU+LPGn5kbFhoY4jLNwTtxcUM8YqbRdu1e9IjIZZGobT51kRSQElzRNr05Zw5NQJ3VNsrJiCGVThThWppaqiNA+eeHVX7H/i8pAGVCZZB5h9eYhIxZR6fnIRaVnCpx0SnjbUqf8FPxRRa2OUKhROsTrvKJyJHQVdU39vCmO2Eyh4TcLo4Rz65tnS6nsWv7UW9sbwPbeAuMp/FyEsiISnvD8XVP4PRZus9beVl2m6VAsDwGWPY5Na0DhOR6mM=
11 | on:
12 | tags: true
13 | repo: SubstraFoundation/substra-ui
14 |
15 | cache: yarn
16 |
17 | script:
18 | - yarn eslint
19 | - yarn test
20 | - yarn build
21 | - npm publish --dry-run
22 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
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 | Copyright 2018-2019 Owkin, inc.
179 |
180 | Licensed under the Apache License, Version 2.0 (the "License");
181 | you may not use this file except in compliance with the License.
182 | You may obtain a copy of the License at
183 |
184 | http://www.apache.org/licenses/LICENSE-2.0
185 |
186 | Unless required by applicable law or agreed to in writing, software
187 | distributed under the License is distributed on an "AS IS" BASIS,
188 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
189 | See the License for the specific language governing permissions and
190 | limitations under the License.
191 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Substra-ui :warning: DEPRECATED :warning:
2 |
3 | > The contents of this library have been merged in the main [substra-frontend](https://github.com/substrafoundation/substra-frontend/) repository. This library isn't maintained anymore and all future development will occur in the [substra-frontend](https://github.com/substrafoundation/substra-frontend/) repository.
4 |
5 | A shared UI components library for the Substra project.
6 |
7 | ## Storybook
8 |
9 | We use [Storybook](https://storybook.js.org/) for component development and testing:
10 |
11 | ```sh
12 | $ yarn storybook
13 | ```
14 |
15 | ## Development setup
16 |
17 | Follow these steps to bypass the package repository and link together the local versions of `substra-ui` and `substrafront`.
18 |
19 | In the `substra-ui` directory:
20 |
21 | ```sh
22 | $ yarn link
23 | ```
24 |
25 | In the `substrafront` directory:
26 |
27 | ```sh
28 | $ yarn link "@substrafoundation/substra-ui"
29 | $ yarn workspace ssr-package link "@substrafoundation/substra-ui"
30 | ```
31 |
32 | Then you'll need to make your WIP content available to substrafront by either:
33 | * editing `package.json` in the `substra-ui` directory, changing `"module": "es/index.js",` into `"main": "src/index.js",`
34 | * or running `yarn build:es --watch` in the `substra-ui` directory
35 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@babel/preset-env',
4 | '@babel/preset-react',
5 | ],
6 | plugins: [
7 | '@babel/plugin-proposal-export-default-from',
8 | '@babel/plugin-proposal-export-namespace-from',
9 | 'babel-plugin-macros',
10 | '@babel/plugin-proposal-class-properties',
11 | ],
12 | env: {
13 | es: {
14 | presets: [
15 | [
16 | '@babel/preset-env',
17 | {
18 | modules: false,
19 | },
20 | ],
21 | ],
22 | },
23 | cjs: {
24 | presets: [
25 | [
26 | '@babel/preset-env',
27 | ],
28 | ],
29 | },
30 | },
31 | };
32 |
33 |
--------------------------------------------------------------------------------
/demo-public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Substra UI
8 |
9 |
10 |
11 |
12 |
13 | You need to enable JavaScript to run this app.
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | snapshotSerializers: ['jest-emotion'],
3 | moduleNameMapper: {
4 | 'react-syntax-highlighter/dist/esm/styles/prism': '/test/mocks/prismMock.js',
5 | /*
6 | Taken from https://jestjs.io/docs/en/webpack#handling-static-assets
7 | Needed for handling the font files
8 | */
9 | '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '/test/mocks/fileMock.js',
10 | '\\.(css|less)$': '/test/mocks/styleMock.js'
11 | },
12 | setupFilesAfterEnv: [
13 | '/test/setup.js',
14 | ],
15 | };
16 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@substrafoundation/substra-ui",
3 | "sideEffects": false,
4 | "version": "1.1.4",
5 | "description": "A shared UI components library for the Substra project.",
6 | "main": "cjs/index.js",
7 | "module": "es/index.js",
8 | "files": [
9 | "es/",
10 | "cjs/"
11 | ],
12 | "repository": "git@github.com:SubstraFoundation/substra-ui.git",
13 | "author": "Jérémy Morel ",
14 | "license": "Apache-2.0",
15 | "private": false,
16 | "scripts": {
17 | "build:cjs": "NODE_ENV=cjs babel src --out-dir cjs --ignore **/*.test.js,**/*.stories.js",
18 | "build:es": "NODE_ENV=es babel src --out-dir es --ignore **/*.test.js,**/*.stories.js",
19 | "build": "npm run build:cjs && npm run build:es && cp -r src/globalStyles/lato cjs/globalStyles/ && cp -r src/globalStyles/lato es/globalStyles/",
20 | "eslint": "eslint --fix src",
21 | "eslint-check": "eslint src",
22 | "test": "jest src",
23 | "storybook": "start-storybook -p 6006",
24 | "build-storybook": "build-storybook"
25 | },
26 | "dependencies": {
27 | "@emotion/core": "10.0.15",
28 | "@emotion/styled": "10.0.15",
29 | "copy-to-clipboard": "3.2.0",
30 | "diacritic": "0.0.2",
31 | "downshift": "3.2.12",
32 | "emotion": "10.0.14",
33 | "emotion-normalize": "10.1.0",
34 | "file-saver": "2.0.2",
35 | "keycode": "2.2.0",
36 | "mime-types": "2.1.24",
37 | "prop-types": "15.7.2",
38 | "react-syntax-highlighter": "11.0.2",
39 | "react-tabs": "3.0.0"
40 | },
41 | "peerDependencies": {
42 | "react": "16.x",
43 | "react-is": "16.x"
44 | },
45 | "devDependencies": {
46 | "@babel/cli": "7.5.5",
47 | "@babel/core": "7.5.5",
48 | "@babel/plugin-proposal-class-properties": "7.5.5",
49 | "@babel/plugin-proposal-export-default-from": "7.5.2",
50 | "@babel/plugin-proposal-export-namespace-from": "7.5.2",
51 | "@babel/preset-env": "7.5.5",
52 | "@babel/preset-react": "7.0.0",
53 | "@sambego/storybook-state": "1.3.6",
54 | "@storybook/addon-actions": "5.2.5",
55 | "@storybook/addon-knobs": "5.2.5",
56 | "@storybook/addon-links": "5.2.5",
57 | "@storybook/addon-storyshots": "5.2.5",
58 | "@storybook/addon-storysource": "5.2.5",
59 | "@storybook/addons": "5.2.5",
60 | "@storybook/cli": "5.2.5",
61 | "@storybook/react": "5.2.5",
62 | "@testing-library/jest-dom": "4.0.0",
63 | "@testing-library/react": "9.1.1",
64 | "@types/jest": "24.0.17",
65 | "babel-eslint": "10.0.2",
66 | "babel-jest": "24.8.0",
67 | "babel-loader": "8.0.6",
68 | "babel-plugin-emotion": "10.0.15",
69 | "babel-plugin-macros": "2.6.1",
70 | "eslint": "6.1.0",
71 | "eslint-config-airbnb": "18.0.0",
72 | "eslint-plugin-babel": "5.3.0",
73 | "eslint-plugin-import": "2.18.2",
74 | "eslint-plugin-jest": "22.15.1",
75 | "eslint-plugin-jsx-a11y": "6.2.3",
76 | "eslint-plugin-react": "7.14.3",
77 | "jest": "24.8.0",
78 | "jest-dom": "4.0.0",
79 | "jest-emotion": "10.0.14",
80 | "react-redux": "7.1.0",
81 | "react-test-renderer": "16.9.0",
82 | "redux": "4.0.4",
83 | "require-context.macro": "1.1.1"
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/components/alert.js:
--------------------------------------------------------------------------------
1 | import styled from '@emotion/styled';
2 |
3 | import {gold, iceGold} from '../variables/colors';
4 | import {spacingExtraSmall, spacingNormal, spacingSmall} from '../variables/spacing';
5 |
6 | export const alertWrapper = `
7 | background-color: ${iceGold};
8 | border: 1px solid ${gold};
9 | border-radius: 3px;
10 | min-height: 40px;
11 | display: flex;
12 | justify-content: space-between;
13 | align-items: center;
14 | margin: ${spacingNormal} 0;
15 | flex-wrap: wrap;
16 | `;
17 |
18 | export const alertTitle = `
19 | color: ${gold};
20 | font-weight: bold;
21 | margin: ${spacingSmall};
22 | `;
23 |
24 | export const AlertActions = styled('div')`
25 | margin: ${spacingExtraSmall} ${spacingExtraSmall} ${spacingExtraSmall} ${spacingSmall};
26 | `;
27 |
28 | export const alertInlineButton = `
29 | border: none;
30 | background: none;
31 | color: ${gold};
32 | text-decoration: underline;
33 | cursor: pointer;
34 | padding: ${spacingExtraSmall};
35 | `;
36 |
--------------------------------------------------------------------------------
/src/components/alert.stories.js:
--------------------------------------------------------------------------------
1 | import {storiesOf} from '@storybook/react';
2 | import React from 'react';
3 | import {withKnobs, color} from '@storybook/addon-knobs';
4 | import styled from '@emotion/styled';
5 | import {
6 | alertInlineButton,
7 | AlertActions,
8 | alertTitle,
9 | alertWrapper,
10 | } from './alert';
11 | import {darkSkyBlue, iceBlueTwo} from '../variables/colors';
12 |
13 | storiesOf('Alert', module)
14 | .addDecorator(withKnobs)
15 | .add('default', () => {
16 | const AlertWrapper = styled('div')`
17 | ${alertWrapper}
18 | `;
19 | const AlertTitle = styled('div')`
20 | ${alertTitle}
21 | `;
22 | const AlertInlineButton = styled('button')`
23 | ${alertInlineButton}
24 | `;
25 | return (
26 |
27 | This model has not been tested yet
28 |
29 | learn more
30 |
31 |
32 | );
33 | })
34 | .add('override', () => {
35 | const pColor = color('Primary color: ', iceBlueTwo);
36 | const sColor = color('Secondary color: ', darkSkyBlue);
37 | const AlertWrapper = styled('div')`
38 | ${alertWrapper};
39 | background-color: ${pColor};
40 | border: 1px solid ${sColor};
41 | `;
42 | const AlertTitle = styled('div')`
43 | ${alertTitle};
44 | color: ${sColor}
45 | `;
46 | const AlertInlineButton = styled('button')`
47 | ${alertInlineButton};
48 | color: ${sColor}
49 | `;
50 | return (
51 |
52 | This model has not been tested yet
53 |
54 | learn more
55 |
56 |
57 | );
58 | });
59 |
--------------------------------------------------------------------------------
/src/components/alert.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from '@emotion/styled';
3 | import {render, fireEvent} from '@testing-library/react';
4 | import {
5 | alertInlineButton,
6 | AlertActions,
7 | alertTitle,
8 | alertWrapper,
9 | } from './alert';
10 |
11 |
12 | test('A function can be called by the button', () => {
13 | const AlertWrapper = styled('div')`
14 | ${alertWrapper}
15 | `;
16 | const AlertTitle = styled('div')`
17 | ${alertTitle}
18 | `;
19 | const AlertInlineButton = styled('button')`
20 | ${alertInlineButton}
21 | `;
22 |
23 | const mock = jest.fn();
24 |
25 | const {getByTestId} = render(
26 |
27 | This model has not been tested yet
28 |
29 | learn more
30 |
31 | ,
32 | );
33 |
34 | expect(mock).not.toHaveBeenCalled();
35 | fireEvent.click(getByTestId('button'));
36 | expect(mock).toHaveBeenCalledTimes(1);
37 | });
38 |
--------------------------------------------------------------------------------
/src/components/chip.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from '@emotion/styled';
3 | import {spacingSmall} from '../variables/spacing';
4 | import PropTypes from '../utils/propTypes';
5 | import {fontLarge} from '../variables/font';
6 | import {darkSkyBlue} from '../variables/colors';
7 | import {ClearIcon} from '../icons';
8 |
9 | export const chipBackgroundColor = '#E0E0E0';
10 |
11 | export const ChipWrapper = styled('span')`
12 | background-color: ${chipBackgroundColor};
13 | border-radius: 30px;
14 | color: white;
15 | display: inline-flex;
16 | vertical-align: middle;
17 | margin: 3px 3px;
18 | `;
19 |
20 | export const ChipTitle = styled('div')`
21 | color: black;
22 | margin: auto;
23 | margin-left: ${spacingSmall};
24 | font-size: ${fontLarge};
25 | `;
26 |
27 | export const ChipActions = styled('div')`
28 | font-size: ${fontLarge};
29 | `;
30 |
31 | export const ChipButtonStyle = styled.button`
32 | display: inline-flex;
33 | justify-content: center;
34 | width: 16px;
35 | height: 16px;
36 | border-radius: 15px;
37 | padding: 0;
38 | border: 1px;
39 | background-color: darkgray;
40 | cursor: pointer;
41 | outline: none;
42 | margin: 6px 6px; // define the internal margin of the chip
43 |
44 | &:hover {
45 | background-color: gray;
46 | transition: background-color 200ms ease-out;
47 | }
48 | &:focus {
49 | box-shadow: 0 0 3pt 3pt ${darkSkyBlue};
50 | }
51 | `;
52 | const DefaultIcon = props => ;
53 |
54 | export const ChipButton = ({
55 | Icon, iconSize, ...props
56 | }) => (
57 |
58 |
59 |
60 | );
61 |
62 |
63 | ChipButton.propTypes = {
64 | Icon: PropTypes.component,
65 | iconSize: PropTypes.number,
66 | };
67 |
68 | ChipButton.defaultProps = {
69 | Icon: DefaultIcon,
70 | iconSize: 15,
71 | };
72 |
--------------------------------------------------------------------------------
/src/components/chip.stories.js:
--------------------------------------------------------------------------------
1 | import {storiesOf} from '@storybook/react';
2 | import React from 'react';
3 | import {
4 | ChipActions, ChipButton, ChipTitle, ChipWrapper,
5 | } from './chip';
6 |
7 | storiesOf('Chip', module)
8 | .add('default', () => (
9 |
10 | objective:key:1cdafbb018dd195690111d74916b76c9
11 |
12 |
13 |
14 |
15 | ));
16 |
--------------------------------------------------------------------------------
/src/components/chip.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {render, fireEvent} from '@testing-library/react';
3 | import {
4 | ChipActions, ChipButton, ChipTitle, ChipWrapper,
5 | } from './chip';
6 | import Clear from '../icons/clear';
7 |
8 | test('A function can be called by the button', () => {
9 | const mock = jest.fn();
10 |
11 | const {getByTestId} = render(
12 |
13 | objective:key:1cdafbb018dd195690111d74916b76c9
14 |
15 |
16 |
17 | ,
18 | );
19 | expect(mock).not.toHaveBeenCalled();
20 | fireEvent.click(getByTestId('button'));
21 | expect(mock).toHaveBeenCalledTimes(1);
22 | });
23 |
--------------------------------------------------------------------------------
/src/components/codeSample.js:
--------------------------------------------------------------------------------
1 | /* global Blob */
2 | import React, {Component} from 'react';
3 | import {css} from 'emotion';
4 | import styled from '@emotion/styled';
5 | import mime from 'mime-types';
6 | import {Prism as SyntaxHighlighter} from 'react-syntax-highlighter';
7 | import {ghcolors} from 'react-syntax-highlighter/dist/esm/styles/prism';
8 | import {saveAs} from 'file-saver';
9 | import PropTypes from '../utils/propTypes';
10 | import {fontNormalMonospace, monospaceFamily} from '../variables/font';
11 | import {spacingExtraSmall, spacingNormal, spacingSmall} from '../variables/spacing';
12 | import {ice, iceBlue} from '../variables/colors';
13 | import {IconButton} from './iconButton';
14 | import {Collapse, DownloadSimple, Expand} from '../icons';
15 |
16 | const customStyle = {
17 | ...ghcolors,
18 | 'pre[class*="language-"]': {
19 | ...ghcolors['pre[class*="language-"]'],
20 | border: 'none',
21 | margin: 0,
22 | padding: `${spacingSmall}`,
23 | fontFamily: monospaceFamily,
24 | fontSize: fontNormalMonospace,
25 | },
26 | 'code[class*="language-"]': {
27 | ...ghcolors['code[class*="language-"]'],
28 | fontFamily: monospaceFamily,
29 | fontSize: fontNormalMonospace,
30 | },
31 | };
32 |
33 | const lineNumberStyle = {
34 | userSelect: 'none',
35 | };
36 |
37 | const FilenameWrapper = styled('div')`
38 | padding: ${spacingExtraSmall} ${spacingNormal};
39 | `;
40 |
41 | const ActionsWrapper = styled('div')`
42 | padding: ${spacingExtraSmall};
43 | `;
44 |
45 | const marginLeft = css`
46 | margin-left: ${spacingExtraSmall};
47 | `;
48 |
49 | class CodeSample extends Component {
50 | state = {
51 | collapsed: true,
52 | };
53 |
54 | downloadCode = e => {
55 | e.stopPropagation();
56 | const {codeString, filename} = this.props;
57 | const jsonBlob = new Blob([codeString], {type: mime.lookup(filename) || 'text/plain'});
58 | saveAs(jsonBlob, filename);
59 | };
60 |
61 | toggleCollapsed = e => {
62 | e.stopPropagation();
63 | this.setState(state => ({collapsed: !state.collapsed}));
64 | };
65 |
66 | render() {
67 | const {
68 | filename, language, codeString, collapsible, className,
69 | } = this.props;
70 | const {collapsed} = this.state;
71 |
72 | const Wrapper = styled('div')`
73 | border: 1px solid ${ice};
74 | border-radius: 3px;
75 | display: flex;
76 | flex-direction: column;
77 | ${collapsible && collapsed && 'max-height: 150px;'}
78 | `;
79 |
80 | const Header = styled('div')`
81 | display: flex;
82 | justify-content: space-between;
83 | align-items: center;
84 | border-bottom: 1px solid ${ice};
85 | background-color: ${iceBlue};
86 | min-height: 40px;
87 | font-family: ${monospaceFamily};
88 | font-size: ${fontNormalMonospace};
89 | ${collapsible && 'cursor: pointer;'}
90 | `;
91 |
92 | return (
93 |
94 |
95 |
96 | {filename}
97 |
98 |
99 |
105 | {collapsible && (
106 |
113 | )}
114 |
115 |
116 |
122 | {codeString}
123 |
124 |
125 | );
126 | }
127 | }
128 |
129 | CodeSample.propTypes = {
130 | codeString: PropTypes.string.isRequired,
131 | filename: PropTypes.string.isRequired,
132 | language: PropTypes.string.isRequired,
133 | collapsible: PropTypes.bool,
134 | className: PropTypes.string,
135 | };
136 |
137 | CodeSample.defaultProps = {
138 | collapsible: false,
139 | className: '',
140 | };
141 |
142 | export default CodeSample;
143 |
--------------------------------------------------------------------------------
/src/components/codeSample.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {storiesOf} from '@storybook/react';
3 |
4 | import CodeSample from './codeSample';
5 |
6 | const text = `
7 | import os
8 | import pandas as pd
9 | import random
10 | import string
11 | import numpy as np
12 | import logging
13 |
14 | import substratools as tools
15 |
16 |
17 | class Opener(tools.Opener):
18 | def get_X(self, folders):
19 | logging.info(folders)
20 | datas = []
21 | for folder in folders:
22 | datas.append(np.load('{}/X.npy'.format(folder)))
23 | return np.concatenate(datas, axis=0)
24 |
25 | def get_y(self, folders):
26 | y = []
27 | for folder in folders:
28 | y.append(np.load('{}/y.npy'.format(folder)))
29 | return np.concatenate(y, axis=0)
30 |
31 | def save_pred(self, y_pred, path):
32 | with open(path, 'wb') as f:
33 | np.save(f, y_pred)
34 |
35 | def get_pred(self, path):
36 | return np.load(path)
37 |
38 | def fake_X(self):
39 | data = np.random.random((5, 1000, 2051))
40 | return data
41 |
42 | def fake_y(self):
43 | y = np.random.randint(0, high=2, size=(5,))
44 | return y
45 | `;
46 |
47 | storiesOf('CodeSample', module)
48 | .add('default', () => (
49 |
50 | ))
51 | .add('collapsible', () => (
52 |
53 | ));
54 |
--------------------------------------------------------------------------------
/src/components/codeSample.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {render, fireEvent} from '@testing-library/react';
3 | import {saveAs} from 'file-saver';
4 | import CodeSample from './codeSample';
5 |
6 | const codeString = 'toto'; // the default text used in the tests below
7 | const filename = 'opener.py'; // the default filename used in the tests below
8 | const language = 'python'; // the default language used in the tests below
9 |
10 | /* requirements for the "Download on click" test */
11 | jest.mock('file-saver', () => ({saveAs: jest.fn()}));
12 |
13 | test('Change collapse/expand status on click', () => {
14 | const {getByTestId} = render( );
15 | let button = getByTestId('toggle');
16 | expect(button.title).toEqual('Expand'); // check if the button has the title "Expand"
17 | let wrapper = getByTestId('wrapper');
18 | expect(wrapper).toHaveStyle('max-height: 150px'); // check if the wrapper is collapsed through the css
19 | fireEvent.click(button); // simulate a click
20 | button = getByTestId('toggle');
21 | expect(button.title).toEqual('Collapse'); // check if the button has the title "Collapse"
22 | wrapper = getByTestId('wrapper');
23 | expect(wrapper).not.toHaveStyle('max-height: 150px'); // check if the wrapper is expanded through the css
24 | });
25 |
26 | test('Download on click', () => {
27 | const {getByTestId} = render( );
28 | const button = getByTestId('download');
29 | fireEvent.click(button); // simulate a click
30 | expect(saveAs).toHaveBeenCalledTimes(1); // then we verify that the saveAs function was correctly called one time only
31 | expect(saveAs).toHaveBeenCalledWith({content: [codeString], options: {type: 'text/plain'}}, filename); // check the params the download was called with
32 | });
33 |
34 | test('Has a collapse/expand button', () => {
35 | const noButtonCpt = render( );
36 | expect(() => {
37 | noButtonCpt.getByTestId('toggle');
38 | }).toThrow();
39 | const withButtonCpt = render( );
40 | expect(withButtonCpt.getByTestId('toggle')).toBeTruthy();
41 | });
42 |
--------------------------------------------------------------------------------
/src/components/copyInput.js:
--------------------------------------------------------------------------------
1 | import React, {Component, Fragment} from 'react';
2 | import styled from '@emotion/styled';
3 | import {css} from 'emotion';
4 | import {noop} from 'lodash';
5 |
6 | import PropTypes from '../utils/propTypes';
7 | import {CopySimple, Check} from '../icons';
8 | import {IconButton} from './iconButton';
9 | import {ice, tealish} from '../variables/colors';
10 | import {fontNormalMonospace, monospaceFamily} from '../variables/font';
11 | import {spacingSmall} from '../variables/spacing';
12 |
13 |
14 | const Wrapper = styled('div')`
15 | position: relative;
16 | `;
17 |
18 | const Input = styled('input')`
19 | width: 100%;
20 | background-color: transparent;
21 | color: inherit;
22 | font-family: ${monospaceFamily};
23 | font-size: ${fontNormalMonospace};
24 | text-overflow: ellipsis;
25 | border: 1px solid ${ice};
26 | height: 30px;
27 | border-radius: 15px;
28 | line-height: 28px;
29 | padding-left: ${({isPrompt}) => isPrompt ? `calc(${spacingSmall} + 1em)` : spacingSmall};
30 | padding-right: 35px;
31 | `;
32 |
33 | const button = css`
34 | position: absolute;
35 | top: 0;
36 | right: 0;
37 | `;
38 |
39 | const prompt = css`
40 | position: absolute;
41 | top: 0;
42 | left: 0;
43 | height: 30px;
44 | line-height: 30px;
45 | padding-left: ${spacingSmall};
46 | pointer-events: none;
47 | `;
48 |
49 | const Prompt = () => $
;
50 |
51 | const DefaultSuccessIcon = props => ;
52 |
53 | class CopyInput extends Component {
54 | state = {
55 | clicked: false,
56 | };
57 |
58 | constructor(props) {
59 | super(props);
60 | this.inputRef = React.createRef();
61 | }
62 |
63 | componentWillUnmount() {
64 | clearTimeout(this.timeout);
65 | }
66 |
67 | copy = () => {
68 | const {value, addNotification, addNotificationMessage} = this.props;
69 | addNotification(value, addNotificationMessage);
70 | // animate icon
71 | this.setState({clicked: true});
72 | clearTimeout(this.timeout);
73 | this.timeout = setTimeout(() => {
74 | this.setState({clicked: false});
75 | }, 2000);
76 | };
77 |
78 | select = () => {
79 | this.inputRef.current.select();
80 | };
81 |
82 | icon = () => {
83 | const {value, CopyIcon, SuccessIcon} = this.props;
84 | const clicked = this.state.clicked;
85 | const hasChanged = value !== this.previousValue;
86 | if (hasChanged) {
87 | clearTimeout(this.timeout);
88 | }
89 | this.previousValue = value;
90 |
91 | const copySimple = css`
92 | opacity: ${!hasChanged && clicked ? 0 : 1};
93 | ${!hasChanged && 'transition: opacity 200ms ease-out;'}
94 | `;
95 |
96 | const check = css`
97 | position: absolute;
98 | opacity: ${!hasChanged && clicked ? 1 : 0};
99 | ${!hasChanged && 'transition: opacity 200ms ease-out;'}
100 | `;
101 |
102 | return ({width, height}) => (
103 |
104 |
109 |
114 |
115 | );
116 | };
117 |
118 | render() {
119 | const {value, isPrompt} = this.props;
120 | return (
121 |
122 | {isPrompt && }
123 |
131 |
137 |
138 | );
139 | }
140 | }
141 |
142 | CopyInput.propTypes = {
143 | value: PropTypes.string,
144 | isPrompt: PropTypes.bool,
145 | addNotification: PropTypes.func,
146 | addNotificationMessage: PropTypes.string,
147 | CopyIcon: PropTypes.component,
148 | SuccessIcon: PropTypes.component,
149 | };
150 |
151 | CopyInput.defaultProps = {
152 | value: '',
153 | isPrompt: false,
154 | addNotification: noop,
155 | addNotificationMessage: 'Copied!',
156 | CopyIcon: CopySimple,
157 | SuccessIcon: DefaultSuccessIcon,
158 | };
159 |
160 | export default CopyInput;
161 |
--------------------------------------------------------------------------------
/src/components/copyInput.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | import {withKnobs, text, boolean} from '@storybook/addon-knobs/react';
4 |
5 | import CopyInput from './copyInput';
6 | import Book from '../icons/book';
7 | import Model from '../icons/model';
8 |
9 |
10 | storiesOf('CopyInput', module)
11 | .addDecorator(withKnobs)
12 | .add('default', () => {
13 | const value = text('value', 'Lorem ipsum dolor sit amet');
14 | const addNotificationMessage = text('addNotificationMessage');
15 | const addNotification = (v, t) => console.log(`value: ${v}\nmessage: ${t}`);
16 | const isPrompt = boolean('isPrompt', false);
17 |
18 | return (
19 |
25 | );
26 | })
27 | .add('color override', () => {
28 | const CopyIcon = props => ;
29 | const SuccessIcon = props => ;
30 | return ;
31 | });
32 |
--------------------------------------------------------------------------------
/src/components/copyInput.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {render, fireEvent} from '@testing-library/react';
3 |
4 | import CopyInput from './copyInput';
5 |
6 | test('It calls the addNotification method', () => {
7 | const addNotification = jest.fn();
8 | const value = 'my value';
9 | const addNotificationMessage = 'Not the default message';
10 |
11 | const {getByTestId, rerender} = render(
12 | ,
16 | );
17 | expect(addNotification).not.toHaveBeenCalled();
18 | fireEvent.click(getByTestId('button'));
19 | expect(addNotification).toHaveBeenCalledTimes(1);
20 | expect(addNotification).toHaveBeenLastCalledWith(value, 'Copied!');
21 |
22 | rerender(
23 | ,
28 | );
29 | fireEvent.click(getByTestId('button'));
30 | expect(addNotification).toHaveBeenCalledTimes(2);
31 | expect(addNotification).toHaveBeenLastCalledWith(value, addNotificationMessage);
32 | });
33 |
34 | test('It displays a prompt', () => {
35 | const {getByText, rerender} = render( );
36 | expect(getByText('$')).toBeDefined();
37 |
38 | rerender( );
39 | expect(() => getByText('$')).toThrow();
40 | });
41 |
--------------------------------------------------------------------------------
/src/components/copyNotification/copyNotification.js:
--------------------------------------------------------------------------------
1 | import React, {Component, Fragment} from 'react';
2 | import {css} from 'emotion';
3 | import copy from 'copy-to-clipboard';
4 |
5 | import {Check} from '../../icons';
6 | import SnackbarContent from './snackbarContent';
7 |
8 | import {slate, tealish} from '../../variables/colors';
9 | import PropTypes from '../../utils/propTypes';
10 |
11 | export const middle = `
12 | display: inline-block;
13 | vertical-align: top;
14 | `;
15 |
16 | export const snackbarContentCSS = `
17 | color: ${tealish};
18 |
19 | @media (min-width: 960px) {
20 | min-width: 200px;
21 | }
22 | `;
23 |
24 | export const clipboardContentCSS = `
25 | ${middle};
26 | margin-left: 15px;
27 | input {
28 | display: block;
29 | padding: 3px 0;
30 | border: 1px solid #9b9b9b;
31 | color: #9b9b9b;
32 | background-color: transparent;
33 | outline: none;
34 | width: 100%;
35 | }
36 |
37 | p {
38 | color: ${slate};
39 | font-size: 13px;
40 | margin: 4px 0 0;
41 | }
42 | `;
43 |
44 | const withAddNotification = (WrappedComponent, Icon = Check) => {
45 | class CopyNotification extends Component {
46 | state = {
47 | open: false,
48 | key: '',
49 | text: '',
50 | };
51 |
52 | componentWillUnmount() {
53 | if (this.timeout) {
54 | clearTimeout(this.timeout);
55 | }
56 | if (this.queueTimeout) {
57 | clearTimeout(this.queueTimeout);
58 | }
59 | }
60 |
61 | processNotificationQueue = () => {
62 | const {delay: {display}} = this.props;
63 | this.setState(state => ({
64 | ...state,
65 | ...this.queuedNotification,
66 | open: true,
67 | }));
68 | this.timeout = setTimeout(() => {
69 | this.setState(state => ({
70 | ...state,
71 | open: false,
72 | }));
73 | }, display);
74 | };
75 |
76 | addNotification = (key, text) => {
77 | const {open} = this.state;
78 | const {delay: {animation}} = this.props;
79 | copy(key);
80 | this.queuedNotification = {
81 | key,
82 | text,
83 | };
84 | if (this.timeout) {
85 | clearTimeout(this.timeout);
86 | }
87 | if (open) {
88 | this.setState(state => ({
89 | ...state,
90 | open: false,
91 | }));
92 | this.queueTimeout = setTimeout(() => {
93 | this.queueTimeout = undefined;
94 | this.processNotificationQueue();
95 | }, animation);
96 | }
97 | else if (!this.queueTimeout) { // we click when the notification is closing (open = false but animation still running)
98 | this.processNotificationQueue();
99 | }
100 | };
101 |
102 | snackbar = () => {
103 | const {open} = this.state;
104 | const {delay: {animation}} = this.props;
105 | return css`
106 | position: fixed;
107 | left: 25px;
108 | bottom: ${open ? '25px' : '-80px'};
109 | opacity: ${open ? 1 : 0};
110 | transition: all ${animation}ms linear;
111 | `;
112 | };
113 |
114 | render() {
115 | const {
116 | text,
117 | key,
118 | } = this.state;
119 | const {clipboardContentCSS, snackbarContentCSS} = this.props;
120 |
121 | return (
122 |
123 |
124 |
125 |
129 |
133 |
134 |
135 |
136 |
137 | {text}
138 |
139 |
140 |
141 | )}
142 | />
143 |
144 |
145 |
146 | );
147 | }
148 | }
149 | CopyNotification.defaultProps = {
150 | delay: {
151 | animation: 150,
152 | display: 2500,
153 | },
154 | clipboardContentCSS,
155 | snackbarContentCSS,
156 | };
157 |
158 | CopyNotification.propTypes = {
159 | delay: PropTypes.objectOf(PropTypes.number),
160 | clipboardContentCSS: PropTypes.string,
161 | snackbarContentCSS: PropTypes.string,
162 | };
163 |
164 | return CopyNotification;
165 | };
166 |
167 | export default withAddNotification;
168 |
--------------------------------------------------------------------------------
/src/components/copyNotification/copyNotification.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | import {withKnobs, color} from '@storybook/addon-knobs';
4 | import PropTypes from '../../utils/propTypes';
5 | import withAddNotification from './copyNotification';
6 | import Check from '../../icons/check';
7 | import {darkSkyBlue} from '../../variables/colors';
8 |
9 | storiesOf('CopyNotification', module)
10 | .addDecorator(withKnobs)
11 | .add('default', () => {
12 | const addNotificationButton = ({addNotification}) => (
13 | addNotification(Math.random().toString(36).substring(2, 15), Math.random().toString(36).substring(2, 15))}>
14 | Add notification
15 |
16 | );
17 |
18 | addNotificationButton.propTypes = {
19 | addNotification: PropTypes.func.isRequired,
20 | };
21 |
22 | const Button = withAddNotification(addNotificationButton);
23 | return ;
24 | })
25 | .add('animation', () => {
26 | const addNotificationButton = ({addNotification}) => (
27 | addNotification(Math.random().toString(36).substring(2, 15), Math.random().toString(36).substring(2, 15))}>
28 | Add notification
29 |
30 | );
31 |
32 | addNotificationButton.propTypes = {
33 | addNotification: PropTypes.func.isRequired,
34 | };
35 |
36 | const Button = withAddNotification(addNotificationButton);
37 | const delay = {animation: 300, display: 1000};
38 | return ;
39 | })
40 | .add('color override', () => {
41 | const checkColor = color('Color: ', darkSkyBlue);
42 | const addNotificationButton = ({addNotification}) => (
43 | addNotification('1cdafbb018dd195690111d74916b76c96892d897ec3587c814f287946db446c3', 'Objective\'s key successfully copied to clipboard!')}>
44 | Add notification
45 |
46 | );
47 |
48 | addNotificationButton.propTypes = {
49 | addNotification: PropTypes.func.isRequired,
50 | };
51 |
52 | const OwkestraCheck = () => ;
53 |
54 | const Button = withAddNotification(addNotificationButton, OwkestraCheck);
55 | return ;
56 | });
57 |
--------------------------------------------------------------------------------
/src/components/copyNotification/copyNotification.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {fireEvent, render} from '@testing-library/react';
3 | import copy from 'copy-to-clipboard';
4 | import PropTypes from '../../utils/propTypes';
5 | import withAddNotification from './copyNotification';
6 |
7 | jest.mock('copy-to-clipboard');
8 |
9 | const addNotificationButton = ({addNotification}) => (
10 | addNotification('key', 'text')}
14 | >
15 | Add notification
16 |
17 | );
18 |
19 | addNotificationButton.propTypes = {
20 | addNotification: PropTypes.func.isRequired,
21 | };
22 |
23 | const Main = withAddNotification(addNotificationButton);
24 |
25 | test('The notification appears', () => {
26 | const {getByTestId} = render( );
27 |
28 | expect(getByTestId('notification')).toHaveStyle('bottom: -80px');
29 | fireEvent.click(getByTestId('button'));
30 | expect(getByTestId('notification')).toHaveStyle('bottom: 25px');
31 | });
32 |
33 | test('Key copied to the clipboard', () => {
34 | copy.mockReset();
35 | const {getByTestId} = render( );
36 |
37 | expect(copy).not.toHaveBeenCalled();
38 | fireEvent.click(getByTestId('button'));
39 | expect(copy).toHaveBeenCalledTimes(1);
40 | expect(copy).toHaveBeenCalledWith('key');
41 | });
42 |
--------------------------------------------------------------------------------
/src/components/copyNotification/snackbarContent.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import {css} from 'emotion';
4 | import {white} from '../../variables/colors';
5 | import {fontNormal} from '../../variables/font';
6 |
7 | const paper = css`
8 | display: flex;
9 | align-items: center;
10 | flex-wrap: wrap;
11 | padding: 6px 24px;
12 | min-width: 288px;
13 | max-width: 568px;
14 | border-radius: 5px;
15 | box-shadow: 0 4px 8px 1px #bdbdbd;
16 | background-color: ${white};
17 | `;
18 |
19 | const messagePadding = css`
20 | padding: 8px 0;
21 | font-size: ${fontNormal};
22 | line-height: 16px;
23 | `;
24 |
25 | function SnackbarContent(props) {
26 | const {
27 | message,
28 | className,
29 | ...other
30 | } = props;
31 | return (
32 |
36 |
37 | {message}
38 |
39 |
40 | );
41 | }
42 |
43 | SnackbarContent.propTypes = {
44 | /**
45 | * The CSS class name of the wrapper element.
46 | */
47 | className: PropTypes.string,
48 | /**
49 | * The message to display.
50 | */
51 | message: PropTypes.node,
52 | key: PropTypes.string,
53 | };
54 |
55 | SnackbarContent.defaultProps = {
56 | className: '',
57 | message: null,
58 | key: '',
59 | };
60 |
61 | export default SnackbarContent;
62 |
--------------------------------------------------------------------------------
/src/components/iconButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {css} from 'emotion';
3 | import PropTypes from '../utils/propTypes';
4 | import {ice} from '../variables/colors';
5 |
6 | const roundButton = css`
7 | display: inline-flex;
8 | align-items: center;
9 | justify-content: center;
10 | width: 30px;
11 | height: 30px;
12 | border-radius: 15px;
13 | border: 1px solid ${ice};
14 | padding: 0;
15 | background: none;
16 | cursor: pointer;
17 |
18 | &:hover {
19 | background-color: ${ice};
20 | transition: background-color 200ms ease-out;
21 | }
22 | `;
23 |
24 | export const RoundButton = ({className, ...props}) => (
25 |
30 | );
31 |
32 | RoundButton.propTypes = {
33 | className: PropTypes.string,
34 | };
35 |
36 | RoundButton.defaultProps = {
37 | className: '',
38 | };
39 |
40 | export const IconButton = ({Icon, iconSize, ...props}) => (
41 |
42 |
43 |
44 | );
45 |
46 | IconButton.propTypes = {
47 | Icon: PropTypes.component.isRequired,
48 | iconSize: PropTypes.number,
49 | };
50 |
51 | IconButton.defaultProps = {
52 | iconSize: 15,
53 | };
54 |
--------------------------------------------------------------------------------
/src/components/iconButton.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {storiesOf} from '@storybook/react';
3 |
4 | import {IconButton} from './iconButton';
5 | import Book from '../icons/book';
6 |
7 | storiesOf('IconButton', module)
8 | .add('default', () => );
9 |
--------------------------------------------------------------------------------
/src/components/panel.js:
--------------------------------------------------------------------------------
1 | import styled from '@emotion/styled';
2 | import {ice, white} from '../variables/colors';
3 | import {spacingNormal} from '../variables/spacing';
4 |
5 | export const PanelWrapper = styled('div')`
6 | flex-grow: 1;
7 | display: flex;
8 | flex-direction: column;
9 | align-items: stretch;
10 | overflow: hidden;
11 | `;
12 |
13 | export const PanelTop = styled('div')`
14 | background-color: ${white};
15 | border-bottom: 1px solid ${ice};
16 | padding: 0 ${spacingNormal};
17 | height: 40px;
18 | display: flex;
19 | align-items: center;
20 | flex-shrink: 0;
21 | flex-grow: 0;
22 | `;
23 |
24 | export const PanelContent = styled('div')`
25 | overflow: auto;
26 | flex-grow: 1;
27 | `;
28 |
--------------------------------------------------------------------------------
/src/components/panel.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {storiesOf} from '@storybook/react';
3 |
4 | import {PanelContent, PanelTop, PanelWrapper} from './panel';
5 |
6 | storiesOf('Panel', module)
7 | .add('default', () => (
8 |
9 | Top
10 | Content
11 |
12 | ));
13 |
--------------------------------------------------------------------------------
/src/components/roundedButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {css} from 'emotion';
3 | import styled from '@emotion/styled';
4 | import {blueGrey, ice, slate} from '../variables/colors';
5 | import {spacingExtraSmall, spacingNormal} from '../variables/spacing';
6 | import PropTypes from '../utils/propTypes';
7 |
8 | export const Button = styled.button`
9 | color: ${slate};
10 | height: 30px;
11 | line-height: 28px;
12 | border-radius: 15px;
13 | border: 1px solid ${ice};
14 | padding: 0 ${spacingNormal};
15 | background: none;
16 | cursor: pointer;
17 |
18 | &:not(:disabled):hover {
19 | background-color: ${ice};
20 | transition: background-color 200ms ease-out;
21 | }
22 |
23 | &:disabled {
24 | color: ${blueGrey}
25 | }
26 | `;
27 |
28 | const icon = css`
29 | margin-right: ${spacingExtraSmall};
30 | margin-bottom: -3px;
31 | `;
32 |
33 | export const RoundedButton = ({
34 | disabled, Icon, Button, iconColor, iconColorDisabled, children, ...props
35 | }) => (
36 |
37 | {Icon && (
38 |
44 | )}
45 | {children}
46 |
47 | );
48 |
49 | RoundedButton.propTypes = {
50 | disabled: PropTypes.bool,
51 | iconColor: PropTypes.string,
52 | iconColorDisabled: PropTypes.string,
53 | Icon: PropTypes.component,
54 | children: PropTypes.node,
55 | Button: PropTypes.component,
56 | };
57 |
58 | RoundedButton.defaultProps = {
59 | disabled: false,
60 | iconColor: slate,
61 | iconColorDisabled: blueGrey,
62 | Icon: null,
63 | children: null,
64 | Button,
65 | };
66 |
--------------------------------------------------------------------------------
/src/components/roundedButton.stories.js:
--------------------------------------------------------------------------------
1 | import React, {Fragment} from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | import {action} from '@storybook/addon-actions';
4 |
5 | import {RoundedButton} from './roundedButton';
6 | import Book from '../icons/book';
7 |
8 | const onClick = action('onClick');
9 |
10 | storiesOf('RoundedButton', module)
11 | .add('default', () => (
12 |
13 |
14 | Text only
15 |
16 |
17 | Icon + text
18 |
19 |
20 | ))
21 | .add('disabled', () => (
22 |
23 |
24 | Text only
25 |
26 |
27 | Icon + text
28 |
29 |
30 | ))
31 | .add('icon colors', () => (
32 |
33 |
39 | Normal
40 |
41 |
48 | Disabled
49 |
50 |
51 | ));
52 |
--------------------------------------------------------------------------------
/src/components/roundedButton.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {render, fireEvent} from '@testing-library/react';
3 | import {RoundedButton} from './roundedButton';
4 |
5 | test('It should handle onClick', () => {
6 | const callback = jest.fn();
7 | const {container} = render( );
8 | const button = container.querySelector('button');
9 | fireEvent.click(button);
10 | expect(callback).toHaveBeenCalled();
11 | });
12 |
13 | test('It should have a disabled state', () => {
14 | const callback = jest.fn();
15 | const {container} = render( );
16 | const button = container.querySelector('button');
17 | expect(button.disabled).toBeTruthy();
18 | fireEvent.click(button);
19 | expect(callback).not.toHaveBeenCalled();
20 | });
21 |
--------------------------------------------------------------------------------
/src/components/searchBar/overrides/input.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import styled from '@emotion/styled';
3 | import {css} from 'emotion';
4 | import {noop} from 'lodash';
5 |
6 | import PropTypes from '../../../utils/propTypes';
7 |
8 | import {match, parse} from './utils/autosuggest-highlight';
9 |
10 | import {ice} from '../../../variables/colors';
11 | import {fontNormal} from '../../../variables/font';
12 |
13 | const Logic = styled('span')`
14 | color: #1935a7;
15 | margin: 0 auto;
16 | font-weight: bold;
17 | `;
18 |
19 | const placeholder = `
20 | color: darkgray;
21 | opacity: 0.7;
22 | `;
23 |
24 | const styles = {
25 | /* Styles applied to the root element. */
26 | root: disabled => css`
27 | // Mimics the default input display property used by browsers for an input.
28 | display: block;
29 | position: relative;
30 | cursor: text;
31 | font-family: 'Lato';
32 | font-size: ${fontNormal};
33 | line-height: 1.1875em; // Reset (19px), match the native input line-height
34 | ${disabled ? `
35 | color: ${ice};
36 | ` : ''}
37 | `,
38 | /* Styles applied to the root element if `fullWidth={true}`. */
39 | fullWidth: css`
40 | width: 100%;
41 | `,
42 | /* Styles applied to the `input` Wrapper element. */
43 | inputWrapper: css`
44 | width: 200px;
45 | display: inline-block;
46 | `,
47 | /* Styles applied to the `input` element. */
48 | input: disabled => css`
49 | font: inherit;
50 | color: currentColor;
51 | padding: 0;
52 | border: 0;
53 | box-sizing: content-box;
54 | vertical-align: middle;
55 | background: none;
56 | margin: 0; // Reset for Safari
57 | // Remove grey highlight
58 | -webkit-tap-highlight-color: transparent;
59 | display: block;
60 | // Make the flex item shrink with Firefox
61 | min-width: 0;
62 | flex-grow: 1;
63 | &::-webkit-input-placeholder {${placeholder}};
64 | &::-moz-placeholder {${placeholder}}; // Firefox 19+
65 | &:-ms-input-placeholder {${placeholder}}; // IE 11
66 | &::-ms-input-placeholder {${placeholder}}; // Edge
67 | &:focus {
68 | outline: 0;
69 | }
70 | // Reset Firefox invalid required input style
71 | &:invalid {
72 | box-shadow: none;
73 | }
74 | &::-webkit-search-decoration {
75 | // Remove the padding when type=search.
76 | -webkit-appearance: none;
77 | },
78 |
79 | ${disabled ? 'opacity: 1;' : ''} // Reset iOS opacity
80 | `,
81 | /* Styles applied to the `input` element if `margin="dense"`. */
82 | inputMarginDense: css`
83 | padding-top: 3px;
84 | `,
85 | /* Styles applied to the `input` element if `type` is not "text"`. */
86 | inputType: css`
87 | // type="date" or type="time", etc. have specific styles we need to reset.
88 | height: 1.1875em; // Reset (19px), match the native input line-height
89 | `,
90 | /* Styles applied to the `input` element if `type="search"`. */
91 | inputTypeSearch: css`
92 | // Improve type search style.
93 | -moz-appearance: textfield;
94 | -webkit-appearance: textfield;
95 | `,
96 | paper: css`
97 | color: black;
98 | background-color: white;
99 | box-shadow: 0px 1px 2px 0px darkgray;
100 | `,
101 | popperOpen: css`
102 | z-index: 2;
103 | position: absolute;
104 | `,
105 | popperClose: css`
106 | display: none;
107 | `,
108 | highligthed: css`
109 | font-weight: bold;
110 | `,
111 | };
112 |
113 | const menuItemDefaultCss = `
114 | line-height: 50px;
115 | padding-right: 10px;
116 | padding-left: 10px;
117 | min-width: 100px;
118 | cursor: pointer;
119 | &:hover {
120 | background-color: #dfdfdf;
121 | };
122 | `;
123 |
124 | class Input extends Component {
125 | input = null; // Holds the input reference
126 |
127 | handleFocus = event => {
128 | // Fix a bug with IE11 where the focus/blur events are triggered
129 | // while the input is disabled.
130 |
131 | if (this.props.onFocus) {
132 | this.props.onFocus(event);
133 | }
134 | };
135 |
136 | handleBlur = event => {
137 | if (this.props.onBlur) {
138 | this.props.onBlur(event);
139 | }
140 | };
141 |
142 | handleChange = event => {
143 | // Perform in the willUpdate
144 | if (this.props.onChange) {
145 | this.props.onChange(event);
146 | }
147 | };
148 |
149 | handleRefInput = ref => {
150 | this.inputRef = ref;
151 | let refProp;
152 |
153 | if (this.props.inputRef) {
154 | refProp = this.props.inputRef;
155 | }
156 | else if (this.props.inputProps && this.props.inputProps.ref) {
157 | refProp = this.props.inputProps.ref;
158 | }
159 |
160 | if (refProp) {
161 | if (typeof refProp === 'function') {
162 | refProp(ref);
163 | }
164 | else {
165 | refProp.current = ref;
166 | }
167 | }
168 | };
169 |
170 | menuItem = isLogic => isLogic ? css`
171 | ${menuItemDefaultCss};
172 | text-align: center;
173 | ` : css`${menuItemDefaultCss}`
174 | ;
175 |
176 | menuItemHighlighted = isLogic => isLogic ? css`
177 | ${menuItemDefaultCss}
178 | text-align: center;
179 | background-color: #dfdfdf;
180 | ` : css`
181 | ${menuItemDefaultCss};
182 | background-color: #dfdfdf;
183 | `;
184 |
185 | render() {
186 | const {
187 | className: classNameProp, // eslint-disable-line no-unused-vars
188 | classes,
189 | endAdornment,
190 | inputComponent,
191 | inputProps: {className: inputPropsClassName, ...inputPropsProp} = {}, // eslint-disable-line no-unused-vars
192 | onKeyDown,
193 | placeholder,
194 | startAdornment,
195 | value,
196 | isOpen,
197 | getItemProps,
198 | inputValue,
199 | highlightedIndex,
200 | suggestions,
201 | inputRef, // eslint-disable-line no-unused-vars
202 | inputWrapperRef, // eslint-disable-line no-unused-vars
203 | ...other
204 | } = this.props;
205 |
206 | const className = css`
207 | ${styles.formControl};
208 | `;
209 | const inputClassName = css`
210 | ${styles.input()};
211 | ${classes.input}
212 | `;
213 |
214 | const inputwrapperClassName = css`
215 | ${styles.inputWrapper};
216 | ${classes.inputWrapper}
217 | `;
218 |
219 | const InputComponent = 'input';
220 | let inputProps = {
221 | ...inputPropsProp,
222 | ref: this.handleRefInput,
223 | };
224 |
225 | if (inputComponent) {
226 | inputProps = {
227 | // Rename ref to inputRef as we don't know the
228 | // provided `inputComponent` structure.
229 | inputRef: this.handleRefInput,
230 | ...inputProps,
231 | ref: null,
232 | };
233 | }
234 |
235 | return (
236 |
237 | {startAdornment}
238 |
239 |
250 |
254 |
255 | {suggestions(inputValue).map((suggestion, index) => {
256 | const isHighlighted = highlightedIndex === index;
257 | const itemProps = getItemProps({item: suggestion});
258 |
259 | const highlighted = parse(suggestion.label, match(suggestion.label, inputValue, {insideWords: true}));
260 |
261 | return (
262 |
267 | {
268 | suggestion.isLogic
269 | ? (
270 |
271 | {suggestion.label}
272 |
273 | )
274 |
275 | : highlighted.map((o, i) => o.highlight
276 | ? (
277 |
278 | {decodeURIComponent(o.text)}
279 |
280 | )
281 | : decodeURIComponent(o.text))
282 | }
283 |
284 | );
285 | })}
286 |
287 |
288 |
289 | {endAdornment}
290 |
291 | );
292 | }
293 | }
294 |
295 | Input.propTypes = {
296 | /**
297 | * Override or extend the styles applied to the component.
298 | * See [CSS API](#css-api) below for more details.
299 | */
300 | classes: PropTypes.shape({
301 | input: PropTypes.string,
302 | inputWrapper: PropTypes.string,
303 | }).isRequired,
304 | /**
305 | * The CSS class name of the wrapper element.
306 | */
307 | className: PropTypes.string,
308 | /**
309 | * End `
310 | InputAdornment` for this component.
311 | */
312 | endAdornment: PropTypes.node,
313 | /**
314 | * The component used for the native input.
315 | * Either a string to use a DOM element or a component.
316 | */
317 | inputComponent: PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.shape({})]),
318 | /**
319 | * Attributes applied to the `
320 | input` element.
321 | */
322 | inputProps: PropTypes.shape({
323 | ref: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({})]),
324 | }),
325 | /**
326 | * Use that property to pass a ref callback to the native input component.
327 | */
328 | inputRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({})]),
329 | /**
330 | * Use that property to pass a ref callback to wrapper of the native input component.
331 | */
332 | inputWrapperRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({})]),
333 | /**
334 | * If `
335 | dense`, will adjust vertical spacing. This is normally obtained via context from
336 | * FormControl.
337 | */
338 | margin: PropTypes.oneOf(['dense', 'none']),
339 | /**
340 | * @ignore
341 | */
342 | onBlur: PropTypes.func,
343 | /**
344 | * Callback fired when the value is changed.
345 | *
346 | * @param {object} event The event source of the callback.
347 | * You can pull out the new value by accessing `
348 | event.target.value`.
349 | */
350 | onChange: PropTypes.func,
351 | /**
352 | * @ignore
353 | */
354 | onFocus: PropTypes.func,
355 | /**
356 | * @ignore
357 | */
358 | onKeyDown: PropTypes.func,
359 | /**
360 | * The short hint displayed in the input before the user enters a value.
361 | */
362 | placeholder: PropTypes.string,
363 | /**
364 | * Start `
365 | InputAdornment` for this component.
366 | */
367 | startAdornment: PropTypes.node,
368 | /**
369 | * The input value, required for a controlled component.
370 | */
371 | // eslint-disable-next-line react/require-default-props
372 | value: PropTypes.oneOfType([
373 | PropTypes.string,
374 | PropTypes.number,
375 | PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
376 | ]),
377 | /**
378 | * Display the dropdown list
379 | */
380 | isOpen: PropTypes.bool,
381 | /**
382 | * Get item props for the dropdown list menu itens
383 | */
384 | getItemProps: PropTypes.func,
385 | /**
386 | * The input value given from the user for filtering on suggestions
387 | */
388 | inputValue: PropTypes.string,
389 | /**
390 | * The index to know which to highlight
391 | */
392 | highlightedIndex: PropTypes.number,
393 | /**
394 | * Function for getting suggestions
395 | */
396 | suggestions: PropTypes.func,
397 | };
398 |
399 | Input.defaultProps = {
400 | className: '',
401 | endAdornment: '',
402 | inputComponent: '',
403 | inputProps: {},
404 | inputRef: noop,
405 | inputWrapperRef: noop,
406 | margin: 'none',
407 | onBlur: noop,
408 | onChange: noop,
409 | onFocus: noop,
410 | onKeyDown: noop,
411 | placeholder: '',
412 | startAdornment: '',
413 | isOpen: false,
414 | getItemProps: noop,
415 | inputValue: '',
416 | highlightedIndex: 0,
417 | suggestions: noop,
418 | };
419 |
420 | export default Input;
421 |
--------------------------------------------------------------------------------
/src/components/searchBar/overrides/searchInput.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {css} from 'emotion';
3 | import {noop} from 'lodash';
4 |
5 | import {
6 | ChipActions,
7 | ChipButton,
8 | ChipTitle,
9 | ChipWrapper,
10 | } from '../../chip';
11 | import PropTypes from '../../../utils/propTypes';
12 | import {spacingExtraSmall, spacingSmall} from '../../../variables/spacing';
13 | import {fontLarge} from '../../../variables/font';
14 |
15 | // modified textField for our needs
16 | import TextField from './textField';
17 | import Clear from '../../../icons/clear';
18 |
19 | const parentChip = isLogic => css`
20 | padding: ${spacingExtraSmall} ${spacingSmall};
21 | display: inline-flex;
22 | font-size: ${fontLarge};
23 | ${isLogic ? 'color: #1935a7;font-weight: bold' : 'inherit'};
24 | `;
25 |
26 | const styles = placeholder => ({
27 | inputWrapper: css`
28 | margin: 2px 8px;
29 | font-size: ${fontLarge};
30 | width: 80px;
31 | flex-grow: 1;
32 | `,
33 | input: css`
34 | ${placeholder ? '' : 'width: 400px;'} // needed to display the entire placeholder
35 | font-size: ${fontLarge};
36 | height: 30px;
37 | `,
38 | });
39 |
40 | class SearchInput extends Component {
41 | handleClick = () => {
42 | const {openMenu, clickInput} = this.props;
43 | openMenu();
44 | clickInput();
45 | };
46 |
47 | inputProps = () => {
48 | const {
49 | selectedItem, isOpen, inputValue, highlightedIndex,
50 | getInputProps, handleInputChange, handleKeyDown, handleDelete,
51 | getItemProps, placeholder,
52 | } = this.props;
53 |
54 | return getInputProps({
55 | startAdornment: selectedItem.map(o => o.child
56 | ? (
57 |
58 | {`${o.parent}:${decodeURIComponent(o.child)}`}
59 |
60 |
61 |
62 |
63 | )
64 | : (
65 |
66 | {`${o.parent}${o.isLogic ? '' : ':'}`}
67 |
68 | )),
69 | onChange: handleInputChange,
70 | onKeyDown: handleKeyDown,
71 | placeholder: selectedItem.length ? '' : placeholder,
72 | classes: styles(selectedItem.length),
73 | isOpen,
74 | getItemProps,
75 | inputValue,
76 | highlightedIndex,
77 | suggestions: this.getSuggestions,
78 | });
79 | };
80 |
81 | getSuggestions = inputValue => {
82 | const {suggestions} = this.props;
83 |
84 | return suggestions.filter(suggestion => (!inputValue || suggestion.label.toLowerCase().includes(inputValue.replace(/ /g, '').toLowerCase())));
85 | };
86 |
87 | render() {
88 | const {input} = this.props;
89 |
90 | return (
91 |
92 |
96 |
97 | );
98 | }
99 | }
100 |
101 | SearchInput.defaultProps = {
102 | input: null,
103 | clickInput: noop,
104 | openMenu: noop,
105 | suggestions: [],
106 | selectedItem: [],
107 | getInputProps: noop,
108 | handleInputChange: noop,
109 | handleKeyDown: noop,
110 | handleDelete: noop,
111 | getItemProps: noop,
112 | isOpen: false,
113 | inputValue: '',
114 | highlightedIndex: 0,
115 | placeholder: '',
116 | };
117 |
118 | SearchInput.propTypes = {
119 | input: PropTypes.shape({}),
120 | clickInput: PropTypes.func,
121 | openMenu: PropTypes.func,
122 | suggestions: PropTypes.arrayOf(PropTypes.shape({})),
123 | selectedItem: PropTypes.arrayOf(PropTypes.shape({})),
124 | getInputProps: PropTypes.func,
125 | handleInputChange: PropTypes.func,
126 | handleKeyDown: PropTypes.func,
127 | handleDelete: PropTypes.func,
128 | getItemProps: PropTypes.func,
129 | isOpen: PropTypes.bool,
130 | inputValue: PropTypes.string,
131 | highlightedIndex: PropTypes.number,
132 | placeholder: PropTypes.string,
133 | };
134 |
135 | export default SearchInput;
136 |
--------------------------------------------------------------------------------
/src/components/searchBar/overrides/textField.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {noop} from 'lodash';
3 | import PropTypes from '../../../utils/propTypes';
4 | import Input from './input';
5 |
6 | function TextField(props) {
7 | const {
8 | className,
9 | defaultValue,
10 | InputProps,
11 | inputRef,
12 | onBlur,
13 | onChange,
14 | onFocus,
15 | placeholder,
16 | value,
17 | ...other
18 | } = props;
19 |
20 | const InputElement = (
21 |
31 | );
32 |
33 | return (
34 |
38 | {InputElement}
39 |
40 | );
41 | }
42 |
43 | TextField.propTypes = {
44 | /**
45 | * @ignore
46 | */
47 | className: PropTypes.string,
48 | /**
49 | * The default value of the `Input` element.
50 | */
51 | // eslint-disable-next-line react/require-default-props
52 | defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
53 | /**
54 | * Properties applied to the `Input` element.
55 | */
56 | InputProps: PropTypes.shape({}),
57 | /**
58 | * Use that property to pass a ref callback to the native input component.
59 | */
60 | inputRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({})]),
61 | /**
62 | * @ignore
63 | */
64 | onBlur: PropTypes.func,
65 | /**
66 | * Callback fired when the value is changed.
67 | *
68 | * @param {object} event The event source of the callback.
69 | * You can pull out the new value by accessing `event.target.value`.
70 | */
71 | onChange: PropTypes.func,
72 | /**
73 | * @ignore
74 | */
75 | onFocus: PropTypes.func,
76 | /**
77 | * The short hint displayed in the input before the user enters a value.
78 | */
79 | placeholder: PropTypes.string,
80 | /**
81 | * The value of the `Input` element, required for a controlled component.
82 | */
83 | // eslint-disable-next-line react/require-default-props
84 | value: PropTypes.oneOfType([
85 | PropTypes.string,
86 | PropTypes.number,
87 | PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
88 | ]),
89 | };
90 |
91 | TextField.defaultProps = {
92 | className: '',
93 | InputProps: {},
94 | inputRef: {},
95 | onBlur: noop,
96 | onChange: noop,
97 | onFocus: noop,
98 | placeholder: '',
99 | };
100 |
101 | export default TextField;
102 |
--------------------------------------------------------------------------------
/src/components/searchBar/overrides/utils/autosuggest-highlight.js:
--------------------------------------------------------------------------------
1 | import {clean as removeDiacritics} from 'diacritic';
2 |
3 | // https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Regular_Expressions#Using_special_characters
4 | const specialCharsRegex = /[.*+?^${}()|[\]\\]/g;
5 |
6 | // http://www.ecma-international.org/ecma-262/5.1/#sec-15.10.2.6
7 | const wordCharacterRegex = /[a-z0-9_]/i;
8 |
9 | const whitespacesRegex = /\s+/;
10 |
11 | function escapeRegexCharacters(str) {
12 | return str.replace(specialCharsRegex, '\\$&');
13 | }
14 |
15 | export const match = (t, q, o) => {
16 | const options = {
17 | insideWords: false,
18 | findAllOccurrences: false,
19 | requireMatchAll: false,
20 | ...o,
21 | };
22 |
23 | let text = removeDiacritics(t);
24 | const query = removeDiacritics(q);
25 |
26 | return (
27 | query
28 | .trim()
29 | .split(whitespacesRegex)
30 | // If query is blank, we'll get empty string here, so let's filter it out.
31 | .filter(word => word.length > 0)
32 | .reduce((result, word) => {
33 | const wordLen = word.length;
34 | const prefix = !options.insideWords && wordCharacterRegex.test(word[0]) ? '\\b' : '';
35 | const regex = new RegExp(prefix + escapeRegexCharacters(word), 'i');
36 | let occurrence;
37 | let index;
38 |
39 | occurrence = regex.exec(text);
40 | if (options.requireMatchAll && occurrence === null) {
41 | text = '';
42 | return [];
43 | }
44 |
45 | while (occurrence) {
46 | index = occurrence.index;
47 | result.push([index, index + wordLen]);
48 |
49 | // Replace what we just found with spaces so we don't find it again.
50 | text = text.slice(0, index)
51 | + new Array(wordLen + 1).join(' ')
52 | + text.slice(index + wordLen);
53 |
54 | if (!options.findAllOccurrences) {
55 | break;
56 | }
57 |
58 | occurrence = regex.exec(text);
59 | }
60 |
61 | return result;
62 | }, [])
63 | .sort((match1, match2) => match1[0] - match2[0])
64 | );
65 | };
66 |
67 | export const parse = (t, matches) => {
68 | const result = [];
69 |
70 | const text = t;
71 |
72 | if (matches.length === 0) {
73 | result.push({
74 | text,
75 | highlight: false,
76 | });
77 | }
78 | else if (matches[0][0] > 0) {
79 | result.push({
80 | text: text.slice(0, matches[0][0]),
81 | highlight: false,
82 | });
83 | }
84 |
85 | matches.forEach((match, i) => {
86 | const startIndex = match[0];
87 | const endIndex = match[1];
88 |
89 | result.push({
90 | text: text.slice(startIndex, endIndex),
91 | highlight: true,
92 | });
93 |
94 | if (i === matches.length - 1) {
95 | if (endIndex < text.length) {
96 | result.push({
97 | text: text.slice(endIndex, text.length),
98 | highlight: false,
99 | });
100 | }
101 | }
102 | else if (endIndex < matches[i + 1][0]) {
103 | result.push({
104 | text: text.slice(endIndex, matches[i + 1][0]),
105 | highlight: false,
106 | });
107 | }
108 | });
109 |
110 | return result;
111 | };
112 |
--------------------------------------------------------------------------------
/src/components/searchBar/searchBar.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import keycode from 'keycode';
3 | import uuidv4 from 'uuid/v4';
4 | import decodeUriComponent from 'decode-uri-component';
5 | import {isEqual, noop} from 'lodash';
6 | import styled from '@emotion/styled';
7 | import Downshift from 'downshift';
8 | import {css} from 'emotion';
9 |
10 | import ClearIcon from '../../icons/clear';
11 | import PropTypes from '../../utils/propTypes';
12 |
13 | import {IconButton} from '../iconButton';
14 | import SearchInput from './overrides/searchInput';
15 |
16 | import {ice, darkSkyBlue, white} from '../../variables/colors';
17 |
18 | const InputWrapper = styled('div')`
19 | border: 1px solid ${ice};
20 | background-color: ${white};
21 | display: flex;
22 | align-items: center;
23 | justify-content: space-between;
24 | min-height: 40px;
25 | border-radius: 20px;
26 | padding: 1px;
27 | position: static;
28 | `;
29 |
30 | const searchInputWrapper = css`
31 | flex-grow: 1;
32 | margin-left: 2px;
33 | `;
34 |
35 | const clearButton = css`
36 | border: none;
37 | z-index: 1;
38 | outline: none;
39 | margin-right: 2px;
40 |
41 | &:focus {
42 | box-shadow: 0 0 3pt 3pt ${darkSkyBlue};
43 | }
44 | `;
45 |
46 | // use getRootProps https://github.com/paypal/downshift#getrootprops
47 | const SearchInputWrapper = ({innerRef, ...rest}) => (
48 |
53 | );
54 |
55 | SearchInputWrapper.propTypes = {
56 | innerRef: PropTypes.func.isRequired,
57 | };
58 |
59 | class SearchBar extends Component {
60 | constructor(props) {
61 | super(props);
62 |
63 | this.input = React.createRef();
64 | }
65 |
66 | componentDidMount() {
67 | // init suggestions
68 | const {location, setState, selectedItem} = this.props;
69 |
70 | let newSelectedItem = [];
71 | // fill search from state.location
72 | if (location && location.query && location.query.search) {
73 | // get groups separated by -OR-
74 | const groups = location.query.search.split('-OR-');
75 |
76 | newSelectedItem = groups.reduce((p, group) => {
77 | // create related chips
78 | const chips = group.split(',').map(o => {
79 | const el = decodeUriComponent(o).split(':');
80 |
81 | return {
82 | parent: el[0],
83 | child: el.splice(1).join(':'),
84 | uuid: uuidv4(),
85 | isLogic: false,
86 | };
87 | });
88 |
89 | // do not hoist it, uuid need to be different
90 | const logic = {
91 | parent: '-OR-',
92 | child: '',
93 | uuid: uuidv4(),
94 | isLogic: true,
95 | };
96 |
97 | return [
98 | ...p,
99 | ...(group ? chips : []),
100 | logic, // will add an extra logic el on the last iteration
101 | ];
102 | }, []).slice(0, -1); // remove last added `-OR-`
103 |
104 | if (!isEqual(selectedItem, newSelectedItem)) {
105 | setState({
106 | selectedItem: newSelectedItem,
107 | toUpdate: false,
108 | });
109 | }
110 | }
111 | }
112 |
113 | handleKeyDown = event => {
114 | const {setState, inputValue, selectedItem} = this.props;
115 |
116 | if (selectedItem.length && !inputValue.length && keycode(event) === 'backspace') {
117 | const l = selectedItem.length;
118 |
119 | if (l) {
120 | const newSelectedItems = selectedItem.slice(0, l - 1);
121 | const last = selectedItem[l - 1];
122 |
123 | setState({
124 | selectedItem: newSelectedItems,
125 | isParent: true,
126 | item: '',
127 | toUpdate: !(last.isLogic || !last.child),
128 | });
129 | }
130 | }
131 | };
132 |
133 | handleInputChange = event => {
134 | const {setState} = this.props;
135 |
136 | setState({
137 | inputValue: event.target.value,
138 | toUpdate: false,
139 | });
140 | };
141 |
142 | handleChange = item => {
143 | // item may be null when handleChange is triggered by hitting the ESC key
144 | if (item) {
145 | const {
146 | parentSuggestions, isParent, selectedItem,
147 | setState,
148 | } = this.props;
149 |
150 | let newSelectedItem,
151 | toUpdate = false;
152 |
153 | if (!isParent) { // remove precedent parent and add child
154 | const prev = selectedItem.pop();
155 | newSelectedItem = [...selectedItem, {...prev, child: item.label}];
156 | toUpdate = true;
157 | }
158 | // if is parent, simply add
159 | else {
160 | newSelectedItem = [
161 | ...selectedItem, {
162 | parent: item.label,
163 | child: '',
164 | uuid: uuidv4(),
165 | isLogic: item.isLogic,
166 | }];
167 | }
168 |
169 | // calculate if previous in parent menu
170 | const selectedParent = parentSuggestions.map(o => o.label).includes(item.label);
171 |
172 | // set item in redux, and launch related sagas for fetching list if needed
173 | setState({
174 | isParent: item.isLogic || !selectedParent,
175 | inputValue: '',
176 | selectedItem: newSelectedItem,
177 | item: selectedParent || item.isLogic ? item.label : '',
178 | toUpdate,
179 | });
180 | }
181 | };
182 |
183 | handleDelete = item => () => {
184 | const {setState, selectedItem} = this.props;
185 |
186 | // remove empty parent, the item we want to delete
187 | let newSelectedItems = selectedItem.filter(o => !(
188 | (o.child === '' && !o.isLogic) // remove parent without child
189 | || o.uuid === item.uuid), // remove item clicked
190 |
191 | );
192 |
193 | // remove -OR- item if not a chip before (i.e nothing or another -OR-)
194 | newSelectedItems = newSelectedItems.filter((o, i) => !(
195 | o.isLogic && i === 0 // remove first item if isLogic
196 | || o.isLogic && i > 0 && newSelectedItems[i - 1].isLogic), // remove isLogic if precedent isLogic
197 |
198 | );
199 |
200 | // need to setItem correctly after deleting
201 | const l = newSelectedItems.length,
202 | last = l ? newSelectedItems[l - 1] : undefined;
203 |
204 | setState({
205 | selectedItem: newSelectedItems,
206 | isParent: true,
207 | item: last && last.isLogic ? '-OR-' : '',
208 | toUpdate: true,
209 | });
210 |
211 | this.clickInput();
212 | };
213 |
214 | handleOuterClick = () => {
215 | const {setState, inputValue} = this.props;
216 |
217 | if (inputValue) {
218 | setState({
219 | inputValue: '',
220 | });
221 | }
222 | };
223 |
224 | clear = () => {
225 | const {setState} = this.props;
226 |
227 | setState({
228 | selectedItem: [],
229 | isParent: true,
230 | item: '',
231 | toUpdate: true,
232 | });
233 |
234 | this.clickInput();
235 | };
236 |
237 | clickInput = () => {
238 | setTimeout(() => {
239 | // should appear after blur of input (need to call setTimeout as downshift does)
240 | // stay on focus
241 | if (this.input.current) {
242 | this.input.current.focus();
243 | }
244 | }, 0);
245 | };
246 |
247 | searchInput = props => {
248 | const {suggestions, placeholder} = this.props;
249 |
250 | // due to ugly render props philosophy of Downshift, we have to extract the declaration in a function...
251 | return (
252 |
253 |
263 |
264 | );
265 | };
266 |
267 | itemToString = item => item === null ? '' : item.label;
268 |
269 | render() {
270 | const {inputValue, selectedItem} = this.props;
271 |
272 | return (
273 |
274 |
286 | {this.searchInput}
287 |
288 |
289 |
296 |
297 | );
298 | }
299 | }
300 |
301 | SearchBar.propTypes = {
302 | location: PropTypes.shape({
303 | query: PropTypes.shape({
304 | search: PropTypes.string,
305 | }),
306 | }),
307 | suggestions: PropTypes.arrayOf(PropTypes.shape({})),
308 | parentSuggestions: PropTypes.arrayOf(PropTypes.shape({})),
309 | setState: PropTypes.func,
310 | inputValue: PropTypes.string,
311 | selectedItem: PropTypes.arrayOf(PropTypes.shape({
312 | isLogic: PropTypes.bool,
313 | child: PropTypes.string,
314 | })),
315 | isParent: PropTypes.bool,
316 | placeholder: PropTypes.string,
317 | };
318 |
319 | SearchBar.defaultProps = {
320 | location: null,
321 | suggestions: [],
322 | parentSuggestions: [],
323 | setState: noop,
324 | inputValue: '',
325 | selectedItem: [],
326 | isParent: true,
327 | placeholder: '',
328 | };
329 |
330 | export default SearchBar;
331 |
--------------------------------------------------------------------------------
/src/components/searchBar/searchBar.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | import {StateDecorator, Store} from '@sambego/storybook-state';
4 | import {
5 | defaultState,
6 | getSuggestions,
7 | } from './searchBarJest';
8 | import SearchBar from './searchBar';
9 |
10 | const store = new Store(defaultState);
11 |
12 | let previousState = {...defaultState};
13 |
14 | const setState = state => {
15 | store.set(state);
16 | };
17 |
18 | setState({...defaultState, ...getSuggestions(defaultState.isParent, [])});
19 |
20 | store.subscribe(state => {
21 | if (previousState.isParent !== state.isParent || state.item === '-OR-' || (
22 | !state.selectedItem.length && state.suggestions[0].label === '-OR-'
23 | )) {
24 | previousState = {...state};
25 | store.set({
26 | ...state,
27 | ...getSuggestions(state.isParent, state.selectedItem, state.item),
28 | });
29 | }
30 | previousState = {...state};
31 | });
32 | storiesOf('SearchBar', module)
33 | .addDecorator(StateDecorator(store))
34 | .add('default', () => (
35 |
44 | ));
45 |
--------------------------------------------------------------------------------
/src/components/searchBar/searchBar.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {render, fireEvent} from '@testing-library/react';
3 | import {createStore} from 'redux';
4 | import {Provider, connect} from 'react-redux';
5 | import {
6 | defaultState, getSuggestions,
7 | } from './searchBarJest';
8 |
9 | import SearchBar from './searchBar';
10 |
11 | const initialStateWithSelectedItem = {
12 | ...defaultState,
13 | selectedItem: [{parent: 'AAAA', child: '2', uuid: 'aee83084-fafb-4d26-8144-883c45244183'}],
14 | };
15 |
16 | const initialStateWithLocation = {
17 | ...defaultState,
18 | location: {query: {search: 'test'}},
19 | };
20 |
21 | const initialStateWithSelectedItemAndLocation = {
22 | ...defaultState,
23 | selectedItem: [{parent: 'AAAA', child: '2', uuid: 'aee83084-fafb-4d26-8144-883c45244183'}],
24 | location: {query: {search: 'test'}},
25 | };
26 |
27 | const renderSearchBar = (initialState = defaultState) => {
28 | const reducer = (state = initialState, {type, payload}) => {
29 | switch (type) {
30 | case 'SET':
31 | return {
32 | ...state,
33 | ...getSuggestions(state.isParent, state.selectedItem, state.item),
34 | ...payload,
35 | };
36 | default:
37 | return {
38 | ...state,
39 | ...getSuggestions(state.isParent, state.selectedItem, state.item),
40 | };
41 | }
42 | };
43 | const store = createStore(reducer, initialState);
44 |
45 | const mapStateToProps = ({
46 | inputValue, suggestions, parentSuggestions, isParent, location, selectedItem,
47 | }) => ({
48 | inputValue,
49 | suggestions,
50 | parentSuggestions,
51 | isParent,
52 | location,
53 | selectedItem,
54 | placeholder: 'Custom placeholder',
55 | });
56 |
57 | const mapDispatchToProps = dispatch => ({
58 | setState: payload => dispatch({type: 'SET', payload}),
59 | });
60 |
61 | const ReduxSearchBar = connect(mapStateToProps, mapDispatchToProps)(SearchBar);
62 | return render((
63 |
64 |
65 |
66 |
67 | ));
68 | };
69 |
70 | test('The popper is displayed when the searchbar is clicked', () => {
71 | const {getByTestId} = renderSearchBar();
72 |
73 | expect(getByTestId('popper')).toHaveStyle('display: none;');
74 | fireEvent.click(getByTestId('searchbar'));
75 | expect(getByTestId('popper')).toHaveStyle('display: block;');
76 | });
77 |
78 | test('The width is 400px when there are no suggestions selected', () => {
79 | const {getByTestId} = renderSearchBar();
80 |
81 | expect(getByTestId('searchbar')).toHaveStyle('width: 400px;');
82 | });
83 |
84 | test('The width isn\'t 400px when there are suggestions selected', () => {
85 | const {getByTestId} = renderSearchBar(initialStateWithSelectedItem);
86 |
87 | expect(getByTestId('searchbar')).not.toHaveStyle('width: 400px;');
88 | });
89 |
90 | test('The placeholder is displayed when there are no selected items', () => {
91 | const {getByTestId} = renderSearchBar();
92 |
93 | expect(getByTestId('searchbar').placeholder).toEqual('Custom placeholder');
94 | });
95 |
96 | test('The placeholder isn\'t displayed when there are selected items', () => {
97 | const {getByTestId} = renderSearchBar(initialStateWithSelectedItem);
98 |
99 | expect(getByTestId('searchbar').placeholder).toEqual('');
100 | });
101 |
102 | test('The clear button remove every selected items', () => {
103 | const {getByTestId} = renderSearchBar(initialStateWithSelectedItem);
104 |
105 | expect(getByTestId('chip-aee83084-fafb-4d26-8144-883c45244183')).toBeDefined();
106 | fireEvent.click(getByTestId('button'));
107 | expect(() => {
108 | getByTestId('chip-aee83084-fafb-4d26-8144-883c45244183');
109 | }).toThrow();
110 | });
111 |
112 | test('Test if the location is synchronized', () => {
113 | const {getByText} = renderSearchBar(initialStateWithLocation);
114 |
115 | expect(getByText('test:')).toBeDefined();
116 | });
117 |
118 | test('Test if the location overrides selectedItem', () => {
119 | const {getByText} = renderSearchBar(initialStateWithSelectedItemAndLocation);
120 |
121 | expect(getByText('test:')).toBeDefined();
122 | });
123 |
--------------------------------------------------------------------------------
/src/components/searchBar/searchBarJest.js:
--------------------------------------------------------------------------------
1 | import {uniqBy} from 'lodash';
2 |
3 | export const defaultState = {
4 | inputValue: '',
5 | isParent: true,
6 | selectedItem: [],
7 | suggestions: [],
8 | parentSuggestions: [],
9 | };
10 |
11 | export const suggestions = [
12 | 'AAAA:1',
13 | 'AAAA:2',
14 | 'BBBB:1',
15 | 'BBBB:3',
16 | ];
17 |
18 | export const getSuggestions = (isParent, selectedItem, item) => {
19 | const isLogic = item === '-OR-';
20 | const level_1 = uniqBy(suggestions.map(s => ({label: s.split(':')[0]})), 'label');
21 |
22 | if (isParent) {
23 | return {
24 | suggestions: [
25 | ...(selectedItem.length && !isLogic ? [{label: '-OR-', isLogic: true}] : []),
26 | ...level_1,
27 | ],
28 | parentSuggestions: level_1,
29 | item: isLogic ? '' : item,
30 | };
31 | }
32 |
33 | const s = suggestions.reduce((p, c) => {
34 | const [parent, child] = c.split(':');
35 | return [
36 | ...p,
37 | ...(parent === selectedItem[0].parent ? [{label: child}] : []),
38 | ];
39 | }, []);
40 | const level_2 = uniqBy(s, 'label');
41 |
42 | return {
43 | suggestions: level_2,
44 | parentSuggestions: level_1,
45 | };
46 | };
47 |
--------------------------------------------------------------------------------
/src/components/select.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {css} from 'emotion';
3 |
4 | import {ice, white} from '../variables/colors';
5 | import {
6 | spacingExtraSmall, spacingLarge, spacingNormal, spacingSmall,
7 | } from '../variables/spacing';
8 |
9 |
10 | // Adapted from https://www.filamentgroup.com/lab/select-css.html
11 | // (via https://css-tricks.com/styling-a-select-like-its-2019/)
12 | // Caret SVG:
13 | //
14 |
15 | const select = css`
16 | display: inline-block;
17 | font-size: inherit;
18 | font-family: inherit;
19 | font-weight: inherit;
20 | color: inherit;
21 | line-height: ${spacingNormal};
22 | padding: ${spacingExtraSmall} ${spacingLarge} ${spacingExtraSmall} ${spacingSmall};
23 | width: auto;
24 | max-width: 100%;
25 | box-sizing: border-box;
26 | margin: 0;
27 | border: 1px solid ${ice};
28 | border-radius: ${spacingNormal};
29 | -moz-appearance: none;
30 | -webkit-appearance: none;
31 | appearance: none;
32 | background-color: ${white};
33 | background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9IiM4MTkwOWQiIGZvY3VzYWJsZT0iZmFsc2UiIHZpZXdCb3g9IjAgMCAyNCAyNCIgYXJpYS1oaWRkZW49InRydWUiIHJvbGU9InByZXNlbnRhdGlvbiI+PHBhdGggZD0iTTcgMTBsNSA1IDUtNXoiPjwvcGF0aD48L3N2Zz4=');
34 | background-repeat: no-repeat;
35 | background-position: right 3px top 50%;
36 | background-size: 24px 24px;
37 | cursor: pointer;
38 |
39 | &::-ms-expand {
40 | display: none;
41 | }
42 |
43 | &:hover {
44 | border-color: ${ice};
45 | background-color: ${ice};
46 | }
47 |
48 | &:focus {
49 | border-color: ${ice};
50 | outline: none;
51 | }
52 |
53 | & option {
54 | font-weight:normal;
55 | }
56 | `;
57 |
58 | const Select = props => (
59 |
63 | );
64 |
65 | export default Select;
66 |
--------------------------------------------------------------------------------
/src/components/select.stories.js:
--------------------------------------------------------------------------------
1 | import {storiesOf} from '@storybook/react';
2 | import React from 'react';
3 | import Select from './select';
4 |
5 | storiesOf('Select', module)
6 | .add('default', () => (
7 |
8 | NAME (A-Z)
9 | NAME (Z-A)
10 |
11 | ));
12 |
--------------------------------------------------------------------------------
/src/components/tabs.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {css} from 'emotion';
3 | import {
4 | Tab as ReactTab, TabList as ReactTabList,
5 | } from 'react-tabs';
6 | import {
7 | blueGrey, ice, tealish, white,
8 | } from '../variables/colors';
9 | import {spacingNormal, spacingSmall} from '../variables/spacing';
10 |
11 | export {Tabs, TabPanel} from 'react-tabs';
12 |
13 | const cssTabList = `
14 | border-bottom: 1px solid ${ice};
15 | padding: 0;
16 | margin: ${spacingNormal} 0;
17 | display: flex;
18 | list-style: none;
19 | `;
20 |
21 | export const cssTabTemplate = `
22 | padding: ${spacingSmall} ${spacingNormal};
23 | border: 1px solid transparent;
24 | cursor: pointer;
25 | border-top-left-radius: 3px;
26 | border-top-right-radius: 3px;
27 | margin-bottom: -1px;
28 |
29 | &.selected {
30 | border-color: ${ice} ${ice} ${white} ${ice};
31 | color: ${tealish};
32 | }
33 |
34 | &.disabled {
35 | cursor: not-allowed;
36 | color: ${blueGrey};
37 | }
38 | `;
39 |
40 | const tabList = css`
41 | ${cssTabList}
42 | `;
43 |
44 | const tabTemplate = css`
45 | ${cssTabTemplate}
46 | `;
47 |
48 | export const TabList = props => (
49 |
53 | );
54 | TabList.tabsRole = 'TabList';
55 |
56 | export const Tab = props => (
57 |
63 | );
64 | Tab.tabsRole = 'Tab';
65 |
--------------------------------------------------------------------------------
/src/components/tabs.stories.js:
--------------------------------------------------------------------------------
1 | import {storiesOf} from '@storybook/react';
2 | import React from 'react';
3 | import {css} from 'emotion';
4 | import {color, withKnobs} from '@storybook/addon-knobs';
5 | import {
6 | TabList,
7 | Tab,
8 | Tabs,
9 | TabPanel,
10 | cssTabTemplate,
11 | } from './tabs';
12 | import {gold} from '../variables/colors';
13 |
14 | storiesOf('Tabs', module)
15 | .addDecorator(withKnobs)
16 | .add('default', () => (
17 |
18 |
19 | Description
20 | Metrics
21 |
22 |
23 | First tab's content
24 |
25 |
26 | Second tab's content
27 |
28 |
29 | ))
30 | .add('color override', () => {
31 | const checkColor = color('Color: ', gold);
32 | const tabStyle = css`
33 | ${cssTabTemplate};
34 | &.selected {
35 | color: ${checkColor};
36 | }
37 | `;
38 | const MyTab = props => ;
39 | MyTab.tabsRole = 'Tab';
40 | return (
41 |
42 |
43 | Description
44 | Metrics
45 |
46 |
47 | First tab's content
48 |
49 |
50 | Second tab's content
51 |
52 |
53 | );
54 | })
55 | .add('disabled', () => (
56 |
57 |
58 | Description
59 | Metrics
60 |
61 |
62 | First tab's content
63 |
64 |
65 | Second tab's content
66 |
67 |
68 | ));
69 |
--------------------------------------------------------------------------------
/src/components/tabs.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {render, fireEvent} from '@testing-library/react';
3 | import {
4 | TabList,
5 | Tab,
6 | Tabs,
7 | TabPanel,
8 | } from './tabs';
9 | import {tealish} from '../variables/colors';
10 |
11 | test('Tab\'s color changes & the description is updated', () => {
12 | const {getByTestId} = render(
13 |
14 | Description
15 | Metrics
16 |
17 |
18 | First tab's content
19 |
20 |
21 | Second tab's content
22 |
23 | );
24 |
25 | expect(getByTestId('desc_1')).toBeDefined();
26 | expect(() => getByTestId('desc_2')).toThrow();
27 |
28 | expect(getByTestId('title_1')).toHaveStyle(`color: ${tealish}`);
29 | expect(getByTestId('title_2')).not.toHaveStyle(`color: ${tealish}`);
30 |
31 | fireEvent.click(getByTestId('title_2'));
32 |
33 | expect(() => getByTestId('desc_1')).toThrow();
34 | expect(getByTestId('desc_2')).toBeDefined();
35 |
36 | expect(getByTestId('title_1')).not.toHaveStyle(`color: ${tealish}`);
37 | expect(getByTestId('title_2')).toHaveStyle(`color: ${tealish}`);
38 | });
39 | test('It should have a disabled state', () => {
40 | const {getByTestId} = render(
41 |
42 | Description
43 | Metrics
44 |
45 |
46 | First tab's content
47 |
48 |
49 | Second tab's content
50 |
51 | );
52 |
53 | expect(getByTestId('desc_1')).toBeDefined();
54 | expect(() => getByTestId('desc_2')).toThrow();
55 |
56 | expect(getByTestId('title_1')).toHaveStyle(`color: ${tealish}`);
57 | expect(getByTestId('title_2')).not.toHaveStyle(`color: ${tealish}`);
58 |
59 | fireEvent.click(getByTestId('title_2'));
60 |
61 | expect(getByTestId('desc_1')).toBeDefined();
62 | expect(() => getByTestId('desc_2')).toThrow();
63 |
64 | expect(getByTestId('title_1')).toHaveStyle(`color: ${tealish}`);
65 | expect(getByTestId('title_2')).not.toHaveStyle(`color: ${tealish}`);
66 | });
67 |
--------------------------------------------------------------------------------
/src/components/twoPanelLayout.js:
--------------------------------------------------------------------------------
1 | /* global window */
2 | import React, {Component, Fragment} from 'react';
3 | import {css} from 'emotion';
4 | import PropTypes from '../utils/propTypes';
5 | import {spacingLarge, spacingNormal} from '../variables/spacing';
6 | import {white, ice} from '../variables/colors';
7 |
8 | const MIN_COL_WIDTH = 250;
9 |
10 | export const middle = css`
11 | display: inline-block;
12 | vertical-align: top;
13 | `;
14 |
15 | export const margin = 40;
16 | const barSize = 15;
17 | const halfBarSize = (barSize - 1) / 2;
18 |
19 | export const verticalBar = css`
20 | ${middle};
21 | width: ${barSize}px;
22 | margin-right: -${halfBarSize}px;
23 | margin-left: -${halfBarSize}px;
24 | z-index: 1;
25 | cursor: col-resize;
26 | background-color: transparent;
27 | flex-grow: 0;
28 | flex-shrink: 0;
29 |
30 | position: relative;
31 | :before {
32 | content: "";
33 | position: absolute;
34 | top: 0;
35 | bottom: 0;
36 | left: ${halfBarSize}px;
37 | border-left: 1px solid ${ice};
38 | }
39 | `;
40 |
41 |
42 | class TwoPanelLayout extends Component {
43 | state = {
44 | hold: false,
45 | };
46 |
47 | constructor(props) {
48 | super(props);
49 | const {defaultLeftPanelWidth} = this.props;
50 | this.state.leftPanelWidth = defaultLeftPanelWidth;
51 | this.contentRef = React.createRef();
52 | }
53 |
54 | componentDidMount() {
55 | this.updateDimensions();
56 | window.addEventListener('resize', this.updateDimensions);
57 | }
58 |
59 | componentWillUnmount() {
60 | window.removeEventListener('resize', this.updateDimensions);
61 | }
62 |
63 | updateDimensions = () => {
64 | if (this.contentRef.current) {
65 | const containerWidth = this.contentRef.current.offsetWidth;
66 | const leftPanelWidth = this.state.leftPanelWidth.unit === '%' ? this.state.leftPanelWidth.value * containerWidth / 100 : this.state.leftPanelWidth.value;
67 |
68 | this.updateLeftPanelWidth(containerWidth, leftPanelWidth);
69 | }
70 | };
71 |
72 | move = e => {
73 | if (this.state.hold) {
74 | e.persist();
75 |
76 | const containerWidth = e.currentTarget.offsetWidth;
77 | const leftPanelWidth = e.clientX - margin - 1;
78 | this.updateLeftPanelWidth(containerWidth, leftPanelWidth);
79 | }
80 | };
81 |
82 | updateLeftPanelWidth(containerWidth, leftPanelWidth) {
83 | const MAX_COL_WIDTH = Math.max(0, containerWidth - MIN_COL_WIDTH);
84 | const actualLeftPanelWidth = Math.min(Math.max(MIN_COL_WIDTH, leftPanelWidth), MAX_COL_WIDTH);
85 |
86 | this.setState(state => ({
87 | ...state,
88 | leftPanelWidth: {value: actualLeftPanelWidth, unit: 'px'},
89 | }
90 | ));
91 | }
92 |
93 | mouseDown = () => this.setState(state => ({
94 | ...state,
95 | hold: true,
96 | }));
97 |
98 | mouseUp = () => {
99 | if (this.state.hold) {
100 | this.setState(state => ({
101 | ...state,
102 | hold: false,
103 | }));
104 | }
105 | };
106 |
107 | getLayout = () => css`
108 | margin: 0 ${spacingLarge} ${spacingNormal} ${spacingLarge};
109 | background-color: ${white};
110 | border: 1px solid ${ice};
111 | display: flex;
112 | flex: 1;
113 | align-items: stretch;
114 | overflow: hidden;
115 | ${this.state.hold ? `
116 | cursor: col-resize;
117 | user-select: none;
118 | ` : ''}
119 | `;
120 |
121 | getLeftPanel = rightPanelContent => css`
122 | width: ${rightPanelContent ? `${this.state.leftPanelWidth.value}${this.state.leftPanelWidth.unit}` : '100%'};
123 | flex-grow: 0;
124 | flex-shrink: 0;
125 | display: flex;
126 | overflow: hidden;
127 | `;
128 |
129 | getRightPanel = css`
130 | flex-grow: 1;
131 | display: flex;
132 | overflow: hidden;
133 | `;
134 |
135 | render() {
136 | const {leftPanelContent, rightPanelContent} = this.props;
137 |
138 | return (
139 |
145 |
146 | {leftPanelContent}
147 |
148 |
149 | {rightPanelContent && (
150 |
151 |
155 |
156 | {rightPanelContent}
157 |
158 |
159 | )}
160 |
161 | );
162 | }
163 | }
164 |
165 | TwoPanelLayout.propTypes = {
166 | defaultLeftPanelWidth: PropTypes.shape({
167 | value: PropTypes.number,
168 | unit: PropTypes.string,
169 | }),
170 | leftPanelContent: PropTypes.node.isRequired,
171 | rightPanelContent: PropTypes.node,
172 | };
173 |
174 | TwoPanelLayout.defaultProps = {
175 | defaultLeftPanelWidth: {value: 40, unit: '%'},
176 | rightPanelContent: null,
177 | };
178 |
179 | export default TwoPanelLayout;
180 |
--------------------------------------------------------------------------------
/src/components/twoPanelLayout.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {storiesOf} from '@storybook/react';
3 |
4 | import TwoPanelLayout from './twoPanelLayout';
5 | import {PanelContent, PanelTop, PanelWrapper} from './panel';
6 |
7 | storiesOf('TwoPanelsLayout', module)
8 | .add('single panel', () => (
9 |
12 | ))
13 | .add('two panels', () => (
14 |
18 | ))
19 | .add('two panels with proper style', () => {
20 | const panelWithStyle = (
21 |
22 | Panel top
23 | Panel content
24 |
25 | );
26 | return (
27 |
31 | );
32 | });
33 |
--------------------------------------------------------------------------------
/src/globalStyles/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import {Global, css} from '@emotion/core';
4 | import emotionNormalize from 'emotion-normalize';
5 | import {iceBlue, slate} from '../variables/colors';
6 | import {fontNormal} from '../variables/font';
7 | import {spacingSmall} from '../variables/spacing';
8 |
9 | // unused for now
10 | // const fontFaceItalic = (variant, weight) => css`
11 | // ${fontFace(variant, 'normal', weight)}
12 | // ${fontFace(`${variant}Italic`, 'italic', weight)}
13 | // `;
14 |
15 |
16 | // hardcode which font we want for avoiding making webpack load all available fonts
17 | const fontFaceNormal = `
18 | @font-face {
19 | font-family: 'Lato';
20 | font-style: normal;
21 | font-weight: 700;
22 | src: url(${require('./lato/LatoLatin-Bold.eot')}); /* IE9 Compat Modes */
23 | src: url('${require('./lato/LatoLatin-Bold.eot')}?#iefix') format('embedded-opentype'), /* IE6-IE8 */
24 | url(${require('./lato/LatoLatin-Bold.woff2')}) format('woff2'), /* Super Modern Browsers */
25 | url(${require('./lato/LatoLatin-Bold.woff')}) format('woff'), /* Pretty Modern Browsers */
26 | url(${require('./lato/LatoLatin-Bold.ttf')}) format('truetype'); /* Safari, Android, iOS */
27 | };
28 | @font-face {
29 | font-family: 'Lato';
30 | font-style: normal;
31 | font-weight: 400;
32 | src: url(${require('./lato/LatoLatin-Regular.eot')}); /* IE9 Compat Modes */
33 | src: url('${require('./lato/LatoLatin-Regular.eot')}?#iefix') format('embedded-opentype'), /* IE6-IE8 */
34 | url(${require('./lato/LatoLatin-Regular.woff2')}) format('woff2'), /* Super Modern Browsers */
35 | url(${require('./lato/LatoLatin-Regular.woff')}) format('woff'), /* Pretty Modern Browsers */
36 | url(${require('./lato/LatoLatin-Regular.ttf')}) format('truetype'); /* Safari, Android, iOS */
37 | };
38 | `;
39 |
40 |
41 | const globalStyles = css`
42 | ${emotionNormalize}
43 |
44 | ${fontFaceNormal};
45 |
46 | html {
47 | box-sizing: border-box;
48 | }
49 |
50 | *, *:before, *:after {
51 | box-sizing: inherit;
52 | }
53 |
54 | html, body, #root, #root > div {
55 | height: 100%;
56 | }
57 |
58 | body {
59 | margin: 0;
60 | padding: 0;
61 | background: ${iceBlue};
62 | color: ${slate};
63 | font-family: 'Lato', sans-serif;
64 | font-size: ${fontNormal};
65 | letter-spacing: 0.5px;
66 |
67 | -webkit-font-smoothing: antialiased; /* This needs to be set or some font faced fonts look bold on Mac in Chrome/Webkit based browsers. */
68 | -moz-osx-font-smoothing: grayscale; /* Fixes font bold issue in Firefox version 25+ on Mac */
69 | }
70 |
71 | h1, h2, h3, h4 {
72 | margin: 0;
73 | }
74 |
75 | button,
76 | html [type="button"], /* 1 */
77 | [type="reset"],
78 | [type="submit"] {
79 | -webkit-appearance: none; /* 2 */
80 | }
81 |
82 | .error { color: #ba0000; }
83 |
84 | span.error {
85 | display: block;
86 | margin-top: ${spacingSmall};
87 | font-size: ${fontNormal};
88 | }
89 |
90 | dl { display: inline-block;vertical-align: top; }
91 | dd, dt { display: inline-block; }
92 | dt { width: 45%;padding-right: 5%; }
93 | dd { width: 50%; }
94 | `;
95 |
96 | const GlobalStyles = () => ;
97 |
98 | export default GlobalStyles;
99 |
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-Black.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-Black.eot
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-Black.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-Black.ttf
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-Black.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-Black.woff
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-Black.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-Black.woff2
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-BlackItalic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-BlackItalic.eot
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-BlackItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-BlackItalic.ttf
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-BlackItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-BlackItalic.woff
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-BlackItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-BlackItalic.woff2
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-Bold.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-Bold.eot
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-Bold.ttf
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-Bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-Bold.woff
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-Bold.woff2
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-BoldItalic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-BoldItalic.eot
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-BoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-BoldItalic.ttf
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-BoldItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-BoldItalic.woff
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-BoldItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-BoldItalic.woff2
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-Hairline.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-Hairline.eot
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-Hairline.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-Hairline.ttf
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-Hairline.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-Hairline.woff
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-Hairline.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-Hairline.woff2
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-HairlineItalic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-HairlineItalic.eot
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-HairlineItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-HairlineItalic.ttf
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-HairlineItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-HairlineItalic.woff
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-HairlineItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-HairlineItalic.woff2
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-Heavy.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-Heavy.eot
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-Heavy.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-Heavy.ttf
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-Heavy.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-Heavy.woff
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-Heavy.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-Heavy.woff2
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-HeavyItalic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-HeavyItalic.eot
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-HeavyItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-HeavyItalic.ttf
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-HeavyItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-HeavyItalic.woff
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-HeavyItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-HeavyItalic.woff2
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-Light.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-Light.eot
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-Light.ttf
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-Light.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-Light.woff
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-Light.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-Light.woff2
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-LightItalic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-LightItalic.eot
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-LightItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-LightItalic.ttf
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-LightItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-LightItalic.woff
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-LightItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-LightItalic.woff2
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-Medium.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-Medium.eot
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-Medium.ttf
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-Medium.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-Medium.woff
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-Medium.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-Medium.woff2
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-MediumItalic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-MediumItalic.eot
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-MediumItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-MediumItalic.ttf
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-MediumItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-MediumItalic.woff
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-MediumItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-MediumItalic.woff2
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-Regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-Regular.eot
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-Regular.ttf
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-Regular.woff
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-Regular.woff2
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-RegularItalic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-RegularItalic.eot
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-RegularItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-RegularItalic.ttf
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-RegularItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-RegularItalic.woff
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-RegularItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-RegularItalic.woff2
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-Semibold.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-Semibold.eot
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-Semibold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-Semibold.ttf
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-Semibold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-Semibold.woff
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-Semibold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-Semibold.woff2
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-SemiboldItalic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-SemiboldItalic.eot
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-SemiboldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-SemiboldItalic.ttf
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-SemiboldItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-SemiboldItalic.woff
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-SemiboldItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-SemiboldItalic.woff2
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-Thin.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-Thin.eot
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-Thin.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-Thin.ttf
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-Thin.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-Thin.woff
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-Thin.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-Thin.woff2
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-ThinItalic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-ThinItalic.eot
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-ThinItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-ThinItalic.ttf
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-ThinItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-ThinItalic.woff
--------------------------------------------------------------------------------
/src/globalStyles/lato/LatoLatin-ThinItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LabeliaLabs/substra-ui/c5e9c7bacfb308e55fe29116bfa8183363daffcb/src/globalStyles/lato/LatoLatin-ThinItalic.woff2
--------------------------------------------------------------------------------
/src/icons/alert.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import {gold} from '../variables/colors';
4 |
5 | const Alert = ({
6 | className, width, height, color, ...props
7 | }) => (
8 |
16 |
17 |
18 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | );
28 |
29 | Alert.defaultProps = {
30 | className: '',
31 | width: 24,
32 | height: 24,
33 | color: gold,
34 | };
35 |
36 | Alert.propTypes = {
37 | className: PropTypes.string,
38 | width: PropTypes.number,
39 | height: PropTypes.number,
40 | color: PropTypes.string,
41 | };
42 |
43 | export default Alert;
44 |
--------------------------------------------------------------------------------
/src/icons/algo.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import {slate} from '../variables/colors';
4 |
5 | const Algo = ({
6 | className, width, height, color, ...props
7 | }) => (
8 |
16 |
17 |
21 |
22 |
26 |
27 |
28 |
32 |
33 |
34 |
35 | );
36 |
37 | Algo.defaultProps = {
38 | className: '',
39 | width: 45,
40 | height: 25,
41 | color: slate,
42 | };
43 |
44 | Algo.propTypes = {
45 | className: PropTypes.string,
46 | width: PropTypes.number,
47 | height: PropTypes.number,
48 | color: PropTypes.string,
49 | };
50 |
51 | export default Algo;
52 |
--------------------------------------------------------------------------------
/src/icons/book.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import {slate} from '../variables/colors';
4 |
5 | const Book = ({
6 | className, width, height, color, ...props
7 | }) => (
8 |
16 |
21 |
22 | );
23 |
24 | Book.defaultProps = {
25 | className: '',
26 | width: 24,
27 | height: 24,
28 | color: slate,
29 | };
30 |
31 | Book.propTypes = {
32 | className: PropTypes.string,
33 | width: PropTypes.number,
34 | height: PropTypes.number,
35 | color: PropTypes.string,
36 | };
37 |
38 | export default Book;
39 |
--------------------------------------------------------------------------------
/src/icons/check.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const Check = ({
5 | className, width, height, color, ...props
6 | }) => (
7 |
15 |
16 |
17 |
18 |
19 | );
20 |
21 | Check.defaultProps = {
22 | className: '',
23 | width: 24,
24 | height: 24,
25 | color: '#28a745',
26 | };
27 |
28 | Check.propTypes = {
29 | className: PropTypes.string,
30 | width: PropTypes.number,
31 | height: PropTypes.number,
32 | color: PropTypes.string,
33 | };
34 |
35 | export default Check;
36 |
--------------------------------------------------------------------------------
/src/icons/clear.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import {slate} from '../variables/colors';
4 |
5 | const ClearIcon = ({
6 | className, width, height, color, ...props
7 | }) => (
8 |
16 |
17 |
18 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | );
28 |
29 | ClearIcon.defaultProps = {
30 | className: '',
31 | width: 24,
32 | height: 24,
33 | color: slate,
34 | };
35 |
36 | ClearIcon.propTypes = {
37 | className: PropTypes.string,
38 | width: PropTypes.number,
39 | height: PropTypes.number,
40 | color: PropTypes.string,
41 | };
42 |
43 | export default ClearIcon;
44 |
--------------------------------------------------------------------------------
/src/icons/clipboard.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import {slate} from '../variables/colors';
4 |
5 | const Clipboard = ({
6 | className, width, height, color, ...props
7 | }) => (
8 |
16 |
17 |
21 |
22 |
23 | );
24 |
25 | Clipboard.defaultProps = {
26 | className: '',
27 | width: 24,
28 | height: 24,
29 | color: slate,
30 | };
31 |
32 | Clipboard.propTypes = {
33 | className: PropTypes.string,
34 | width: PropTypes.number,
35 | height: PropTypes.number,
36 | color: PropTypes.string,
37 | };
38 |
39 | export default Clipboard;
40 |
--------------------------------------------------------------------------------
/src/icons/collapse.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import {slate} from '../variables/colors';
4 |
5 | const Collapse = ({
6 | className, width, height, color, ...props
7 | }) => (
8 |
16 |
22 |
25 |
26 |
27 | );
28 |
29 | Collapse.defaultProps = {
30 | className: '',
31 | width: 22,
32 | height: 22,
33 | color: slate,
34 | };
35 |
36 | Collapse.propTypes = {
37 | className: PropTypes.string,
38 | width: PropTypes.number,
39 | height: PropTypes.number,
40 | color: PropTypes.string,
41 | };
42 |
43 | export default Collapse;
44 |
--------------------------------------------------------------------------------
/src/icons/copyDrop.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import {slate, tealish} from '../variables/colors';
4 |
5 | const CopyDrop = ({
6 | className, width, height, color, secondaryColor, ...props
7 | }) => (
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | );
41 |
42 | CopyDrop.defaultProps = {
43 | className: '',
44 | width: 24,
45 | height: 24,
46 | color: slate,
47 | secondaryColor: tealish,
48 | };
49 |
50 | CopyDrop.propTypes = {
51 | className: PropTypes.string,
52 | width: PropTypes.number,
53 | height: PropTypes.number,
54 | color: PropTypes.string,
55 | secondaryColor: PropTypes.string,
56 | };
57 |
58 | export default CopyDrop;
59 |
--------------------------------------------------------------------------------
/src/icons/copySimple.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import {slate} from '../variables/colors';
4 |
5 | const CopySimple = ({
6 | className, width, height, color, ...props
7 | }) => (
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | );
32 |
33 | CopySimple.defaultProps = {
34 | className: '',
35 | width: 28,
36 | height: 29,
37 | color: slate,
38 | };
39 |
40 | CopySimple.propTypes = {
41 | className: PropTypes.string,
42 | width: PropTypes.number,
43 | height: PropTypes.number,
44 | color: PropTypes.string,
45 | };
46 |
47 | export default CopySimple;
48 |
--------------------------------------------------------------------------------
/src/icons/dataset.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import {slate} from '../variables/colors';
4 |
5 | const Dataset = ({
6 | className, width, height, color, ...props
7 | }) => (
8 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | );
24 |
25 | Dataset.defaultProps = {
26 | className: '',
27 | width: 23,
28 | height: 25,
29 | color: slate,
30 | };
31 |
32 | Dataset.propTypes = {
33 | className: PropTypes.string,
34 | width: PropTypes.number,
35 | height: PropTypes.number,
36 | color: PropTypes.string,
37 | };
38 |
39 | export default Dataset;
40 |
--------------------------------------------------------------------------------
/src/icons/downloadDrop.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import {slate, tealish} from '../variables/colors';
4 |
5 | const DownloadDrop = ({
6 | className, width, height, color, secondaryColor, ...props
7 | }) => (
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | );
24 |
25 | DownloadDrop.defaultProps = {
26 | className: '',
27 | width: 24,
28 | height: 24,
29 | color: slate,
30 | secondaryColor: tealish,
31 | };
32 |
33 | DownloadDrop.propTypes = {
34 | className: PropTypes.string,
35 | width: PropTypes.number,
36 | height: PropTypes.number,
37 | color: PropTypes.string,
38 | secondaryColor: PropTypes.string,
39 | };
40 |
41 | export default DownloadDrop;
42 |
--------------------------------------------------------------------------------
/src/icons/downloadSimple.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import {slate} from '../variables/colors';
4 |
5 | const DownloadSimple = ({
6 | className, width, height, color, ...props
7 | }) => (
8 |
16 |
17 |
18 |
23 |
24 |
29 |
30 |
31 |
32 |
33 | );
34 |
35 | DownloadSimple.defaultProps = {
36 | className: '',
37 | width: 24,
38 | height: 24,
39 | color: slate,
40 | };
41 |
42 | DownloadSimple.propTypes = {
43 | className: PropTypes.string,
44 | width: PropTypes.number,
45 | height: PropTypes.number,
46 | color: PropTypes.string,
47 | };
48 |
49 | export default DownloadSimple;
50 |
--------------------------------------------------------------------------------
/src/icons/expand.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import {slate} from '../variables/colors';
4 |
5 | const Expand = ({
6 | className, width, height, color, ...props
7 | }) => (
8 |
16 |
22 |
25 |
26 |
27 | );
28 |
29 | Expand.defaultProps = {
30 | className: '',
31 | width: 22,
32 | height: 22,
33 | color: slate,
34 | };
35 |
36 | Expand.propTypes = {
37 | className: PropTypes.string,
38 | width: PropTypes.number,
39 | height: PropTypes.number,
40 | color: PropTypes.string,
41 | };
42 |
43 | export default Expand;
44 |
--------------------------------------------------------------------------------
/src/icons/filterUp.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import {slate, tealish} from '../variables/colors';
4 |
5 | const FilterUp = ({
6 | className, width, height, color, secondaryColor, ...props
7 | }) => (
8 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | );
26 |
27 | FilterUp.defaultProps = {
28 | color: slate,
29 | secondaryColor: tealish,
30 | className: '',
31 | width: 24,
32 | height: 24,
33 | };
34 |
35 | FilterUp.propTypes = {
36 | color: PropTypes.string,
37 | secondaryColor: PropTypes.string,
38 | className: PropTypes.string,
39 | width: PropTypes.number,
40 | height: PropTypes.number,
41 | };
42 |
43 | export default FilterUp;
44 |
--------------------------------------------------------------------------------
/src/icons/folder.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import {slate} from '../variables/colors';
4 |
5 | const Folder = ({
6 | className, width, height, color, ...props
7 | }) => (
8 |
16 |
20 |
21 |
22 |
23 |
24 | );
25 |
26 | Folder.defaultProps = {
27 | className: '',
28 | width: 24,
29 | height: 24,
30 | color: slate,
31 | };
32 |
33 | Folder.propTypes = {
34 | className: PropTypes.string,
35 | width: PropTypes.number,
36 | height: PropTypes.number,
37 | color: PropTypes.string,
38 | };
39 |
40 | export default Folder;
41 |
--------------------------------------------------------------------------------
/src/icons/icons.stories.js:
--------------------------------------------------------------------------------
1 | import React, {Fragment} from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | import {withKnobs, color, number} from '@storybook/addon-knobs/react';
4 |
5 | import styled from '@emotion/styled';
6 | import {
7 | Alert,
8 | Algo,
9 | Book,
10 | Check,
11 | ClearIcon,
12 | Clipboard,
13 | Collapse,
14 | CopyDrop,
15 | CopySimple,
16 | Dataset,
17 | DownloadDrop,
18 | DownloadSimple,
19 | Expand,
20 | FilterUp,
21 | Folder,
22 | Model,
23 | MoreVertical,
24 | OwkestraLogo,
25 | Permission,
26 | Search,
27 | SubstraLogo,
28 | } from './index';
29 | import {slate, tealish} from '../variables/colors';
30 | import {spacingSmall} from '../variables/spacing';
31 |
32 | const Dl = styled.dl`
33 | display: grid;
34 | grid-template-columns: 50px auto;
35 | grid-gap: ${spacingSmall};
36 | `;
37 |
38 | const Dt = styled.dt`
39 | grid-column: 1;
40 | width: 50px;
41 | `;
42 |
43 | const Dd = styled.dd`
44 | grid-column: 2;
45 | margin: 0;
46 | `;
47 |
48 | storiesOf('Icons', module)
49 | .addDecorator(withKnobs)
50 | .add('default', () => {
51 | const colorKnob = color('color', slate);
52 | const secondaryColorKnob = color('secondaryColor', tealish);
53 | const heightKnob = number('height', 24);
54 | const widthKnob = number('width', 24);
55 | return (
56 |
57 |
58 |
59 |
60 |
61 |
62 | Alert
63 |
64 |
65 |
66 |
67 |
68 | Algo
69 |
70 |
71 |
72 |
73 |
74 | Book
75 |
76 |
77 |
78 |
79 |
80 | Check
81 |
82 |
83 |
84 |
85 |
86 | ClearIcon
87 |
88 |
89 |
90 |
91 |
92 | Clipboard
93 |
94 |
95 |
96 |
97 |
98 | Collapse
99 |
100 |
101 |
102 |
103 |
104 | CopyDrop
105 |
106 |
107 |
108 |
109 |
110 | CopySimple
111 |
112 |
113 |
114 |
115 |
116 | Dataset
117 |
118 |
119 |
120 |
121 |
122 | DownloadDrop
123 |
124 |
125 |
126 |
127 |
128 | DownloadSimple
129 |
130 |
131 |
132 |
133 |
134 | Expand
135 |
136 |
137 |
138 |
139 |
140 | FilterUp
141 |
142 |
143 |
144 |
145 |
146 | Folder
147 |
148 |
149 |
150 |
151 |
152 | Model
153 |
154 |
155 |
156 |
157 |
158 | MoreVertical
159 |
160 |
161 |
162 |
163 |
164 | Permission
165 |
166 |
167 |
168 |
169 |
170 | Search
171 |
172 |
173 |
174 | );
175 | })
176 | .add('logos', () => (
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 | ));
186 |
--------------------------------------------------------------------------------
/src/icons/index.js:
--------------------------------------------------------------------------------
1 | export {default as Alert} from './alert';
2 | export {default as Algo} from './algo';
3 | export {default as Book} from './book';
4 | export {default as Check} from './check';
5 | export {default as ClearIcon} from './clear';
6 | export {default as Clipboard} from './clipboard';
7 | export {default as Collapse} from './collapse';
8 | export {default as CopyDrop} from './copyDrop';
9 | export {default as CopySimple} from './copySimple';
10 | export {default as Dataset} from './dataset';
11 | export {default as DownloadDrop} from './downloadDrop';
12 | export {default as DownloadSimple} from './downloadSimple';
13 | export {default as Expand} from './expand';
14 | export {default as FilterUp} from './filterUp';
15 | export {default as Folder} from './folder';
16 | export {default as Model} from './model';
17 | export {default as MoreVertical} from './moreVertical';
18 | export {default as OwkestraLogo} from './owkestraLogo';
19 | export {default as Permission} from './permission';
20 | export {default as Search} from './search';
21 | export {default as SubstraLogo} from './substraLogo';
22 |
--------------------------------------------------------------------------------
/src/icons/model.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import {slate} from '../variables/colors';
4 |
5 | const Model = ({
6 | className, width, height, color, ...props
7 | }) => (
8 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | );
29 |
30 | Model.defaultProps = {
31 | className: '',
32 | width: 24,
33 | height: 22,
34 | color: slate,
35 | };
36 |
37 | Model.propTypes = {
38 | className: PropTypes.string,
39 | width: PropTypes.number,
40 | height: PropTypes.number,
41 | color: PropTypes.string,
42 | };
43 |
44 | export default Model;
45 |
--------------------------------------------------------------------------------
/src/icons/moreVertical.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import {slate} from '../variables/colors';
4 |
5 | const MoreVertical = ({
6 | className, width, height, color, ...props
7 | }) => (
8 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | );
23 |
24 | MoreVertical.defaultProps = {
25 | className: '',
26 | width: 24,
27 | height: 24,
28 | color: slate,
29 | };
30 |
31 | MoreVertical.propTypes = {
32 | className: PropTypes.string,
33 | width: PropTypes.number,
34 | height: PropTypes.number,
35 | color: PropTypes.string,
36 | };
37 |
38 | export default MoreVertical;
39 |
--------------------------------------------------------------------------------
/src/icons/owkestraLogo.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default props => (
4 |
9 |
10 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
35 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
76 |
77 | Owkestra Logo
78 |
82 |
86 |
90 |
94 |
98 |
102 |
106 |
110 |
114 |
118 |
122 |
126 |
130 |
134 |
138 |
142 |
146 |
150 |
151 | );
152 |
--------------------------------------------------------------------------------
/src/icons/permission.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const Permission = ({
5 | className, width, height, color, ...props
6 | }) => (
7 |
15 |
16 |
20 |
21 |
22 |
23 | );
24 |
25 | Permission.defaultProps = {
26 | className: '',
27 | width: 8,
28 | height: 9,
29 | color: '#4C9BBA',
30 | };
31 |
32 | Permission.propTypes = {
33 | className: PropTypes.string,
34 | width: PropTypes.number,
35 | height: PropTypes.number,
36 | color: PropTypes.string,
37 | };
38 |
39 | export default Permission;
40 |
--------------------------------------------------------------------------------
/src/icons/search.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import {slate} from '../variables/colors';
4 |
5 | const Search = ({
6 | className, width, height, color, ...props
7 | }) => (
8 |
16 |
17 |
22 |
23 |
24 | );
25 |
26 | Search.defaultProps = {
27 | className: '',
28 | width: 24,
29 | height: 24,
30 | color: slate,
31 | };
32 |
33 | Search.propTypes = {
34 | className: PropTypes.string,
35 | width: PropTypes.number,
36 | height: PropTypes.number,
37 | color: PropTypes.string,
38 | };
39 |
40 | export default Search;
41 |
--------------------------------------------------------------------------------
/src/icons/substraLogo.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import {slate, tealish} from '../variables/colors';
4 |
5 | const SubstraLogo = ({
6 | className, width, height, ...props
7 | }) => (
8 |
16 |
17 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
35 |
36 |
37 | );
38 |
39 | SubstraLogo.defaultProps = {
40 | className: '',
41 | width: 340,
42 | height: 64,
43 | };
44 |
45 | SubstraLogo.propTypes = {
46 | className: PropTypes.string,
47 | width: PropTypes.number,
48 | height: PropTypes.number,
49 | };
50 |
51 | export default SubstraLogo;
52 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | // Components
2 | export {Button, RoundedButton} from './components/roundedButton';
3 | export {IconButton, RoundButton} from './components/iconButton';
4 | export CodeSample from './components/codeSample';
5 | export CopyInput from './components/copyInput';
6 | export SearchBar from './components/searchBar/searchBar';
7 | export withAddNotification from './components/copyNotification/copyNotification';
8 | export {
9 | TabList,
10 | Tab,
11 | Tabs,
12 | TabPanel,
13 | cssTabTemplate,
14 | } from './components/tabs';
15 | export {PanelWrapper, PanelTop, PanelContent} from './components/panel';
16 | export TwoPanelLayout from './components/twoPanelLayout';
17 | export Select from './components/select';
18 |
19 | export {
20 | alertWrapper,
21 | alertTitle,
22 | AlertActions,
23 | alertInlineButton,
24 | } from './components/alert';
25 |
26 | // Global styles
27 | export GlobalStyles from './globalStyles';
28 |
29 | // Icons
30 | export {
31 | Alert,
32 | Algo,
33 | Book,
34 | Check,
35 | Clipboard,
36 | Collapse,
37 | CopyDrop,
38 | CopySimple,
39 | Dataset,
40 | DownloadDrop,
41 | DownloadSimple,
42 | Expand,
43 | FilterUp,
44 | Folder,
45 | Model,
46 | MoreVertical,
47 | OwkestraLogo,
48 | Permission,
49 | Search,
50 | SubstraLogo,
51 | } from './icons';
52 |
53 | // Variables
54 | export colors from './variables/colors';
55 | export font from './variables/font';
56 | export spacing from './variables/spacing';
57 |
--------------------------------------------------------------------------------
/src/storybook.test.js:
--------------------------------------------------------------------------------
1 | import initStoryshots from '@storybook/addon-storyshots';
2 |
3 | initStoryshots();
4 |
--------------------------------------------------------------------------------
/src/utils/propTypes.js:
--------------------------------------------------------------------------------
1 | import RootPropTypes from 'prop-types';
2 | import {isValidElementType} from 'react-is';
3 |
4 |
5 | /*
6 | * Custom component propTypes
7 | * Adapted from https://github.com/facebook/react/issues/5143 and https://github.com/facebook/react/issues/9125
8 | */
9 | const createComponentPropType = isRequired => (props, propName, componentName) => {
10 | const prop = props[propName];
11 | if (!prop && isRequired) {
12 | throw new Error(`Missing required prop ${propName}`);
13 | }
14 | else if (prop && !isValidElementType(prop)) {
15 | return new Error(`Invalid prop '${propName}' supplied to '${componentName}': the prop is not a valid React component`);
16 | }
17 | };
18 |
19 | const componentPropType = createComponentPropType(false);
20 | componentPropType.isRequired = createComponentPropType(true);
21 |
22 |
23 | const PropTypes = {
24 | ...RootPropTypes,
25 | component: componentPropType,
26 | };
27 |
28 | export default PropTypes;
29 |
--------------------------------------------------------------------------------
/src/variables/colors.js:
--------------------------------------------------------------------------------
1 | export const white = '#ffffff',
2 | iceBlue = '#f7f8f8',
3 | ice = '#e7e8e8',
4 | blueGrey = '#81909d',
5 | gold = '#edc20f',
6 | iceGold = '#fdf8e7',
7 | slate = '#4b6073',
8 | tealish = '#1dbcc0',
9 | darkSkyBlue = '#4ba5d2',
10 | iceBlueTwo = '#edf6fa';
11 |
12 | export default {
13 | white,
14 | iceBlue,
15 | ice,
16 | blueGrey,
17 | gold,
18 | iceGold,
19 | slate,
20 | tealish,
21 | darkSkyBlue,
22 | iceBlueTwo,
23 | };
24 |
--------------------------------------------------------------------------------
/src/variables/font.js:
--------------------------------------------------------------------------------
1 | export const
2 | monospaceFamily = 'SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace',
3 | fontNormalMonospace = '11px',
4 | fontNormal = '12px',
5 | fontLarge = '14px';
6 |
7 |
8 | export default {
9 | monospaceFamily,
10 | fontNormalMonospace,
11 | fontNormal,
12 | fontLarge,
13 | };
14 |
--------------------------------------------------------------------------------
/src/variables/spacing.js:
--------------------------------------------------------------------------------
1 | export const
2 | spacingExtraSmall = '5px',
3 | spacingSmall = '10px',
4 | spacingNormal = '20px',
5 | spacingLarge = '40px';
6 |
7 | export default {
8 | spacingExtraSmall,
9 | spacingSmall,
10 | spacingNormal,
11 | spacingLarge,
12 | };
13 |
--------------------------------------------------------------------------------
/test/mocks/fileMock.js:
--------------------------------------------------------------------------------
1 | module.exports = 'test-file-stub';
2 |
--------------------------------------------------------------------------------
/test/mocks/prismMock.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | ghcolors: {
3 | 'pre[class*="language-"': {},
4 | 'code[class*="language-"]': {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/test/mocks/styleMock.js:
--------------------------------------------------------------------------------
1 | module.exports = {};
2 |
--------------------------------------------------------------------------------
/test/setup.js:
--------------------------------------------------------------------------------
1 | import {toHaveStyle} from '@testing-library/jest-dom';
2 | import '@testing-library/jest-dom/extend-expect';
3 |
4 | expect.extend({toHaveStyle});
5 |
6 | global.Blob = (content, options) => ({content, options});
7 |
--------------------------------------------------------------------------------