├── .eslintignore ├── .eslintrc ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .percy.js ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── babel.config.js ├── jest.config.js ├── package-lock.json ├── package.json ├── renovate.json ├── scripts └── bumpversion.mjs ├── src ├── components │ ├── Accordion.js │ ├── Accordion.test.js │ ├── Button.js │ ├── Button.test.js │ ├── Checkbox.js │ ├── Checkbox.test.js │ ├── CheckboxGroup.js │ ├── Container.js │ ├── Container.test.js │ ├── DatePicker.js │ ├── DatePicker.test.js │ ├── Divider.js │ ├── Divider.test.js │ ├── Dropdown.js │ ├── Dropdown.test.js │ ├── Flex.js │ ├── Flex.test.js │ ├── Footer.js │ ├── Footer.test.js │ ├── Form.js │ ├── Form.test.js │ ├── Frequency.js │ ├── Frequency.test.js │ ├── Grid.js │ ├── Grid.test.js │ ├── Header.js │ ├── Header.test.js │ ├── Icon.js │ ├── Icon.test.js │ ├── Input.js │ ├── Input.test.js │ ├── Link.js │ ├── Link.test.js │ ├── List.js │ ├── List.test.js │ ├── LoadingIcon.js │ ├── LoadingIcon.test.js │ ├── Logo.js │ ├── Message.js │ ├── Message.test.js │ ├── Placeholder.js │ ├── Placeholder.test.js │ ├── RadioGroup.js │ ├── RadioGroup.test.js │ ├── Select.js │ ├── Select.test.js │ ├── ShadowContainer.js │ ├── Stack.js │ ├── Stack.test.js │ ├── Stepper.js │ ├── Stepper.test.js │ ├── Sticky.js │ ├── Sticky.test.js │ ├── Text.js │ ├── Text.test.js │ ├── Textarea.js │ ├── Textarea.test.js │ ├── TimeSpan.js │ ├── TimeSpan.test.js │ ├── VisuallyHidden.js │ ├── icon-list.js │ ├── index.js │ ├── internal │ │ ├── Field.js │ │ ├── FocusVisiblePolyfill.js │ │ ├── InternalCheckbox.js │ │ ├── InternalDropdown.js │ │ ├── InternalInput.js │ │ ├── InternalRadioGroup.js │ │ └── InternalSelect.js │ └── logo-list.js ├── hooks │ ├── internal │ │ ├── useField.js │ │ └── useForm.js │ ├── useAccordion.js │ ├── useAccordionItem.js │ ├── useAllResponsiveProps.js │ ├── useAllResponsiveProps.test.js │ ├── useBackground.js │ ├── useBreakpoint.js │ ├── useFooterLinks.js │ ├── useListType.js │ ├── useResponsiveProp.js │ ├── useResponsivePropsCSS.js │ ├── useResponsivePropsCSS.test.js │ ├── useSticky.js │ ├── useTextStyle.js │ ├── useTheme.js │ └── useWindow.js ├── icons │ ├── arrow-back.js │ ├── arrow-forward.js │ ├── assistance.js │ ├── birthday.js │ ├── blocking.js │ ├── calculator.js │ ├── call.js │ ├── chevron-down.js │ ├── chevron-left.js │ ├── chevron-right.js │ ├── chevron-up.js │ ├── comparison.js │ ├── critical.js │ ├── cross-small.js │ ├── cross.js │ ├── devices.js │ ├── document.js │ ├── download.js │ ├── duplicate.js │ ├── edit.js │ ├── external-link.js │ ├── eye-hidden.js │ ├── eye-visible.js │ ├── face-id.js │ ├── facebook.js │ ├── fingerprint.js │ ├── github.js │ ├── hamburger.js │ ├── home.js │ ├── info-or-minor.js │ ├── instagram.js │ ├── link.js │ ├── linkedin.js │ ├── lock-small.js │ ├── lock.js │ ├── logout.js │ ├── mail.js │ ├── message.js │ ├── notification-new.js │ ├── notification.js │ ├── overflow-menu.js │ ├── payment.js │ ├── person.js │ ├── question.js │ ├── search.js │ ├── select-object.js │ ├── shield.js │ ├── stop.js │ ├── stopwatch-alt.js │ ├── stopwatch.js │ ├── success.js │ ├── tick-small.js │ ├── tick.js │ ├── time.js │ ├── trash.js │ ├── triangle-down.js │ ├── triangle-up.js │ ├── twitter.js │ ├── warning-or-significant.js │ └── youtube.js ├── index.js ├── logos │ ├── gem.js │ └── latitude.js ├── providers │ ├── BasisProvider.js │ ├── BreakpointProvider.js │ ├── LinkProvider.js │ └── WindowProvider.js ├── themes │ └── default │ │ ├── accordion.js │ │ ├── button.js │ │ ├── checkbox.js │ │ ├── dropdown.js │ │ ├── field.js │ │ ├── index.js │ │ ├── input.js │ │ ├── link.js │ │ ├── list.js │ │ ├── radioGroup.js │ │ ├── select.js │ │ ├── stepper.js │ │ ├── text.js │ │ ├── textStyles.js │ │ └── textarea.js └── utils │ ├── array.js │ ├── component.js │ ├── component.test.js │ ├── constants.js │ ├── core.js │ ├── core.test.js │ ├── css.js │ ├── css.test.js │ ├── getDataAttributes.js │ ├── objectPath.js │ ├── sticky.js │ ├── sticky.test.js │ ├── string.js │ ├── string.test.js │ ├── test.js │ ├── theme.js │ └── theme.test.js ├── vercel.json ├── website ├── componentsStatus.js ├── gatsby-browser.js ├── gatsby-config.js ├── gatsby-node.js ├── gatsby-ssr.js ├── package-lock.json ├── package.json ├── react-error-overlay.js └── src │ ├── components │ ├── CacheProviderWithContainer.js │ ├── CheckboxesSetting.js │ ├── ComponentCode.js │ ├── ComponentContainer.js │ ├── ComponentPreview.js │ ├── ComponentStatusIndicator.js │ ├── ErrorBoundary.js │ ├── RadioGroupSetting.js │ ├── RangeSetting.js │ ├── SEO.js │ ├── Sidebar.js │ ├── Splitbee.js │ ├── kitchen-sink │ │ ├── KitchenSinkForm.js │ │ └── KitchenSinkLayout.js │ └── playground │ │ ├── PlaygroundCodeError.js │ │ ├── PlaygroundCodePanel.js │ │ ├── PlaygroundNewScreenSettings.js │ │ ├── PlaygroundScreen.js │ │ ├── PlaygroundScreenSettings.js │ │ ├── PlaygroundSettings.js │ │ ├── PlaygroundSettingsButton.js │ │ ├── PlaygroundSettingsInput.js │ │ ├── recoilState.js │ │ ├── useLocalStorageValue.js │ │ ├── utils.js │ │ └── utils.test.js │ ├── hooks │ ├── useCanary.js │ ├── useCopyToClipboard.js │ ├── useDebounce.js │ └── useLocalStorage.js │ ├── images │ └── gatsby-icon.png │ ├── layouts │ ├── Page.js │ ├── Resources.js │ └── Usage.js │ ├── pages │ ├── 404.js │ ├── colors │ │ ├── accessibility.js │ │ ├── index.js │ │ └── resources.mdx │ ├── components │ │ ├── accordion │ │ │ ├── index.js │ │ │ ├── resources.mdx │ │ │ └── usage.mdx │ │ ├── button │ │ │ ├── index.js │ │ │ ├── resources.mdx │ │ │ └── usage.mdx │ │ ├── checkbox-group │ │ │ ├── index.js │ │ │ ├── resources.mdx │ │ │ └── usage.mdx │ │ ├── checkbox │ │ │ ├── index.js │ │ │ ├── resources.mdx │ │ │ └── usage.mdx │ │ ├── container │ │ │ ├── index.js │ │ │ ├── resources.mdx │ │ │ └── usage.mdx │ │ ├── date-picker │ │ │ ├── index.js │ │ │ ├── resources.mdx │ │ │ └── usage.mdx │ │ ├── divider │ │ │ ├── index.js │ │ │ ├── resources.mdx │ │ │ └── usage.mdx │ │ ├── dropdown │ │ │ ├── index.js │ │ │ ├── resources.mdx │ │ │ └── usage.mdx │ │ ├── flex │ │ │ ├── index.js │ │ │ ├── resources.mdx │ │ │ └── usage.mdx │ │ ├── footer │ │ │ ├── index.js │ │ │ ├── resources.mdx │ │ │ └── usage.mdx │ │ ├── form │ │ │ ├── index.js │ │ │ ├── resources.mdx │ │ │ └── usage.mdx │ │ ├── frequency │ │ │ ├── index.js │ │ │ ├── resources.mdx │ │ │ └── usage.mdx │ │ ├── grid │ │ │ ├── index.js │ │ │ ├── resources.mdx │ │ │ └── usage.mdx │ │ ├── header │ │ │ ├── index.js │ │ │ ├── resources.mdx │ │ │ └── usage.mdx │ │ ├── icon │ │ │ ├── index.js │ │ │ ├── resources.mdx │ │ │ └── usage.mdx │ │ ├── input │ │ │ ├── index.js │ │ │ ├── resources.mdx │ │ │ └── usage.mdx │ │ ├── link │ │ │ ├── index.js │ │ │ ├── resources.mdx │ │ │ └── usage.mdx │ │ ├── list │ │ │ ├── index.js │ │ │ ├── resources.mdx │ │ │ └── usage.mdx │ │ ├── loading-icon │ │ │ ├── index.js │ │ │ ├── resources.mdx │ │ │ └── usage.mdx │ │ ├── message │ │ │ ├── index.js │ │ │ ├── resources.mdx │ │ │ └── usage.mdx │ │ ├── placeholder │ │ │ ├── index.js │ │ │ ├── resources.mdx │ │ │ └── usage.mdx │ │ ├── radio-group │ │ │ ├── index.js │ │ │ ├── resources.mdx │ │ │ └── usage.mdx │ │ ├── select │ │ │ ├── index.js │ │ │ ├── resources.mdx │ │ │ └── usage.mdx │ │ ├── shadow-container │ │ │ ├── index.js │ │ │ ├── resources.mdx │ │ │ └── usage.mdx │ │ ├── stack │ │ │ ├── index.js │ │ │ ├── resources.mdx │ │ │ └── usage.mdx │ │ ├── stepper │ │ │ ├── index.js │ │ │ ├── resources.mdx │ │ │ └── usage.mdx │ │ ├── sticky │ │ │ ├── index.js │ │ │ ├── resources.mdx │ │ │ └── usage.mdx │ │ ├── text │ │ │ ├── index.js │ │ │ ├── resources.mdx │ │ │ └── usage.mdx │ │ ├── textarea │ │ │ ├── index.js │ │ │ ├── resources.mdx │ │ │ └── usage.mdx │ │ └── time-span │ │ │ ├── index.js │ │ │ ├── resources.mdx │ │ │ └── usage.mdx │ ├── index.js │ ├── kitchen-sink │ │ └── components │ │ │ ├── accordion.js │ │ │ ├── button.js │ │ │ ├── checkbox-group.js │ │ │ ├── checkbox.js │ │ │ ├── container.js │ │ │ ├── date-picker.js │ │ │ ├── divider.js │ │ │ ├── dropdown.js │ │ │ ├── flex.js │ │ │ ├── footer.js │ │ │ ├── frequency.js │ │ │ ├── grid.js │ │ │ ├── header.js │ │ │ ├── input.js │ │ │ ├── link.js │ │ │ ├── list.js │ │ │ ├── message.js │ │ │ ├── placeholder.js │ │ │ ├── radio-group.js │ │ │ ├── select.js │ │ │ ├── shadow-container.js │ │ │ ├── stack.js │ │ │ ├── stepper.js │ │ │ ├── sticky.js │ │ │ ├── text.js │ │ │ ├── textarea.js │ │ │ └── time-span.js │ ├── playground │ │ ├── index.js │ │ └── index.test.js │ ├── preview │ │ └── index.js │ ├── spacing │ │ ├── index.js │ │ └── usage.mdx │ └── typography │ │ ├── index.js │ │ └── usage.mdx │ ├── themes │ └── website │ │ ├── button.js │ │ ├── field.js │ │ ├── index.js │ │ ├── input.js │ │ ├── link.js │ │ └── select.js │ └── utils │ ├── ast.js │ ├── ast.test.js │ ├── color.js │ ├── color.test.js │ ├── constants.js │ ├── formatting.js │ ├── meta.js │ ├── meta.test.js │ └── url.js └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | public 4 | website/.cache 5 | website/node_modules 6 | website/public 7 | website/package-lock.json 8 | package-lock.json 9 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": [ 4 | "eslint:recommended", 5 | "plugin:react/recommended", 6 | "plugin:jest/recommended", 7 | "plugin:testing-library/recommended", 8 | "plugin:testing-library/react", 9 | "plugin:jest-dom/recommended", 10 | "plugin:jsx-a11y/strict", 11 | "plugin:import/errors" 12 | ], 13 | "plugins": [ 14 | "import", 15 | "react-hooks", 16 | "jest", 17 | "testing-library", 18 | "jest-dom", 19 | "jsx-a11y" 20 | ], 21 | "rules": { 22 | "no-console": ["error", { "allow": ["error"] }], 23 | "no-unused-vars": [ 24 | "error", 25 | { "argsIgnorePattern": "^_", "ignoreRestSiblings": true } 26 | ], 27 | "import/no-cycle": "error", 28 | "react/jsx-boolean-value": [ 29 | "error", 30 | "never", 31 | { "always": ["initialValue"] } 32 | ], 33 | "react-hooks/rules-of-hooks": "error", 34 | "react-hooks/exhaustive-deps": "error", 35 | "jsx-a11y/label-has-for": "off" 36 | }, 37 | "env": { 38 | "browser": true, 39 | "es6": true, 40 | "node": true, 41 | "jest": true 42 | }, 43 | "settings": { 44 | "react": { 45 | "version": "detect" 46 | }, 47 | "import/resolver": { 48 | "alias": [["basis", "./src"]] 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | node-version: [12.x] 12 | 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v2 16 | 17 | - name: Use Node.js ${{ matrix.node-version }} 18 | uses: actions/setup-node@v2 19 | with: 20 | node-version: ${{ matrix.node-version }} 21 | 22 | - name: Install 23 | run: npm install 24 | 25 | - name: Lint 26 | run: npm run lint 27 | 28 | - name: Test 29 | run: npm test 30 | 31 | - name: Build basis 32 | run: npm run build-basis 33 | 34 | - name: Build website 35 | run: npm run build 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build 2 | dist 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | 11 | # Runtime data 12 | pids 13 | *.pid 14 | *.seed 15 | *.pid.lock 16 | 17 | # Directory for instrumented libs generated by jscoverage/JSCover 18 | lib-cov 19 | 20 | # Coverage directory used by tools like istanbul 21 | coverage 22 | 23 | # nyc test coverage 24 | .nyc_output 25 | 26 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 27 | .grunt 28 | 29 | # Bower dependency directory (https://bower.io/) 30 | bower_components 31 | 32 | # node-waf configuration 33 | .lock-wscript 34 | 35 | # Compiled binary addons (http://nodejs.org/api/addons.html) 36 | build/Release 37 | 38 | # Dependency directories 39 | node_modules/ 40 | jspm_packages/ 41 | 42 | # Typescript v1 declaration files 43 | typings/ 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional eslint cache 49 | .eslintcache 50 | 51 | # Optional REPL history 52 | .node_repl_history 53 | 54 | # Output of 'npm pack' 55 | *.tgz 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # gatsby files 61 | .cache/ 62 | public 63 | website/public 64 | 65 | # Mac files 66 | .DS_Store 67 | 68 | # Yarn 69 | yarn-error.log 70 | .pnp/ 71 | .pnp.js 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # VSCode 76 | .vscode 77 | 78 | # Vercel 79 | .vercel 80 | 81 | # JetBrains 82 | .idea -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npm run prettier && npm run lint && npm test 2 | -------------------------------------------------------------------------------- /.percy.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | version: 1, 3 | snapshot: { 4 | widths: [1280], 5 | }, 6 | "static-snapshots": { 7 | path: "public", 8 | "snapshot-files": "kitchen-sink", 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | public 4 | package-lock.json 5 | website/.cache 6 | website/node_modules 7 | website/public 8 | website/package-lock.json 9 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "useTabs": false, 4 | "tabWidth": 2, 5 | "semi": true, 6 | "singleQuote": false, 7 | "printWidth": 80 8 | } 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Latitude Financial Services 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![CI](https://github.com/LatitudeFinancialOSS/basis/workflows/CI/badge.svg) 2 | 3 | # Basis Design System 4 | 5 | ## Installation 6 | 7 | ```shell 8 | npm install --save basis @emotion/core prop-types 9 | ``` 10 | 11 | Install the fonts that your theme needs. For example, if you are using the default theme: 12 | 13 | ```shell 14 | npm install --save typeface-{montserrat,roboto} 15 | ``` 16 | 17 | ## Usage 18 | 19 | ```jsx 20 | import React from "react"; 21 | import { BasisProvider, defaultTheme, Text } from "basis"; 22 | import "typeface-montserrat"; 23 | import "typeface-roboto"; 24 | 25 | function App() { 26 | return ( 27 | 28 | Hello World 29 | 30 | ); 31 | } 32 | 33 | export default App; 34 | ``` 35 | 36 | ## Developing locally 37 | 38 | ```shell 39 | npm install 40 | npm start 41 | ``` 42 | 43 | ## Thanks 44 | 45 | - [Formidable Labs](https://formidable.com/) for creating [react-live](https://www.npmjs.com/package/react-live). 46 | - [Ryan Seddon](https://twitter.com/ryanseddon) for creating [react-frame-component](https://www.npmjs.com/package/react-frame-component). 47 | - [Sharvil Nanavati](https://twitter.com/snrrrub) for providing the `basis` npm package name. 48 | - [Vercel](https://vercel.com) for outstanding deployment experience. 49 | 50 | ## License 51 | 52 | MIT 53 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | const presets = [ 2 | [ 3 | "@babel/env", 4 | { 5 | modules: process.env.BABEL_ENV === "cjs" ? "cjs" : false, 6 | }, 7 | ], 8 | "@babel/react", 9 | "@emotion/babel-preset-css-prop", 10 | ]; 11 | const plugins = ["@babel/plugin-proposal-class-properties"]; 12 | 13 | module.exports = { 14 | presets, 15 | plugins, 16 | }; 17 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testMatch: [ 3 | "/src/**/*.test.js", 4 | "/website/src/**/*.test.js", 5 | ], 6 | moduleNameMapper: { 7 | "^react($|/.+)": "/node_modules/react$1", 8 | basis: "/src", 9 | "typeface-montserrat": "identity-obj-proxy", 10 | "typeface-roboto": "identity-obj-proxy", 11 | }, 12 | bail: true, 13 | }; 14 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base"], 3 | "schedule": "every weekend", 4 | "baseBranches": ["master", "2.0"], 5 | "packageRules": [ 6 | { 7 | "depTypeList": ["dependencies", "devDependencies"], 8 | "updateTypes": ["minor", "patch"], 9 | "rangeStrategy": "pin", 10 | "groupName": "minor and patch" 11 | }, 12 | { 13 | "depTypeList": ["dependencies", "devDependencies"], 14 | "updateTypes": ["major"], 15 | "rangeStrategy": "pin", 16 | "dependencyDashboardApproval": true, 17 | "dependencyDashboardAutoclose": true 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /src/components/Divider.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import PropTypes from "prop-types"; 3 | import { nanoid } from "nanoid"; 4 | import useTheme from "../hooks/useTheme"; 5 | import { responsiveMarginType } from "../hooks/useResponsiveProp"; 6 | import useResponsivePropsCSS from "../hooks/useResponsivePropsCSS"; 7 | import { responsiveMargin } from "../utils/css"; 8 | 9 | const COLORS = ["grey.t07"]; 10 | const DEFAULT_PROPS = { 11 | color: "grey.t07", 12 | }; 13 | 14 | Divider.DEFAULT_PROPS = DEFAULT_PROPS; 15 | 16 | function Divider(_props) { 17 | const props = { ...DEFAULT_PROPS, ..._props }; 18 | const { color, testId } = props; 19 | const [patternId] = useState(() => `divider-${nanoid()}`); 20 | const theme = useTheme(); 21 | const circleColor = theme.getColor(color); 22 | const responsiveCSS = useResponsivePropsCSS(props, DEFAULT_PROPS, { 23 | margin: responsiveMargin, 24 | }); 25 | 26 | return ( 27 | 35 | 43 | 44 | 45 | 46 | 47 | 57 | 58 | ); 59 | } 60 | 61 | Divider.propTypes = { 62 | ...responsiveMarginType, 63 | color: PropTypes.oneOf(COLORS), 64 | testId: PropTypes.string, 65 | }; 66 | 67 | export default Divider; 68 | -------------------------------------------------------------------------------- /src/components/Divider.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "@testing-library/jest-dom/extend-expect"; 3 | import Divider from "./Divider"; 4 | import { render, screen } from "../utils/test"; 5 | 6 | describe("Divider", () => { 7 | it("with margin", () => { 8 | render(); 9 | 10 | const svg = screen.getByRole("img"); 11 | 12 | expect(svg).toHaveStyle({ 13 | margin: "24px 0px", 14 | }); 15 | }); 16 | 17 | it("with testId", () => { 18 | render(); 19 | 20 | const svg = screen.getByRole("img"); 21 | 22 | expect(svg).toHaveAttribute("data-testid", "my-divider"); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/components/Flex.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import useResponsivePropsCSS from "../hooks/useResponsivePropsCSS"; 4 | import { 5 | responsiveMarginType, 6 | responsiveWidthType, 7 | responsiveHeightType, 8 | responsivePropType, 9 | } from "../hooks/useResponsiveProp"; 10 | import { 11 | responsiveMargin, 12 | responsiveSize, 13 | responsiveFlexDirection, 14 | responsiveFlexPlaceItems, 15 | } from "../utils/css"; 16 | import { FLEX_DIRECTIONS, FLEX_PLACE_ITEMS } from "../utils/constants"; 17 | 18 | const DIRECTIONS = FLEX_DIRECTIONS; 19 | const PLACE_ITEMS = FLEX_PLACE_ITEMS; 20 | 21 | const DEFAULT_PROPS = { 22 | direction: "row", 23 | }; 24 | 25 | Flex.DIRECTIONS = DIRECTIONS; 26 | Flex.PLACE_ITEMS = PLACE_ITEMS; 27 | Flex.DEFAULT_PROPS = DEFAULT_PROPS; 28 | 29 | function Flex(_props) { 30 | const props = { ...DEFAULT_PROPS, ..._props }; 31 | const { children, testId } = props; 32 | const wrapperCSS = useResponsivePropsCSS(props, DEFAULT_PROPS, { 33 | margin: responsiveMargin, 34 | width: responsiveSize("width"), 35 | height: responsiveSize("height"), 36 | }); 37 | const flexCSS = useResponsivePropsCSS(props, DEFAULT_PROPS, { 38 | placeItems: responsiveFlexPlaceItems, 39 | direction: responsiveFlexDirection, 40 | }); 41 | 42 | return ( 43 |
50 |
58 | {children} 59 |
60 |
61 | ); 62 | } 63 | 64 | Flex.propTypes = { 65 | ...responsiveMarginType, 66 | ...responsiveWidthType, 67 | ...responsiveHeightType, 68 | ...responsivePropType("direction", PropTypes.oneOf(DIRECTIONS)), 69 | ...responsivePropType("placeItems", PropTypes.oneOf(PLACE_ITEMS)), 70 | children: PropTypes.node.isRequired, 71 | testId: PropTypes.string, 72 | }; 73 | 74 | export default Flex; 75 | -------------------------------------------------------------------------------- /src/components/Flex.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { render } from "../utils/test"; 3 | import "@testing-library/jest-dom/extend-expect"; 4 | import Flex from "./Flex"; 5 | 6 | describe("Flex", () => { 7 | it("with width", () => { 8 | const { container } = render(Content goes here); 9 | 10 | expect(container.firstChild).toHaveStyle({ 11 | width: "320px", 12 | }); 13 | }); 14 | 15 | it("with height", () => { 16 | const { container } = render(Content goes here); 17 | 18 | expect(container.firstChild).toHaveStyle({ 19 | height: "100%", 20 | }); 21 | }); 22 | 23 | it("with testId", () => { 24 | const { container } = render( 25 | Content goes here 26 | ); 27 | 28 | expect(container.firstChild).toHaveAttribute("data-testid", "my-flex"); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /src/components/Grid.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "@testing-library/jest-dom/extend-expect"; 3 | import Grid from "./Grid"; 4 | import { render, screen } from "../utils/test"; 5 | 6 | describe("Grid", () => { 7 | it("with margin", () => { 8 | render(Hello); 9 | 10 | expect(screen.getByText("Hello")).toHaveStyle({ 11 | margin: "24px 0 0 0", 12 | }); 13 | }); 14 | 15 | it("with height", () => { 16 | render(Hello); 17 | 18 | expect(screen.getByText("Hello")).toHaveStyle({ 19 | height: "300px", 20 | }); 21 | }); 22 | 23 | it("with testId", () => { 24 | render( 25 | 26 | Hello 27 | 28 | ); 29 | 30 | const gridItem = screen.getByText("Hello"); 31 | 32 | expect(gridItem).toHaveAttribute("data-testid", "my-grid-item"); 33 | expect(gridItem.parentElement).toHaveAttribute("data-testid", "my-grid"); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/components/Header.js: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from "react"; 2 | import PropTypes from "prop-types"; 3 | import Container from "./Container"; 4 | import Flex from "./Flex"; 5 | import Logo from "./Logo"; 6 | import { getPropsFromMap } from "../utils/component"; 7 | 8 | function HeaderLogo({ name, testId }) { 9 | return ( 10 | 19 | ); 20 | } 21 | 22 | HeaderLogo.propTypes = { 23 | name: PropTypes.oneOf(Logo.NAMES).isRequired, 24 | testId: PropTypes.string, 25 | }; 26 | 27 | const DEFAULT_PROPS = {}; 28 | 29 | Header.DEFAULT_PROPS = DEFAULT_PROPS; 30 | Header.ID = "Header"; 31 | Header.HEIGHT_MAP = { 32 | default: 56, 33 | lg: 80, 34 | }; 35 | 36 | function Header(_props) { 37 | const props = { ...DEFAULT_PROPS, ..._props }; 38 | const { children, testId } = props; 39 | const heightMap = Header.HEIGHT_MAP; 40 | const heightProps = useMemo(() => getPropsFromMap("height", heightMap), [ 41 | heightMap, 42 | ]); 43 | 44 | return ( 45 |
46 | 47 | 48 | 49 | {children} 50 | 51 | 52 | 53 |
54 | ); 55 | } 56 | 57 | Header.propTypes = { 58 | children: PropTypes.node.isRequired, 59 | testId: PropTypes.string, 60 | }; 61 | 62 | Header.Logo = HeaderLogo; 63 | 64 | export default Header; 65 | -------------------------------------------------------------------------------- /src/components/Header.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "@testing-library/jest-dom/extend-expect"; 3 | import Header from "./Header"; 4 | import { render, screen } from "../utils/test"; 5 | 6 | describe("Header", () => { 7 | it("exposes an ID", () => { 8 | expect(Header.ID).toBe("Header"); 9 | }); 10 | 11 | it("exposes a HEIGHT_MAP", () => { 12 | expect(Header.HEIGHT_MAP).toStrictEqual({ 13 | default: 56, 14 | lg: 80, 15 | }); 16 | }); 17 | 18 | it("with testId", () => { 19 | const { container } = render( 20 |
21 | 22 |
23 | ); 24 | 25 | expect(container.firstChild).toHaveAttribute("data-testid", "my-header"); 26 | expect(screen.getByTestId("my-header-logo")).toBeInTheDocument(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/components/Icon.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { render } from "../utils/test"; 3 | import "@testing-library/jest-dom/extend-expect"; 4 | import Icon from "./Icon"; 5 | 6 | describe("Icon", () => { 7 | Icon.NAMES.forEach((name) => { 8 | it(`Icon - ${name}`, () => { 9 | const { container } = render(); 10 | const svg = container.firstChild; 11 | 12 | expect(svg).toHaveAttribute("role", "img"); 13 | expect(svg).toHaveAttribute("data-testid", `my-${name}`); 14 | }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/components/LoadingIcon.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { render } from "../utils/test"; 3 | import "@testing-library/jest-dom/extend-expect"; 4 | import LoadingIcon from "./LoadingIcon"; 5 | 6 | describe("LoadingIcon", () => { 7 | it("with testId", () => { 8 | const { container } = render(); 9 | 10 | expect(container.firstChild).toHaveAttribute( 11 | "data-testid", 12 | "my-loading-icon" 13 | ); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/components/Logo.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { 4 | responsiveHeightType, 5 | responsiveMaxWidthType, 6 | } from "../hooks/useResponsiveProp"; 7 | import useAllResponsiveProps from "../hooks/useAllResponsiveProps"; 8 | import { logoListMapping } from "./logo-list"; 9 | 10 | const NAMES = ["latitude", "gem"]; 11 | const COLORS = ["primary.blue.t100", "black", "white"]; 12 | 13 | const DEFAULT_PROPS = {}; 14 | 15 | Logo.NAMES = NAMES; 16 | Logo.COLORS = COLORS; 17 | Logo.DEFAULT_PROPS = DEFAULT_PROPS; 18 | 19 | function Logo(_props) { 20 | const props = { ...DEFAULT_PROPS, ..._props }; 21 | const { name, color, testId } = props; 22 | const heightProps = useAllResponsiveProps(props, "height"); 23 | const maxWidthProps = useAllResponsiveProps(props, "maxWidth"); 24 | 25 | if (!NAMES.includes(name)) { 26 | return null; 27 | } 28 | 29 | const LogoComponent = logoListMapping[name]; 30 | const logoProps = { 31 | color, 32 | ...heightProps, 33 | ...maxWidthProps, 34 | testId, 35 | }; 36 | 37 | return ; 38 | } 39 | 40 | Logo.propTypes = { 41 | name: PropTypes.oneOf(NAMES).isRequired, 42 | color: PropTypes.oneOf(COLORS), 43 | ...responsiveHeightType, 44 | ...responsiveMaxWidthType, 45 | testId: PropTypes.string, 46 | }; 47 | 48 | export default Logo; 49 | -------------------------------------------------------------------------------- /src/components/Placeholder.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import useTheme from "../hooks/useTheme"; 4 | import { 5 | responsiveWidthType, 6 | responsiveHeightType, 7 | } from "../hooks/useResponsiveProp"; 8 | import useResponsivePropsCSS from "../hooks/useResponsivePropsCSS"; 9 | import { responsiveSize } from "../utils/css"; 10 | import { mergeProps } from "../utils/component"; 11 | import Flex from "./Flex"; 12 | import Text from "./Text"; 13 | 14 | const DEFAULT_PROPS = { 15 | height: "72px", 16 | label: "Placeholder", 17 | }; 18 | 19 | function Placeholder(props) { 20 | const theme = useTheme(); 21 | const mergedProps = mergeProps( 22 | props, 23 | DEFAULT_PROPS, 24 | {}, 25 | { 26 | label: (label) => typeof label === "string" && label.trim().length > 0, 27 | } 28 | ); 29 | const { label, testId } = mergedProps; 30 | const responsivePropsCSS = useResponsivePropsCSS(props, DEFAULT_PROPS, { 31 | width: responsiveSize("width"), 32 | height: responsiveSize("height"), 33 | }); 34 | 35 | return ( 36 |
45 | 46 | {label} 47 | 48 |
49 | ); 50 | } 51 | 52 | Placeholder.propTypes = { 53 | ...responsiveWidthType, 54 | ...responsiveHeightType, 55 | label: PropTypes.string, 56 | testId: PropTypes.string, 57 | }; 58 | 59 | export default Placeholder; 60 | -------------------------------------------------------------------------------- /src/components/Placeholder.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { render, screen } from "../utils/test"; 3 | import "@testing-library/jest-dom/extend-expect"; 4 | import Placeholder from "./Placeholder"; 5 | 6 | describe("Placeholder", () => { 7 | it("with width", () => { 8 | const { container } = render(); 9 | 10 | expect(container.firstChild).toHaveStyle({ 11 | width: "80px", 12 | }); 13 | }); 14 | 15 | it("with height", () => { 16 | const { container } = render(); 17 | 18 | expect(container.firstChild).toHaveStyle({ 19 | height: "100%", 20 | }); 21 | }); 22 | 23 | it("with label", () => { 24 | render(); 25 | 26 | expect(screen.getByText("Navigation placeholder")).toBeInTheDocument(); 27 | }); 28 | 29 | it("with testId", () => { 30 | const { container } = render(); 31 | 32 | expect(container.firstChild).toHaveAttribute( 33 | "data-testid", 34 | "my-placeholder" 35 | ); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /src/components/Stack.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "@testing-library/jest-dom/extend-expect"; 3 | import { Stack, Text } from "."; 4 | import { render, screen } from "../utils/test"; 5 | 6 | describe("Stack", () => { 7 | it("renders all children", () => { 8 | const { container } = render( 9 | 10 | Item 1 11 | Item 2 12 | Item 3 13 | Item 4 14 | 15 | ); 16 | 17 | expect(container.firstChild.firstChild.childElementCount).toBe(4); 18 | expect(screen.getByText("Item 1")).toBeInTheDocument(); 19 | expect(screen.getByText("Item 2")).toBeInTheDocument(); 20 | expect(screen.getByText("Item 3")).toBeInTheDocument(); 21 | expect(screen.getByText("Item 4")).toBeInTheDocument(); 22 | }); 23 | 24 | it("with margin", () => { 25 | const { container } = render( 26 | 27 | Hello 28 | World 29 | 30 | ); 31 | 32 | expect(container.firstChild).toHaveStyle({ 33 | margin: "16px 32px", 34 | }); 35 | }); 36 | 37 | it("with width", () => { 38 | const { container } = render( 39 | 40 | Hello 41 | World 42 | 43 | ); 44 | 45 | expect(container.firstChild).toHaveStyle({ 46 | width: "320px", 47 | }); 48 | }); 49 | 50 | it("with flatten", () => { 51 | const { container } = render( 52 | 53 | 1 54 | <> 55 | 2 56 | 3 57 | 58 | 4 59 | 60 | ); 61 | 62 | expect(container.firstChild.firstChild.childElementCount).toBe(4); 63 | }); 64 | 65 | it("with testId", () => { 66 | const { container } = render( 67 | 68 | Hello 69 | World 70 | 71 | ); 72 | 73 | expect(container.firstChild).toHaveAttribute("data-testid", "my-stack"); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /src/components/Stepper.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { render } from "../utils/test"; 3 | import "@testing-library/jest-dom/extend-expect"; 4 | import Stepper from "./Stepper"; 5 | 6 | describe("Stepper", () => { 7 | it("exposes an ID", () => { 8 | expect(Stepper.ID).toBe("Stepper"); 9 | }); 10 | 11 | it("exposes a HEIGHT_MAP", () => { 12 | expect(Stepper.HEIGHT_MAP).toStrictEqual({ 13 | default: 100, 14 | }); 15 | }); 16 | 17 | it("with testId", () => { 18 | const { container } = render( 19 | 20 | 21 | 27 | 28 | 29 | 30 | 31 | ); 32 | 33 | expect(container.firstChild).toHaveAttribute("data-testid", "my-stepper"); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/components/VisuallyHidden.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | // See: https://www.scottohara.me/blog/2017/04/14/inclusively-hidden.html 5 | const css = { 6 | position: "absolute", 7 | width: "1px", 8 | height: "1px", 9 | overflow: "hidden", 10 | whiteSpace: "nowrap", 11 | clip: "rect(0, 0, 0, 0)", 12 | clipPath: "inset(50%)", 13 | }; 14 | 15 | function VisuallyHidden({ children }) { 16 | if (typeof children === "string") { 17 | return {children}; 18 | } 19 | 20 | const child = React.Children.only(children); 21 | 22 | /* 23 | Note: We avoid adding extra DOM elements here since we rely on elements order in some places. 24 | For example, Radio has the following DOM structure: 25 | 26 | 27 | 28 |