├── .browserslistrc ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github └── workflows │ ├── deploy.yml │ └── lint.yml ├── .gitignore ├── .scss-lint.yml ├── .travis.yml ├── .vscode ├── cSpell.json └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── config ├── env.js ├── jest │ ├── cssTransform.js │ ├── fileTransform.js │ └── setup.js ├── paths.js ├── polyfills.js ├── webpack.config.es.js └── webpack.config.lib.js ├── deploy.sh ├── jsconfig.json ├── package.json ├── scripts ├── build.js └── start.js ├── src ├── components │ ├── Accordion │ │ ├── Collapse.js │ │ ├── Context.js │ │ ├── Toggle.js │ │ └── index.js │ ├── AskBox │ │ └── index.js │ ├── Avatar │ │ └── index.js │ ├── Badge │ │ └── index.js │ ├── Breadcrumb │ │ ├── Item.js │ │ └── index.js │ ├── BubbleChat │ │ ├── Context.js │ │ ├── Image.js │ │ └── index.js │ ├── Button │ │ ├── Group.js │ │ └── index.js │ ├── Calender │ │ ├── Calendar.js │ │ ├── DatePicker.js │ │ ├── DateRangePicker.js │ │ ├── TimePicker.js │ │ ├── index.js │ │ └── v2 │ │ │ └── DatePicker │ │ │ ├── DateInput.jsx │ │ │ ├── DateInput.spec.jsx │ │ │ ├── DateInput │ │ │ ├── DayInput.jsx │ │ │ ├── DayInput.spec.jsx │ │ │ ├── Input.jsx │ │ │ ├── MonthInput.jsx │ │ │ ├── MonthInput.spec.jsx │ │ │ ├── MonthSelect.jsx │ │ │ ├── MonthSelect.spec.jsx │ │ │ ├── NativeInput.jsx │ │ │ ├── NativeInput.spec.jsx │ │ │ ├── YearInput.jsx │ │ │ └── YearInput.spec.jsx │ │ │ ├── DatePicker.jsx │ │ │ ├── DatePicker.spec.jsx │ │ │ ├── Divider.jsx │ │ │ └── shared │ │ │ ├── dateFormatter.js │ │ │ ├── dates.js │ │ │ ├── dates.spec.js │ │ │ ├── propTypes.js │ │ │ ├── utils.js │ │ │ └── utils.spec.js │ ├── Card │ │ └── index.js │ ├── Carousel │ │ └── index.js │ ├── ChatBox │ │ └── index.js │ ├── Collapse │ │ └── index.js │ ├── Composer │ │ └── index.js │ ├── Counter │ │ └── index.js │ ├── Dropdown │ │ ├── Button.js │ │ ├── Container.js │ │ ├── Context.js │ │ ├── Toggle.js │ │ └── index.js │ ├── EmptyState │ │ └── index.js │ ├── Fade │ │ └── index.js │ ├── FileAttachment │ │ └── index.js │ ├── Form │ │ ├── Check.js │ │ ├── Context.js │ │ ├── Feedback.js │ │ ├── File.js │ │ ├── Group.js │ │ ├── Input.js │ │ ├── InputGroup.js │ │ ├── Label.js │ │ ├── Select.js │ │ └── index.js │ ├── Header │ │ ├── Mobile.js │ │ └── index.js │ ├── Icon │ │ └── index.js │ ├── Loader │ │ └── index.js │ ├── Logo │ │ └── index.js │ ├── Media │ │ └── index.js │ ├── Message │ │ ├── Context.js │ │ ├── Title.js │ │ └── index.js │ ├── Modal │ │ ├── Context.js │ │ ├── Header.js │ │ ├── Inside.js │ │ └── index.js │ ├── MultiSteps │ │ ├── Item.js │ │ └── index.js │ ├── Overlay │ │ ├── Trigger.js │ │ └── index.js │ ├── PageLayout │ │ ├── Body.js │ │ ├── Context.js │ │ ├── Footer.js │ │ ├── Header.js │ │ └── index.js │ ├── Pagination │ │ ├── Item.js │ │ └── index.js │ ├── ProblemInfo │ │ └── index.js │ ├── Progress │ │ └── index.js │ ├── Rating │ │ └── index.js │ ├── SafeAnchor │ │ └── index.js │ ├── SearchBox │ │ └── index.js │ ├── Separator │ │ └── index.js │ ├── SessionType │ │ └── index.js │ ├── SidebarMenu │ │ ├── Context.js │ │ ├── Item.js │ │ ├── SubMenu.js │ │ └── index.js │ ├── Skeleton │ │ └── index.js │ ├── Slider │ │ ├── Handle.js │ │ ├── Range.js │ │ ├── createSliderWithTooltip.js │ │ └── index.js │ ├── Tab │ │ ├── Context.js │ │ ├── Item.js │ │ └── index.js │ ├── Tag │ │ └── index.js │ ├── TagInput │ │ └── index.js │ ├── Toast │ │ └── index.js │ ├── Toggle │ │ └── index.js │ ├── Tooltip │ │ └── index.js │ ├── TopBanner │ │ └── index.js │ └── TopMenu │ │ ├── Context.js │ │ ├── Item.js │ │ ├── SubMenu.js │ │ └── index.js ├── constants │ ├── common.js │ ├── icons.js │ └── messages.js ├── hooks │ ├── useForkRef.js │ ├── useIsFocusVisible.js │ ├── usePopper.js │ ├── useRootClose.js │ └── useWaitForDOMRef.js ├── index.js ├── plugins │ ├── AssetPlugin.js │ ├── PluginArray.js │ └── index.js └── utils │ ├── createBlock.js │ ├── createChainedFunction.js │ ├── setRef.js │ └── triggerBrowserReflow.js └── yarn.lock /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | ie >= 8 3 | edge >= 15 4 | ie_mob >= 10 5 | ff >= 45 6 | chrome >= 45 7 | safari >= 7 8 | opera >= 23 9 | ios >= 7 10 | android >= 4 11 | bb >= 10 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = false 9 | insert_final_newline = false 10 | 11 | [**.json] 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [**.js] 16 | indent_style = space 17 | indent_size = 2 18 | 19 | [**.scss] 20 | indent_style = space 21 | indent_size = 2 -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /config 2 | /scripts -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "browser": true, 5 | "node": true, 6 | "jquery": true, 7 | "jest": true 8 | }, 9 | "extends": "airbnb", 10 | "rules": { 11 | "jsx-a11y/label-has-associated-control": 0, 12 | "space-before-function-paren": 0, 13 | "react/prefer-stateless-function": 0, 14 | "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }], 15 | "linebreak-style": 0, 16 | "global-require": 0, 17 | "eol-last": 0, 18 | "comma-dangle": ["error", "always-multiline"], 19 | "spaced-comment": 0, 20 | "react/require-default-props": 0, 21 | "react/forbid-prop-types": 0, 22 | "jsx-a11y/label-has-for": 0, 23 | "jsx-a11y/anchor-is-valid": 0, 24 | "jsx-a11y/href-no-hash": 0, 25 | "jsx-a11y/interactive-supports-focus": 0, 26 | "jsx-a11y/click-events-have-key-events": 0, 27 | "jsx-a11y/mouse-events-have-key-events": 0, 28 | "jsx-a11y/no-static-element-interactions": 0, 29 | "react/no-did-mount-set-state": 0, 30 | "max-len": 0, 31 | "react/jsx-no-bind": 0, 32 | "class-methods-use-this" :0, 33 | "react/prop-types": 0, 34 | "no-unused-expressions": 0, 35 | "radix": 0, 36 | "object-curly-newline": 0, 37 | "no-console": 0, 38 | "import/no-unresolved": 0, 39 | "import/no-named-as-default": 0, 40 | "import/no-extraneous-dependencies": 0, 41 | "import/extensions": 0, 42 | "prefer-destructuring": 0, 43 | "no-multi-assign": 0, 44 | "function-paren-newline": 0, 45 | "react/no-unescaped-entities": 0, 46 | "no-debugger": 0, 47 | "no-shadow": 0, 48 | "no-plusplus": 0, 49 | "camelcase": 0, 50 | "react/sort-comp": [ 51 | 1, 52 | { 53 | "order": [ 54 | "type-annotations", 55 | "static-methods", 56 | "lifecycle", 57 | "everything-else", 58 | "render" 59 | ] 60 | } 61 | ], 62 | "react/no-multi-comp": 0, 63 | "import/prefer-default-export": 0 64 | }, 65 | "globals": { 66 | "zE": true, 67 | "log": true, 68 | "logToQueue": true, 69 | "jsdom": true 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Use Node.js 16 11 | uses: actions/setup-node@v1 12 | with: 13 | node-version: 16.x 14 | - uses: actions/checkout@v3 15 | with: 16 | submodules: recursive 17 | - run: deploy.sh 18 | if: "github.event.head_commit.message == 'chore: Deploy'" 19 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | env: 8 | CI: true 9 | FORCE_COLOR: 2 10 | NODE: 14.x 11 | 12 | jobs: 13 | lint: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Clone repository 18 | uses: actions/checkout@v2 19 | 20 | - name: Set up Node.js 21 | uses: actions/setup-node@v1 22 | with: 23 | node-version: "${{ env.NODE }}" 24 | 25 | - name: Set up yarn cache 26 | id: yarn-cache 27 | uses: actions/cache@v2 28 | with: 29 | path: '**/node_modules' 30 | key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} 31 | 32 | - name: Install dependencies with yarn 33 | if: steps.yarn-cache.outputs.cache-hit != 'true' 34 | run: yarn install --frozen-lockfile 35 | 36 | - name: Lint 37 | run: yarn lint 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # parcel-bundler cache (https://parceljs.org/) 72 | .cache 73 | 74 | # Next.js build output 75 | .next 76 | 77 | # Nuxt.js build / generate output 78 | .nuxt 79 | dist 80 | 81 | # Gatsby files 82 | .cache/ 83 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 84 | # https://nextjs.org/blog/next-9-1#public-directory-support 85 | # public 86 | 87 | # vuepress build output 88 | .vuepress/dist 89 | 90 | # Serverless directories 91 | .serverless/ 92 | 93 | # FuseBox cache 94 | .fusebox/ 95 | 96 | # DynamoDB Local files 97 | .dynamodb/ 98 | 99 | # TernJS port file 100 | .tern-port 101 | 102 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 103 | 104 | # dependencies 105 | /node_modules 106 | /.pnp 107 | .pnp.js 108 | 109 | # testing 110 | /coverage 111 | 112 | # production 113 | /storybook-static 114 | /lib 115 | /es 116 | /dist 117 | # misc 118 | .DS_Store 119 | .env.local 120 | .env.development.local 121 | .env.test.local 122 | .env.production.local 123 | 124 | npm-debug.log* 125 | yarn-debug.log* 126 | yarn-error.log* 127 | package-lock.json 128 | package.json.backup 129 | -------------------------------------------------------------------------------- /.scss-lint.yml: -------------------------------------------------------------------------------- 1 | 2 | scss_files: 'src/**/**.scss' 3 | plugin_directories: ['.scss-linters'] 4 | plugin_gems: [] 5 | severity: warning 6 | linters: 7 | BorderZero: 8 | enabled: true 9 | convention: zero 10 | NameFormat: 11 | enabled: true 12 | PrivateNamingConvention: 13 | enabled: true 14 | prefix: _ 15 | SingleLinePerProperty: 16 | enabled: true 17 | allow_single_line_rule_sets: false 18 | StringQuotes: 19 | enabled: true 20 | style: double_quotes 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '10' 4 | script: 5 | - npm install -g yarn 6 | - yarn 7 | - yarn build 8 | cache: 9 | directories: 10 | - node_modules 11 | deploy: 12 | provider: s3 13 | access_key_id: ${S3_ACCESS_KEY} 14 | secret_access_key: ${S3_SECRET_ACCESS_KEY} 15 | bucket: ${S3_BUCKET} 16 | region: ${S3_REGION} 17 | skip_cleanup: true 18 | local_dir: dist 19 | upload-dir: dev/design/designsystem/${VERSION} 20 | on: 21 | branch: ${DEPLOY_BRANCH} 22 | notifications: 23 | # TODO: use var 24 | slack: gotitai:jP3x4ahOD7ygOkB2tegwGj0k 25 | -------------------------------------------------------------------------------- /.vscode/cSpell.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1", 3 | "language": "en", 4 | "enabledLanguageIds": [ 5 | "javascript", 6 | "javascriptreact", 7 | "typescript", 8 | "typescriptreact", 9 | "yml" 10 | ], 11 | "allowCompoundWords": false, 12 | "ignorePaths": [ 13 | "node_modules", 14 | "**/node_modules", 15 | "**/node_modules/**", 16 | "node_modules/**", 17 | "*.scss" 18 | ], 19 | "words": [ 20 | "astroturf", 21 | "Docgen", 22 | "Doclet", 23 | "Doclets", 24 | "frontmatter", 25 | "gotitinc", 26 | "lifecycles", 27 | "Menlo", 28 | "monospace", 29 | "noopener", 30 | "noreferrer", 31 | "tutoruniverse" 32 | ] 33 | } 34 | // this will ignore anything the node_modules directory 35 | // the same for this one 36 | // the same for this one 37 | // Doesn't currently work due to how the current working directory is determined. -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2, 3 | "files.trimTrailingWhitespace": true, 4 | "files.insertFinalNewline": true, 5 | "files.trimFinalNewlines": true, 6 | "editor.wordWrap": "on", 7 | "emmet.includeLanguages": { 8 | "javascript": "javascriptreact" 9 | }, 10 | "files.associations": { 11 | "*.mdx": "mdx" 12 | }, 13 | "editor.codeActionsOnSave": { 14 | "source.fixAll.eslint": true 15 | }, 16 | "cSpell.words": [ 17 | "Docgen", 18 | "Doclet", 19 | "Doclets", 20 | "Menlo", 21 | "astroturf", 22 | "frontmatter", 23 | "gotitinc", 24 | "lifecycles", 25 | "monospace", 26 | "noopener", 27 | "noreferrer", 28 | "tutoruniverse" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Release 2.0.3 - January 18, 2022 2 | ### Fixed 3 | * Code base: use absolute imports, rearrange hooks and utils 4 | * TopMenu: TopMenu-itemAfter of TopMenu-subMenu has wrong position (need CSS changes) 5 | * Toast: Upgrade from `react-toastify@6` to version `7.0.4` to fix issue: 6 | - `pauseOnFocusLoss` is not applied to windows already unfocused. [Ref](https://github.com/fkhadra/react-toastify/issues/541) 7 | * BubbleChat: map text className from `variantTextClassNames` using variant (like `variantClassNames`) 8 | * Form/Select: add missing `is-disabled` className on disabled 9 | ### Added 10 | * Icons: `shapes`, `return`, `umbrella`, `game`, `tagCloud`, `one`, `two`, `three`, `four`, `five`, `six`, `seven`, `eight`, `nine`, `ten` 11 | * Message: add test-id `[data-testid="message-close"]` 12 | * Modal/Header: add test-id `[data-testid="modal-close-button"]` 13 | ### Updated 14 | * react-toastify to fixed version 15 | * Support React 17 16 | 17 | ## Release 2.0.2 - January 18, 2021 18 | ### Fixed 19 | * Button: modified variant `secondary`, fixed ActionBar 20 | * FileAttachment: modified prop `fileTypeLabel` 21 | * BubbleChat: fixed UI bug, removed console log, map style of `type=system` to `variant=primaryLight`, improve prop `options` 22 | * PaginationItem: removed console log 23 | * Toast: fixed warning 24 | ### Added 25 | * Breadcrumb: added prop `noHref` 26 | * Icon: added icon `fileImport`, `fileExport`, `bubbles` 27 | ### Updated 28 | * rc-slider, react-slick, react-split-pane, react-toastify to fixed version 29 | 30 | ## Release 2.0.1 - December 15, 2020 31 | ### Fixed 32 | * BubbleChat: pass `avatar` prop value to prop `name` of Avatar, instead of `src` 33 | * Composer: remove defaultProps `tooltipAttachButton` and `tooltipSendButton` 34 | * Button, Tag, Badge: add prop `textClassName` to custom text color 35 | 36 | ## Release 2.0.0 - December 6, 2020 37 | ### Added 38 | * Migrate all from Got It Design System 1.12.11 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Aha logo 4 | 5 |

6 | 7 |

Aha Design System - React

8 |

9 | Collection of React Components for building web applications. 10 |
11 | Explore Aha docs » 12 |
13 |
14 | Report bug 15 | · 16 | Request feature 17 |

18 | 19 | ## Status 20 | [![CI](https://github.com/gotitinc/aha-react/workflows/Lint/badge.svg)](https://github.com/gotitinc/aha-react/actions) 21 | ![npm bundle size](https://img.shields.io/bundlephobia/min/@ahaui/react) 22 | [![npm version](https://img.shields.io/npm/v/@ahaui/react)](https://www.npmjs.com/package/@ahaui/react) 23 | [![dependencies Status](https://img.shields.io/david/gotitinc/aha-react)](https://david-dm.org/gotitinc/aha-react?type=peer) 24 | [![peerDependencies Status](https://img.shields.io/david/peer/gotitinc/aha-react)](https://david-dm.org/gotitinc/aha-react?type=peer) 25 | [![devDependency Status](https://img.shields.io/david/dev/gotitinc/aha-react)](https://david-dm.org/gotitinc/aha-react?type=dev) 26 | 27 | ## Quick start 28 | 29 | ### Installation 30 | You have to install both `@ahaui/react` and `@ahaui/css` 31 | ```sh 32 | # With npm 33 | npm install @ahaui/react @ahaui/css 34 | 35 | # Or with yarn 36 | yarn add @ahaui/react @ahaui/css 37 | ``` 38 | 39 | ### Usage 40 | ```jsx 41 | import '@ahaui/css/dist/index.min.css'; 42 | import React from 'react'; 43 | import { Button } from '@ahaui/react'; 44 | 45 | function Example() { 46 | const onButtonClick = () => { 47 | alert('Aha!'); 48 | }; 49 | 50 | return ( 51 | 56 | ); 57 | } 58 | ``` 59 | **Aha!** Just simple as that! 60 | 61 | ## Customization 62 | 63 | ### Plugins 64 | You can customize specific Aha React Components via plugins! 65 | 66 | For now, to provide your custom assets to [Logo](./src/components/Logo/index.js), [Avatar](./src/components/Avatar/index.js), [EmptyState](./src/components/Logo/index.js), you can use [AssetPlugin](./src/plugins/AssetPlugin.js). 67 | ```jsx 68 | import { AssetPlugin, Plugins, Logo } from '@ahaui/react'; 69 | 70 | const LogoAssetsPlugin = new AssetPlugin({ 71 | prefix: 'logo', 72 | assets: { 73 | mylogo: require('./assets/images/logo/my-logo.svg').default, 74 | foobar: 'https://foo.bar/image.jpg', 75 | } 76 | }); 77 | 78 | Plugins.loadPlugin(LogoAssetsPlugin); 79 | 80 | function Example() { 81 | return ( 82 | 83 | ); 84 | } 85 | ``` 86 | 87 | Various types of plugin will be developed in the future, stay tune! 88 | 89 | Plugins could also be published standalone as a npm package (follow the template [here](https://github.com/gotitinc/aha-plugin-example)). 90 | 91 | ### Custom CSS 92 | Further instruction could be found in [here](https://github.com/gotitinc/aha-css#custom). 93 | 94 | ## Copyright and License 95 | 96 | Code and documentation copyright 2020 the [Got It, Inc.](https://www.got-it.ai) Code released under the [Apache-2.0 License](https://github.com/gotitinc/aha-react/blob/master/LICENSE). 97 | -------------------------------------------------------------------------------- /config/env.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const paths = require('./paths'); 6 | 7 | // Make sure that including paths.js after env.js will read .env variables. 8 | delete require.cache[require.resolve('./paths')]; 9 | 10 | const NODE_ENV = process.env.NODE_ENV; 11 | if (!NODE_ENV) { 12 | throw new Error( 13 | 'The NODE_ENV environment variable is required but was not specified.' 14 | ); 15 | } 16 | 17 | // https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use 18 | const dotenvFiles = [ 19 | `${paths.dotenv}.${process.env.NODE_ENV}`, 20 | paths.dotenv, 21 | ].filter(Boolean); 22 | 23 | // Load environment variables from .env* files. Suppress warnings using silent 24 | // if this file is missing. dotenv will never modify any environment variables 25 | // that have already been set. 26 | // https://github.com/motdotla/dotenv 27 | dotenvFiles.forEach(dotenvFile => { 28 | if (fs.existsSync(dotenvFile)) { 29 | require('dotenv').config({ 30 | path: dotenvFile, 31 | }); 32 | } 33 | }); 34 | 35 | // We support resolving modules according to `NODE_PATH`. 36 | // This lets you use absolute paths in imports inside large monorepos: 37 | // https://github.com/facebookincubator/create-react-app/issues/253. 38 | // It works similar to `NODE_PATH` in Node itself: 39 | // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders 40 | // Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. 41 | // Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims. 42 | // https://github.com/facebookincubator/create-react-app/issues/1023#issuecomment-265344421 43 | // We also resolve them to make sure all tools using them work consistently. 44 | const appDirectory = fs.realpathSync(process.cwd()); 45 | process.env.NODE_PATH = (process.env.NODE_PATH || '') 46 | .split(path.delimiter) 47 | .filter(folder => folder && !path.isAbsolute(folder)) 48 | .map(folder => path.resolve(appDirectory, folder)) 49 | .join(path.delimiter); 50 | 51 | // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be 52 | // injected into the application via DefinePlugin in Webpack configuration. 53 | const REACT_APP = /^REACT_APP_/i; 54 | 55 | function getClientEnvironment(publicUrl) { 56 | const raw = Object.keys(process.env) 57 | .filter(key => REACT_APP.test(key)) 58 | .reduce( 59 | (env, key) => { 60 | env[key] = process.env[key]; 61 | return env; 62 | }, 63 | { 64 | // Useful for determining whether we’re running in production mode. 65 | // Most importantly, it switches React into the correct mode. 66 | NODE_ENV: process.env.NODE_ENV || 'development', 67 | // Useful for resolving the correct path to static assets in `public`. 68 | // For example, . 69 | // This should only be used as an escape hatch. Normally you would put 70 | // images into the `src` and `import` them in code to get their paths. 71 | PUBLIC_URL: publicUrl, 72 | } 73 | ); 74 | // Stringify all values so we can feed into Webpack DefinePlugin 75 | const stringified = { 76 | 'process.env': Object.keys(raw).reduce((env, key) => { 77 | env[key] = JSON.stringify(raw[key]); 78 | return env; 79 | }, {}), 80 | }; 81 | 82 | return { raw, stringified }; 83 | } 84 | 85 | module.exports = getClientEnvironment; 86 | -------------------------------------------------------------------------------- /config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | // This is a custom Jest transformer turning file imports into filenames. 6 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 7 | 8 | module.exports = { 9 | process(src, filename) { 10 | return `module.exports = ${JSON.stringify(path.basename(filename))};`; 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /config/jest/setup.js: -------------------------------------------------------------------------------- 1 | import { configure } from 'enzyme'; 2 | import Adapter from 'enzyme-adapter-react-16'; 3 | import $ from 'jquery'; 4 | 5 | // Setup enzyme 6 | configure({ adapter: new Adapter() }); 7 | 8 | // Mocks 9 | window.LogzioLogger = function() { 10 | this.log = () => {}; 11 | }; 12 | 13 | global.log = jest.fn(); 14 | global.logToQueue = jest.fn(); 15 | 16 | window.scrollTo = jest.fn(); 17 | window.open = jest.fn(); 18 | window.alert = jest.fn(); 19 | 20 | // fetch 21 | global.fetch = require('jest-fetch-mock'); 22 | 23 | 24 | window.$ = window.jQuery = $; 25 | -------------------------------------------------------------------------------- /config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | 6 | // Make sure any symlinks in the project folder are resolved: 7 | // https://github.com/facebookincubator/create-react-app/issues/637 8 | const appDirectory = fs.realpathSync(process.cwd()); 9 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath); 10 | 11 | // config after eject: we're in ./config/ 12 | module.exports = { 13 | dotenv: resolveApp(`.env`), 14 | appBuildES: resolveApp(`es`), 15 | appBuildLib: resolveApp(`lib`), 16 | appBuildDist: resolveApp(`dist`), 17 | appIndexJs: resolveApp('src/index.js'), 18 | appSCSS: resolveApp('scss'), 19 | appPackageJson: resolveApp('package.json'), 20 | appSrc: resolveApp('src'), 21 | yarnLockFile: resolveApp('yarn.lock'), 22 | testsSetup: resolveApp('src/setupTests.js'), 23 | appNodeModules: resolveApp('node_modules'), 24 | }; 25 | -------------------------------------------------------------------------------- /config/polyfills.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | if (typeof Promise === 'undefined') { 4 | // Rejection tracking prevents a common issue where React gets into an 5 | // inconsistent state due to an error, but it gets swallowed by a Promise, 6 | // and the user has no idea what causes React's erratic future behavior. 7 | require('promise/lib/rejection-tracking').enable(); 8 | window.Promise = require('promise/lib/es6-extensions.js'); 9 | } 10 | 11 | if (!String.prototype.startsWith) { 12 | String.prototype.startsWith = function(searchString, position) { 13 | position = position || 0; 14 | return this.indexOf(searchString, position) === position; 15 | }; 16 | } 17 | 18 | if (!NodeList.prototype.forEach && Array.prototype.forEach) { 19 | NodeList.prototype.forEach = Array.prototype.forEach; 20 | } 21 | 22 | require('babel-polyfill'); 23 | 24 | // fetch() polyfill for making API calls. 25 | require('isomorphic-fetch'); 26 | 27 | // Object.assign() is commonly used with React. 28 | // It will use the native implementation if it's present and isn't buggy. 29 | Object.assign = require('object-assign'); 30 | 31 | // In tests, polyfill requestAnimationFrame since jsdom doesn't provide it yet. 32 | // We don't polyfill it in the browser--this is user's responsibility. 33 | if (process.env.NODE_ENV === 'test') { 34 | require('raf').polyfill(global); 35 | } 36 | 37 | (function (arr) { 38 | arr.forEach(function (item) { 39 | if (item.hasOwnProperty('remove')) { 40 | return; 41 | } 42 | Object.defineProperty(item, 'remove', { 43 | configurable: true, 44 | enumerable: true, 45 | writable: true, 46 | value: function remove() { 47 | if (this.parentNode !== null) 48 | this.parentNode.removeChild(this); 49 | } 50 | }); 51 | }); 52 | })([Element.prototype, CharacterData.prototype, DocumentType.prototype]); 53 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | rm -rf es/images 2 | rm -rf lib/images 3 | echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc 4 | npm publish 5 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "module": "commonjs", 5 | "allowSyntheticDefaultImports": true, 6 | "baseUrl": "./src/" 7 | }, 8 | "exclude": [ 9 | "node_modules" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ahaui/react", 3 | "version": "2.0.3", 4 | "main": "lib/index.js", 5 | "module": "es/index.js", 6 | "files": [ 7 | "es/*.{js,map}", 8 | "lib/*.{js,map}", 9 | "CHANGELOG.md" 10 | ], 11 | "scripts": { 12 | "build": "node scripts/build", 13 | "prepublishOnly": "yarn build", 14 | "start": "node scripts/start", 15 | "format": "eslint --ext js src --fix", 16 | "lint": "eslint --ext js src", 17 | "prepack": "clean-package -rm devDependencies scripts", 18 | "postpack": "clean-package restore" 19 | }, 20 | "publishConfig": { 21 | "access": "public" 22 | }, 23 | "dependencies": { 24 | "@restart/hooks": "0.3.25", 25 | "@wojtekmaj/predict-input-value": "1.0.0", 26 | "@wojtekmaj/react-daterange-picker": "2.5.0", 27 | "classnames": "2.2.6", 28 | "dom-helpers": "5.1.0", 29 | "popper.js": "1.15.0", 30 | "prop-types": "15.7.2", 31 | "prop-types-extra": "1.1.0", 32 | "rc-slider": "8.7.1", 33 | "react-date-picker": "7.10.0", 34 | "react-slick": "0.27.13", 35 | "react-split-pane": "0.1.92", 36 | "react-tagsinput": "3.19.0", 37 | "react-textarea-autosize": "7.1.2", 38 | "react-time-picker": "3.9.0", 39 | "react-toastify": "7.0.4", 40 | "react-transition-group": "4.0.0", 41 | "uncontrollable": "7.1.1", 42 | "warning": "4.0.3" 43 | }, 44 | "devDependencies": { 45 | "@babel/core": "7.6.4", 46 | "@babel/plugin-syntax-class-properties": "7.12.1", 47 | "@babel/plugin-syntax-dynamic-import": "7.8.3", 48 | "@babel/plugin-syntax-optional-chaining": "7.8.3", 49 | "autoprefixer": "9.6.1", 50 | "babel-eslint": "10.1.0", 51 | "babel-loader": "8.1.0", 52 | "babel-plugin-module-resolver": "4.1.0", 53 | "babel-preset-react-app": "9.1.2", 54 | "case-sensitive-paths-webpack-plugin": "2.2.0", 55 | "clean-package": "1.0.1", 56 | "dotenv": "8.2.0", 57 | "eslint": "5.12.0", 58 | "eslint-config-airbnb": "17.1.0", 59 | "eslint-config-react-app": "3.0.6", 60 | "eslint-loader": "2.1.1", 61 | "eslint-plugin-flowtype": "3.2.0", 62 | "eslint-plugin-import": "2.14.0", 63 | "eslint-plugin-jsx-a11y": "6.1.2", 64 | "eslint-plugin-react": "7.12.3", 65 | "fs-extra": "7.0.1", 66 | "husky": ">=4", 67 | "lint-staged": "10.5.2", 68 | "react": "16.9.0", 69 | "react-dev-utils": "9.0.4", 70 | "react-dom": "16.9.0", 71 | "webpack": "4.40.2", 72 | "webpack-node-externals": "1.7.2" 73 | }, 74 | "peerDependencies": { 75 | "@ahaui/css": "^2.0.4", 76 | "react": "^16.9.0 || 17.x", 77 | "react-dom": "^16.9.0 || 17.x" 78 | }, 79 | "lint-staged": { 80 | "*.js": "eslint --cache --fix" 81 | }, 82 | "prettier": { 83 | "singleQuote": true, 84 | "trailingComma": "all" 85 | }, 86 | "author": "Got It, Inc.", 87 | "homepage": "https://github.com/gotitinc/aha-react#readme", 88 | "license": "Apache-2.0", 89 | "repository": { 90 | "type": "git", 91 | "url": "git+https://github.com/gotitinc/aha-react.git" 92 | }, 93 | "description": "Aha Design System - React", 94 | "directories": { 95 | "lib": "lib" 96 | }, 97 | "husky": { 98 | "hooks": { 99 | "pre-commit": "lint-staged" 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /scripts/start.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'development'; 5 | process.env.NODE_ENV = 'development'; 6 | 7 | // Makes the script crash on unhandled rejections instead of silently 8 | // ignoring them. In the future, promise rejections that are not handled will 9 | // terminate the Node.js process with a non-zero exit code. 10 | process.on('unhandledRejection', err => { 11 | throw err; 12 | }); 13 | 14 | // Ensure environment variables are read. 15 | require('../config/env'); 16 | 17 | 18 | const chalk = require('chalk'); 19 | const fs = require('fs-extra'); 20 | const webpack = require('webpack'); 21 | const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); 22 | const FileSizeReporter = require('react-dev-utils/FileSizeReporter'); 23 | const paths = require('../config/paths'); 24 | const esConfig = require('../config/webpack.config.es'); 25 | const libConfig = require('../config/webpack.config.lib'); 26 | 27 | const measureFileSizesBeforeBuild = 28 | FileSizeReporter.measureFileSizesBeforeBuild; 29 | 30 | // Warn and crash if required files are missing 31 | if (!checkRequiredFiles([paths.appIndexJs])) { 32 | process.exit(1); 33 | } 34 | 35 | // First, read the current file sizes in build directory. 36 | // This lets us display how much they changed later. 37 | measureFileSizesBeforeBuild(paths.appBuildES) 38 | .then(previousFileSizes => { 39 | // Remove all content but keep the directory so that 40 | // if you're in it, you don't end up in Trash 41 | fs.emptyDirSync(paths.appBuildES); 42 | // Start the webpack build 43 | return build(previousFileSizes, esConfig, 'es'); 44 | }); 45 | 46 | measureFileSizesBeforeBuild(paths.appBuildLib) 47 | .then(previousFileSizes => { 48 | // Remove all content but keep the directory so that 49 | // if you're in it, you don't end up in Trash 50 | fs.emptyDirSync(paths.appBuildLib); 51 | // Start the webpack build 52 | return build(previousFileSizes, libConfig, 'lib'); 53 | }); 54 | 55 | // Create the production build and print the deployment instructions. 56 | function build(previousFileSizes, config, moduleName) { 57 | let compiler = webpack(config); 58 | 59 | compiler.watch({ 60 | aggregateTimeout: 300, // wait so long for more changes 61 | poll: false // use polling instead of native watchers 62 | }, (err, stats) => { 63 | if (err) { 64 | console.error(err); 65 | } 66 | 67 | console.log(`============================== ${chalk.green(moduleName)} ==============================`); 68 | 69 | console.log(stats.toString({ 70 | chunks: false, // Makes the build much quieter 71 | colors: true 72 | })); 73 | 74 | console.log(''); 75 | console.log(''); 76 | }); 77 | } 78 | -------------------------------------------------------------------------------- /src/components/Accordion/Collapse.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import CollapseBase from 'components/Collapse'; 4 | import AccordionContext from './Context'; 5 | 6 | const propTypes = { 7 | /** 8 | * A key that corresponds to the toggler that triggers this collapse's expand or collapse. 9 | */ 10 | eventKey: PropTypes.string.isRequired, 11 | /** Children prop should only contain a single child, and is enforced as such */ 12 | children: PropTypes.element.isRequired, 13 | }; 14 | 15 | const Collapse = React.forwardRef( 16 | ({ children, eventKey, ...props }, ref) => { 17 | const contextEventKey = useContext(AccordionContext); 18 | return ( 19 | 25 |
{React.Children.only(children)}
26 |
27 | ); 28 | }, 29 | ); 30 | Collapse.displayName = 'AccordionCollapse'; 31 | Collapse.defaultProps = {}; 32 | Collapse.propTypes = propTypes; 33 | export default Collapse; 34 | -------------------------------------------------------------------------------- /src/components/Accordion/Context.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const SelectableContext = React.createContext(null); 4 | const AccordionContext = React.createContext(null); 5 | 6 | export default AccordionContext; 7 | -------------------------------------------------------------------------------- /src/components/Accordion/Toggle.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, cloneElement } from 'react'; 2 | import classNames from 'classnames'; 3 | import PropTypes from 'prop-types'; 4 | import AccordionContext, { SelectableContext } from './Context'; 5 | 6 | const propTypes = { 7 | /** 8 | * A key that corresponds to the collapse component that gets triggered 9 | * when this has been clicked. 10 | */ 11 | eventKey: PropTypes.string.isRequired, 12 | 13 | /** A callback function for when this component is clicked */ 14 | onClick: PropTypes.func, 15 | 16 | /** Children prop should only contain a single child, and is enforced as such */ 17 | children: PropTypes.element.isRequired, 18 | }; 19 | export function useAccordionToggle(eventKey, onClick) { 20 | const contextEventKey = useContext(AccordionContext); 21 | const onSelect = useContext(SelectableContext); 22 | return (e) => { 23 | const eventKeyPassed = eventKey === contextEventKey ? null : eventKey; 24 | onSelect(eventKeyPassed, e); 25 | if (onClick) onClick(e); 26 | }; 27 | } 28 | 29 | const AccordionToggle = React.forwardRef(({ className, eventKey, onClick, children, disabled, ...props }, ref) => { 30 | const onAccordionClick = useAccordionToggle(eventKey, onClick); 31 | return cloneElement(children, { 32 | className: classNames( 33 | 'Accordion-toggle', 34 | disabled ? ' u-pointerEventsNone u-cursorNotAllow' : 'u-cursorPointer', 35 | className && className 36 | ), 37 | onClick: !disabled ? onAccordionClick : null, 38 | ...props, 39 | ref, 40 | children, 41 | }); 42 | }); 43 | AccordionToggle.propTypes = propTypes; 44 | AccordionToggle.defaultProps = {}; 45 | AccordionToggle.displayName = 'AccordionToggle'; 46 | export default AccordionToggle; 47 | -------------------------------------------------------------------------------- /src/components/Accordion/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from 'classnames'; 3 | import PropTypes from 'prop-types'; 4 | import { useUncontrolled } from 'uncontrollable'; 5 | import AccordionContext, { SelectableContext } from './Context'; 6 | import Toggle from './Toggle'; 7 | import Collapse from './Collapse'; 8 | 9 | const propTypes = { 10 | /** 11 | * The current active key that corresponds to the currently expanded card 12 | * 13 | * @controllable onSelect 14 | */ 15 | activeKey: PropTypes.string, 16 | 17 | }; 18 | 19 | const defaultProps = { 20 | 21 | }; 22 | 23 | const Accordion = React.forwardRef(({ className, as: Component = 'div', ...props }, ref) => { 24 | const { 25 | activeKey, 26 | onSelect, 27 | ...controlledProps 28 | } = useUncontrolled(props, { 29 | activeKey: 'onSelect', 30 | }); 31 | 32 | return ( 33 | 34 | 35 | 43 | 44 | 45 | ); 46 | }); 47 | 48 | Accordion.displayName = 'Accordion'; 49 | Accordion.defaultProps = defaultProps; 50 | Accordion.propTypes = propTypes; 51 | Accordion.Toggle = Toggle; 52 | Accordion.Collapse = Collapse; 53 | export default Accordion; 54 | -------------------------------------------------------------------------------- /src/components/AskBox/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from 'classnames'; 3 | import createBlock from 'utils/createBlock'; 4 | 5 | const propTypes = { 6 | 7 | }; 8 | const defaultProps = { 9 | 10 | }; 11 | 12 | const AskBox = React.forwardRef(({ className, ...props }, ref) => ( 13 |
21 | )); 22 | 23 | const Title = createBlock('AskBox-title u-text400 lg:u-text500'); 24 | const Header = createBlock('AskBox-header u-paddingHorizontalSmall lg:u-paddingHorizontalLarge u-paddingTopSmall lg:u-paddingTopMedium u-paddingBottomSmall'); 25 | const Body = createBlock('AskBox-body u-paddingHorizontalSmall lg:u-paddingHorizontalLarge'); 26 | const Footer = createBlock('AskBox-footer u-paddingHorizontalSmall lg:u-paddingHorizontalLarge u-paddingBottomSmall lg:u-paddingBottomMedium'); 27 | const Note = createBlock('AskBox-note u-textCenter u-text200 u-textGray u-paddingTopSmall'); 28 | 29 | AskBox.Title = Title; 30 | AskBox.Header = Header; 31 | AskBox.Body = Body; 32 | AskBox.Footer = Footer; 33 | AskBox.Note = Note; 34 | AskBox.displayName = 'AskBox'; 35 | AskBox.defaultProps = defaultProps; 36 | AskBox.propTypes = propTypes; 37 | 38 | export default AskBox; 39 | -------------------------------------------------------------------------------- /src/components/Avatar/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classNames from 'classnames'; 4 | import Plugins from 'plugins'; 5 | import { PluginType } from 'constants/common'; 6 | 7 | const propTypes = { 8 | /** The Avatar visual name, should be provide via an AssetPlugin with prefix "avatar" */ 9 | name: PropTypes.string, 10 | /** Avatar size variants */ 11 | size: PropTypes.oneOf([ 12 | 'extraSmall', 13 | 'small', 14 | 'medium', 15 | 'large', 16 | 'extraLarge', 17 | 'extraLargePlus', 18 | 'huge', 19 | ]), 20 | /** Providing a `src` will render an `` element */ 21 | src: PropTypes.string, 22 | /** Providing a `text` will render an `
` element */ 23 | text: PropTypes.string, 24 | /** Providing a alt if `src` exits */ 25 | alt: PropTypes.string, 26 | /** Set the width of the avatar */ 27 | width: PropTypes.number, 28 | /** Set the height of the avatar */ 29 | height: PropTypes.number, 30 | /** 31 | * You can use a custom element type for this component. 32 | * @default div 33 | * */ 34 | as: PropTypes.elementType, 35 | }; 36 | 37 | const defaultProps = { 38 | size: 'medium', 39 | alt: 'Avatar', 40 | }; 41 | 42 | const Avatar = React.forwardRef(({ className, size, name, src, alt, height, width, text, as: Component = 'div', ...props }, ref) => { 43 | let nameOri = name; 44 | let srcOri = src; 45 | let textOri = text; 46 | if (srcOri) { 47 | nameOri = false; 48 | textOri = false; 49 | } else if (textOri) { 50 | nameOri = false; 51 | } else if (nameOri) { 52 | srcOri = Plugins 53 | .getPlugins(PluginType.ASSET) 54 | .traverseCall('getAsset', 'avatar', nameOri) 55 | .find(asset => !!asset); 56 | } 57 | const heightStyle = { 58 | width: width && width, 59 | height: height && height, 60 | }; 61 | const mergeProps = { 62 | ref, 63 | style: { ...props.style, ...heightStyle }, 64 | ...props, 65 | }; 66 | return ( 67 | 77 | {srcOri && ( 78 | {alt} 79 | )} 80 | {textOri && ( 81 |
{textOri}
82 | )} 83 |
84 | ); 85 | }); 86 | 87 | 88 | Avatar.displayName = 'Avatar'; 89 | Avatar.defaultProps = defaultProps; 90 | Avatar.propTypes = propTypes; 91 | export default Avatar; 92 | -------------------------------------------------------------------------------- /src/components/Badge/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classNames from 'classnames'; 4 | 5 | const propTypes = { 6 | /** The Badge visual variant */ 7 | variant: PropTypes.oneOf([ 8 | 'default', 9 | 'white', 10 | 'black', 11 | 'primary', 12 | 'primary_subtle', 13 | 'warning', 14 | 'warning_subtle', 15 | 'positive', 16 | 'positive_subtle', 17 | 'information', 18 | 'information_subtle', 19 | 'negative', 20 | 'negative_subtle', 21 | ]), 22 | 23 | /** Fixed className for text color, just available for variant: `primary`, `primary_subtle` */ 24 | textClassName: PropTypes.oneOfType([ 25 | PropTypes.string, 26 | PropTypes.arrayOf(PropTypes.string), 27 | ]), 28 | /** 29 | * You can use a custom element type for this component. 30 | * @default span 31 | */ 32 | as: PropTypes.elementType, 33 | }; 34 | const defaultProps = { 35 | variant: 'default', 36 | }; 37 | 38 | 39 | const variantsTextClassName = { 40 | primary: 'u-textWhite hover:u-textWhite', 41 | primary_subtle: 'u-textPrimary hover:u-textPrimary', 42 | }; 43 | 44 | const variantsClassName = { 45 | default: 'u-textGray hover:u-textGray u-backgroundUltraLight', 46 | white: 'u-texDark hover:u-texDark u-backgroundWhite', 47 | black: 'u-textWhite hover:u-textWhite u-backgroundBlack', 48 | primary: 'u-backgroundPrimary', 49 | primary_subtle: 'u-backgroundPrimaryLighter', 50 | information: 'u-textWhite hover:u-textWhite u-backgroundInformation', 51 | information_subtle: 'u-textInformation hover:u-textInformation u-backgroundInformationLighter', 52 | warning: 'u-textDark hover:u-textDark u-backgroundWarning', 53 | warning_subtle: 'u-textDark hover:u-textDark u-backgroundWarningLighter', 54 | positive: 'u-textWhite hover:u-textWhite u-backgroundPositive', 55 | positive_subtle: 'u-textPositive hover:u-textPositive u-backgroundPositiveLighter', 56 | negative: 'u-textWhite hover:u-textWhite u-backgroundNegative', 57 | negative_subtle: 'u-textNegative hover:u-textNegative u-backgroundNegativeLighter', 58 | }; 59 | 60 | const Badge = React.forwardRef(({ className, textClassName, variant, as: Component = 'span', ...props }, ref) => ( 61 | 72 | )); 73 | 74 | Badge.displayName = 'Badge'; 75 | Badge.defaultProps = defaultProps; 76 | Badge.propTypes = propTypes; 77 | export default Badge; 78 | -------------------------------------------------------------------------------- /src/components/Breadcrumb/Item.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from 'classnames'; 3 | import PropTypes from 'prop-types'; 4 | import SafeAnchor from 'components/SafeAnchor'; 5 | 6 | const propTypes = { 7 | 8 | /** 9 | * `href` attribute for the inner `a` element 10 | * @default # 11 | */ 12 | href: PropTypes.string, 13 | /** 14 | * Non-render the SafeAnchor 15 | */ 16 | noHref: PropTypes.bool, 17 | /** 18 | * `title` attribute for the inner `a` element 19 | */ 20 | title: PropTypes.node, 21 | /** 22 | * `target` attribute for the inner `a` element 23 | */ 24 | target: PropTypes.string, 25 | 26 | }; 27 | const defaultProps = { 28 | 29 | }; 30 | 31 | const Item = React.forwardRef(({ className, children, noHref, position, schema, isLast, ...props }, ref) => { 32 | const Component = isLast || noHref ? 'span' : SafeAnchor; 33 | return ( 34 |
  • 41 | {schema ? ( 42 | 43 | 51 | {children} 52 | 53 | 54 | 55 | ) : ( 56 | 63 | {children} 64 | 65 | )} 66 |
  • 67 | ); 68 | }); 69 | 70 | Item.displayName = 'BreadcrumbItem'; 71 | Item.defaultProps = defaultProps; 72 | Item.propTypes = propTypes; 73 | export default Item; 74 | -------------------------------------------------------------------------------- /src/components/Breadcrumb/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classNames from 'classnames'; 4 | import Item from './Item'; 5 | 6 | const propTypes = { 7 | /** Enable Structured Data `https://schema.org/BreadcrumbList` */ 8 | schema: PropTypes.bool, 9 | }; 10 | const defaultProps = { 11 | schema: false, 12 | }; 13 | 14 | const Breadcrumb = React.forwardRef(({ className, schema, children, ...props }, ref) => { 15 | let schemasList; 16 | let schemasItem; 17 | if (schema) { 18 | schemasList = { 19 | itemScope: true, 20 | itemType: 'http://schema.org/BreadcrumbList', 21 | }; 22 | schemasItem = { 23 | itemScope: true, 24 | itemProp: 'itemListElement', 25 | itemType: 'http://schema.org/ListItem', 26 | }; 27 | } 28 | const numChildren = React.Children.count(children); 29 | const modifiedChildren = React.Children.map(children, (child, index) => { 30 | if (!child) { 31 | return null; 32 | } 33 | return React.cloneElement( 34 | child, ({ 35 | ...schemasItem, 36 | schema, 37 | isLast: index === numChildren - 1, 38 | position: index + 1, 39 | }) 40 | ); 41 | }); 42 | return ( 43 |
      52 | {modifiedChildren} 53 |
    54 | ); 55 | }); 56 | 57 | Breadcrumb.displayName = 'Breadcrumb'; 58 | Breadcrumb.defaultProps = defaultProps; 59 | Breadcrumb.propTypes = propTypes; 60 | Breadcrumb.Item = Item; 61 | export default Breadcrumb; 62 | -------------------------------------------------------------------------------- /src/components/BubbleChat/Context.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Context = React.createContext(); 4 | 5 | export default Context; 6 | -------------------------------------------------------------------------------- /src/components/BubbleChat/Image.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import classNames from 'classnames'; 3 | import Context from './Context'; 4 | 5 | 6 | const BubbleChatImage = React.forwardRef(({ className, ...props }, ref) => { 7 | const { type } = useContext(Context); 8 | return ( 9 |
    18 | 19 |
    20 | ); 21 | }); 22 | 23 | BubbleChatImage.displayName = 'BubbleChat.Image'; 24 | BubbleChatImage.defaultProps = {}; 25 | BubbleChatImage.propTypes = {}; 26 | export default BubbleChatImage; 27 | -------------------------------------------------------------------------------- /src/components/Button/Group.js: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classNames from 'classnames'; 4 | import Context from 'components/Form/Context'; 5 | 6 | const propTypes = { 7 | /** 8 | * Sets the size for all Buttons in the group. 9 | * @default 'medium' 10 | * @type {('small'|'medium'|'large')} 11 | * */ 12 | sizeControl: PropTypes.string, 13 | /** Sets the disabled state for all Buttons in the group. */ 14 | disabledControl: PropTypes.bool, 15 | }; 16 | 17 | const defaultProps = { 18 | disabledControl: false, 19 | }; 20 | 21 | const Group = React.forwardRef(({ className, sizeControl, disabledControl, as: Component = 'div', ...props }, ref) => { 22 | const context = useMemo(() => ({ sizeControl, disabledControl }), [sizeControl, disabledControl]); 23 | return ( 24 | 25 | 33 | 34 | ); 35 | } 36 | ); 37 | Group.displayName = 'ButtonGroup'; 38 | Group.propTypes = propTypes; 39 | Group.defaultProps = defaultProps; 40 | export default Group; 41 | -------------------------------------------------------------------------------- /src/components/Calender/Calendar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from 'classnames'; 3 | import CalendarBase from 'react-calendar/dist/entry.nostyle'; 4 | 5 | const Calendar = React.forwardRef(({ className, ...props }, ref) => ( 6 | 14 | )); 15 | 16 | Calendar.displayName = 'Calendar'; 17 | Calendar.defaultProps = {}; 18 | Calendar.propTypes = {}; 19 | 20 | export default Calendar; 21 | -------------------------------------------------------------------------------- /src/components/Calender/DatePicker.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import classNames from 'classnames'; 3 | import PropTypes from 'prop-types'; 4 | import DatePickerBase from 'react-date-picker/dist/entry.nostyle'; 5 | import Context from 'components/Form/Context'; 6 | import Icon from 'components/Icon'; 7 | import DatePickerBaseV2 from './v2/DatePicker/DatePicker'; 8 | 9 | const DatePicker = React.forwardRef(({ className, noClearIcon, size, version, calendarClassName, ...props }, ref) => { 10 | const { sizeControl } = useContext(Context); 11 | const sizeOri = size || sizeControl; 12 | const BaseClass = (version === 2) ? DatePickerBaseV2 : DatePickerBase; 13 | return ( 14 | } 24 | calendarIcon={} 25 | calendarClassName={classNames( 26 | 'Calendar u-marginTopExtraSmall', 27 | calendarClassName && calendarClassName, 28 | )} 29 | /> 30 | ); 31 | }); 32 | 33 | DatePicker.displayName = 'DatePicker'; 34 | DatePicker.defaultProps = { 35 | noClearIcon: false, 36 | version: 1, 37 | }; 38 | DatePicker.propTypes = { 39 | /** 40 | * Remove clear Icon 41 | */ 42 | noClearIcon: PropTypes.bool, 43 | /** 44 | * DatePicker size variants 45 | * 46 | * Uses sizeControl from `` if not explicitly specified. 47 | * @default 'medium' 48 | * */ 49 | size: PropTypes.oneOf(['small', 'medium', 'large']), 50 | /** 51 | * DatePicker version: 52 | * - 1: default version, import from 'react-date-picker' 53 | * - 2: customized version 54 | * 55 | * @default 1 56 | * */ 57 | version: PropTypes.oneOf([1, 2]), 58 | /** 59 | * Custom className for pop-up Calendar 60 | * */ 61 | calendarClassName: PropTypes.string, 62 | }; 63 | 64 | export default DatePicker; 65 | -------------------------------------------------------------------------------- /src/components/Calender/DateRangePicker.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classNames from 'classnames'; 4 | import DateRangePickerBase from '@wojtekmaj/react-daterange-picker/dist/entry.nostyle'; 5 | import Icon from 'components/Icon'; 6 | import Context from 'components/Form/Context'; 7 | 8 | const DateRangePicker = React.forwardRef(({ className, noClearIcon, size, ...props }, ref) => { 9 | const { sizeControl } = useContext(Context); 10 | const sizeOri = size || sizeControl; 11 | return ( 12 | } 22 | calendarIcon={} 23 | calendarClassName={classNames( 24 | 'Calendar u-marginTopExtraSmall' 25 | )} 26 | /> 27 | ); 28 | }); 29 | 30 | DateRangePicker.displayName = 'DateRangePicker'; 31 | DateRangePicker.defaultProps = { 32 | noClearIcon: false, 33 | }; 34 | DateRangePicker.propTypes = { 35 | /** 36 | * Remove clear Icon 37 | */ 38 | noClearIcon: PropTypes.bool, 39 | /** 40 | * DateRangePicker size variants 41 | * 42 | * Uses sizeControl from `` if not explicitly specified. 43 | * @default 'medium' 44 | * */ 45 | size: PropTypes.oneOf(['small', 'medium', 'large']), 46 | }; 47 | 48 | export default DateRangePicker; 49 | -------------------------------------------------------------------------------- /src/components/Calender/TimePicker.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classNames from 'classnames'; 4 | import TimePickerBase from 'react-time-picker/dist/entry.nostyle'; 5 | import Icon from 'components/Icon'; 6 | import Context from 'components/Form/Context'; 7 | 8 | const TimePicker = React.forwardRef(({ className, noClearIcon, size, ...props }, ref) => { 9 | const { sizeControl } = useContext(Context); 10 | const sizeOri = size || sizeControl; return ( 11 | } 21 | clockIcon={} 22 | calendarClassName={classNames( 23 | 'Calendar u-marginTopExtraSmall' 24 | )} 25 | /> 26 | ); 27 | }); 28 | 29 | TimePicker.displayName = 'TimePicker'; 30 | TimePicker.defaultProps = { 31 | noClearIcon: false, 32 | }; 33 | TimePicker.propTypes = { 34 | /** 35 | * Remove clear Icon 36 | */ 37 | noClearIcon: PropTypes.bool, 38 | /** 39 | * TimePicker size variants 40 | * 41 | * Uses sizeControl from `` if not explicitly specified. 42 | * @default 'medium' 43 | * */ 44 | size: PropTypes.oneOf(['small', 'medium', 'large']), 45 | }; 46 | 47 | export default TimePicker; 48 | -------------------------------------------------------------------------------- /src/components/Calender/index.js: -------------------------------------------------------------------------------- 1 | export { default as Calendar } from './Calendar'; 2 | export { default as DateRangePicker } from './DateRangePicker'; 3 | export { default as DatePicker } from './DatePicker'; 4 | export { default as TimePicker } from './TimePicker'; 5 | -------------------------------------------------------------------------------- /src/components/Calender/v2/DatePicker/DateInput/DayInput.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { 4 | getYear, 5 | getMonthHuman, 6 | getDate, 7 | getDaysInMonth, 8 | } from '@wojtekmaj/date-utils'; 9 | 10 | import Input from './Input'; 11 | 12 | import { isMaxDate, isMinDate } from '../shared/propTypes'; 13 | import { safeMin, safeMax } from '../shared/utils'; 14 | 15 | export default function DayInput({ 16 | maxDate, 17 | minDate, 18 | month, 19 | year, 20 | ...otherProps 21 | }) { 22 | const currentMonthMaxDays = (() => { 23 | if (!month) { 24 | return 31; 25 | } 26 | 27 | return getDaysInMonth(new Date(year, month - 1, 1)); 28 | })(); 29 | 30 | function isSameMonth(date) { 31 | return date && year === getYear(date) && month === getMonthHuman(date); 32 | } 33 | 34 | const maxDay = safeMin(currentMonthMaxDays, isSameMonth(maxDate) && getDate(maxDate)); 35 | const minDay = safeMax(1, isSameMonth(minDate) && getDate(minDate)); 36 | 37 | return ( 38 | 44 | ); 45 | } 46 | 47 | const isNumberOrString = PropTypes.oneOfType([PropTypes.number, PropTypes.string]); 48 | 49 | DayInput.propTypes = { 50 | ariaLabel: PropTypes.string, 51 | className: PropTypes.string.isRequired, 52 | disabled: PropTypes.bool, 53 | itemRef: PropTypes.func, 54 | maxDate: isMaxDate, 55 | minDate: isMinDate, 56 | month: isNumberOrString, 57 | onChange: PropTypes.func, 58 | onKeyDown: PropTypes.func, 59 | onKeyUp: PropTypes.func, 60 | placeholder: PropTypes.string, 61 | required: PropTypes.bool, 62 | showLeadingZeros: PropTypes.bool, 63 | value: isNumberOrString, 64 | year: isNumberOrString, 65 | }; 66 | -------------------------------------------------------------------------------- /src/components/Calender/v2/DatePicker/DateInput/Input.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import mergeClassNames from 'merge-class-names'; 4 | import updateInputWidth, { getFontShorthand } from 'update-input-width'; 5 | import predictInputValue from '@wojtekmaj/predict-input-value'; 6 | 7 | /* eslint-disable jsx-a11y/no-autofocus */ 8 | 9 | function onFocus(event) { 10 | const { target } = event; 11 | 12 | requestAnimationFrame(() => target.select()); 13 | } 14 | 15 | function updateInputWidthOnFontLoad(element) { 16 | if (!document.fonts) { 17 | return; 18 | } 19 | 20 | const font = getFontShorthand(element); 21 | 22 | if (!font) { 23 | return; 24 | } 25 | 26 | const isFontLoaded = document.fonts.check(font); 27 | 28 | if (isFontLoaded) { 29 | return; 30 | } 31 | 32 | function onLoadingDone() { 33 | updateInputWidth(element); 34 | } 35 | 36 | document.fonts.addEventListener('loadingdone', onLoadingDone); 37 | } 38 | 39 | function makeOnKeyPress(max) { 40 | return function onKeyPress(event) { 41 | const { key } = event; 42 | 43 | const isNumberKey = !isNaN(parseInt(key, 10)); 44 | const nextValue = predictInputValue(event); 45 | 46 | if (isNumberKey && (nextValue <= max)) { 47 | return; 48 | } 49 | 50 | event.preventDefault(); 51 | }; 52 | } 53 | 54 | export default function Input({ 55 | ariaLabel, 56 | autoFocus, 57 | className, 58 | disabled, 59 | itemRef, 60 | max, 61 | min, 62 | name, 63 | nameForClass, 64 | onChange, 65 | onKeyDown, 66 | onKeyUp, 67 | placeholder = '--', 68 | required, 69 | showLeadingZeros, 70 | step, 71 | value, 72 | }) { 73 | const hasLeadingZero = showLeadingZeros && value !== null && value < 10; 74 | 75 | return [ 76 | (hasLeadingZero && 0), 77 | { 98 | updateInputWidth(event.target); 99 | 100 | if (onKeyUp) { 101 | onKeyUp(event); 102 | } 103 | }} 104 | placeholder={placeholder} 105 | ref={(ref) => { 106 | if (ref) { 107 | updateInputWidth(ref); 108 | updateInputWidthOnFontLoad(ref); 109 | } 110 | 111 | if (itemRef) { 112 | itemRef(ref, name); 113 | } 114 | }} 115 | required={required} 116 | step={step} 117 | type="text" 118 | value={value !== null ? value : ''} 119 | />, 120 | ]; 121 | } 122 | 123 | Input.propTypes = { 124 | ariaLabel: PropTypes.string, 125 | className: PropTypes.string.isRequired, 126 | disabled: PropTypes.bool, 127 | itemRef: PropTypes.func, 128 | max: PropTypes.number, 129 | min: PropTypes.number, 130 | onChange: PropTypes.func, 131 | onKeyDown: PropTypes.func, 132 | onKeyUp: PropTypes.func, 133 | required: PropTypes.bool, 134 | showLeadingZeros: PropTypes.bool, 135 | step: PropTypes.number, 136 | value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 137 | }; 138 | -------------------------------------------------------------------------------- /src/components/Calender/v2/DatePicker/DateInput/MonthInput.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { getYear, getMonthHuman } from '@wojtekmaj/date-utils'; 4 | 5 | import Input from './Input'; 6 | 7 | import { isMaxDate, isMinDate } from '../shared/propTypes'; 8 | import { safeMin, safeMax } from '../shared/utils'; 9 | 10 | export default function MonthInput({ 11 | maxDate, 12 | minDate, 13 | year, 14 | ...otherProps 15 | }) { 16 | function isSameYear(date) { 17 | return date && year === getYear(date); 18 | } 19 | 20 | const maxMonth = safeMin(12, isSameYear(maxDate) && getMonthHuman(maxDate)); 21 | const minMonth = safeMax(1, isSameYear(minDate) && getMonthHuman(minDate)); 22 | 23 | return ( 24 | 30 | ); 31 | } 32 | 33 | const isNumberOrString = PropTypes.oneOfType([PropTypes.number, PropTypes.string]); 34 | 35 | MonthInput.propTypes = { 36 | ariaLabel: PropTypes.string, 37 | className: PropTypes.string.isRequired, 38 | disabled: PropTypes.bool, 39 | itemRef: PropTypes.func, 40 | maxDate: isMaxDate, 41 | minDate: isMinDate, 42 | onChange: PropTypes.func, 43 | onKeyDown: PropTypes.func, 44 | onKeyUp: PropTypes.func, 45 | placeholder: PropTypes.string, 46 | required: PropTypes.bool, 47 | showLeadingZeros: PropTypes.bool, 48 | value: isNumberOrString, 49 | year: isNumberOrString, 50 | }; 51 | -------------------------------------------------------------------------------- /src/components/Calender/v2/DatePicker/DateInput/MonthSelect.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import mergeClassNames from 'merge-class-names'; 4 | import { getYear, getMonthHuman } from '@wojtekmaj/date-utils'; 5 | 6 | import { formatMonth, formatShortMonth } from '../shared/dateFormatter'; 7 | import { isMaxDate, isMinDate } from '../shared/propTypes'; 8 | import { safeMin, safeMax } from '../shared/utils'; 9 | 10 | export default function MonthSelect({ 11 | ariaLabel, 12 | className, 13 | itemRef, 14 | locale, 15 | maxDate, 16 | minDate, 17 | placeholder = '--', 18 | short, 19 | value, 20 | year, 21 | ...otherProps 22 | }) { 23 | function isSameYear(date) { 24 | return date && year === getYear(date); 25 | } 26 | 27 | const maxMonth = safeMin(12, isSameYear(maxDate) && getMonthHuman(maxDate)); 28 | const minMonth = safeMax(1, isSameYear(minDate) && getMonthHuman(minDate)); 29 | const dates = [...Array(12)].map((el, index) => new Date(2019, index, 1)); 30 | const name = 'month'; 31 | const formatter = short ? formatShortMonth : formatMonth; 32 | 33 | return ( 34 | 69 | ); 70 | } 71 | 72 | const isNumberOrString = PropTypes.oneOfType([PropTypes.number, PropTypes.string]); 73 | 74 | MonthSelect.propTypes = { 75 | ariaLabel: PropTypes.string, 76 | className: PropTypes.string.isRequired, 77 | disabled: PropTypes.bool, 78 | itemRef: PropTypes.func, 79 | locale: PropTypes.string, 80 | maxDate: isMaxDate, 81 | minDate: isMinDate, 82 | onChange: PropTypes.func, 83 | onKeyDown: PropTypes.func, 84 | onKeyUp: PropTypes.func, 85 | placeholder: PropTypes.string, 86 | required: PropTypes.bool, 87 | short: PropTypes.bool, 88 | value: isNumberOrString, 89 | year: isNumberOrString, 90 | }; 91 | -------------------------------------------------------------------------------- /src/components/Calender/v2/DatePicker/DateInput/NativeInput.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { 4 | getYear, 5 | getISOLocalDate, 6 | getISOLocalMonth, 7 | } from '@wojtekmaj/date-utils'; 8 | 9 | import { isMaxDate, isMinDate, isValueType } from '../shared/propTypes'; 10 | 11 | export default function NativeInput({ 12 | ariaLabel, 13 | disabled, 14 | maxDate, 15 | minDate, 16 | name, 17 | onChange, 18 | required, 19 | value, 20 | valueType, 21 | }) { 22 | const nativeInputType = (() => { 23 | switch (valueType) { 24 | case 'decade': 25 | case 'year': 26 | return 'number'; 27 | case 'month': 28 | return 'month'; 29 | case 'day': 30 | return 'date'; 31 | default: 32 | throw new Error('Invalid valueType.'); 33 | } 34 | })(); 35 | 36 | const nativeValueParser = (() => { 37 | switch (valueType) { 38 | case 'century': 39 | case 'decade': 40 | case 'year': 41 | return getYear; 42 | case 'month': 43 | return getISOLocalMonth; 44 | case 'day': 45 | return getISOLocalDate; 46 | default: 47 | throw new Error('Invalid valueType.'); 48 | } 49 | })(); 50 | 51 | function stopPropagation(event) { 52 | event.stopPropagation(); 53 | } 54 | 55 | return ( 56 | 74 | ); 75 | } 76 | 77 | NativeInput.propTypes = { 78 | ariaLabel: PropTypes.string, 79 | disabled: PropTypes.bool, 80 | maxDate: isMaxDate, 81 | minDate: isMinDate, 82 | name: PropTypes.string, 83 | onChange: PropTypes.func, 84 | required: PropTypes.bool, 85 | value: PropTypes.oneOfType([ 86 | PropTypes.string, 87 | PropTypes.instanceOf(Date), 88 | ]), 89 | valueType: isValueType, 90 | }; 91 | -------------------------------------------------------------------------------- /src/components/Calender/v2/DatePicker/DateInput/YearInput.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { getYear } from '@wojtekmaj/date-utils'; 4 | 5 | import Input from './Input'; 6 | 7 | import { isMaxDate, isMinDate, isValueType } from '../shared/propTypes'; 8 | import { safeMax, safeMin } from '../shared/utils'; 9 | 10 | export default function YearInput({ 11 | maxDate, 12 | minDate, 13 | placeholder = '----', 14 | valueType, 15 | ...otherProps 16 | }) { 17 | const maxYear = safeMin(275760, maxDate && getYear(maxDate)); 18 | const minYear = safeMax(1, minDate && getYear(minDate)); 19 | 20 | const yearStep = (() => { 21 | if (valueType === 'century') { 22 | return 10; 23 | } 24 | 25 | return 1; 26 | })(); 27 | 28 | return ( 29 | 37 | ); 38 | } 39 | 40 | const isNumberOrString = PropTypes.oneOfType([PropTypes.number, PropTypes.string]); 41 | 42 | YearInput.propTypes = { 43 | ariaLabel: PropTypes.string, 44 | className: PropTypes.string.isRequired, 45 | disabled: PropTypes.bool, 46 | itemRef: PropTypes.func, 47 | maxDate: isMaxDate, 48 | minDate: isMinDate, 49 | onChange: PropTypes.func, 50 | onKeyDown: PropTypes.func, 51 | onKeyUp: PropTypes.func, 52 | placeholder: PropTypes.string, 53 | required: PropTypes.bool, 54 | value: isNumberOrString, 55 | valueType: isValueType, 56 | }; 57 | -------------------------------------------------------------------------------- /src/components/Calender/v2/DatePicker/Divider.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | export default function Divider({ children }) { 5 | return ( 6 | 7 | {children} 8 | 9 | ); 10 | } 11 | 12 | Divider.propTypes = { 13 | children: PropTypes.node, 14 | }; 15 | -------------------------------------------------------------------------------- /src/components/Calender/v2/DatePicker/shared/dateFormatter.js: -------------------------------------------------------------------------------- 1 | import getUserLocale from 'get-user-locale'; 2 | 3 | export function getFormatter(options) { 4 | return (locale, date) => date.toLocaleString(locale || getUserLocale(), options); 5 | } 6 | 7 | /** 8 | * Changes the hour in a Date to ensure right date formatting even if DST is messed up. 9 | * Workaround for bug in WebKit and Firefox with historical dates. 10 | * For more details, see: 11 | * https://bugs.chromium.org/p/chromium/issues/detail?id=750465 12 | * https://bugzilla.mozilla.org/show_bug.cgi?id=1385643 13 | * 14 | * @param {Date} date Date. 15 | */ 16 | function toSafeHour(date) { 17 | const safeDate = new Date(date); 18 | return new Date(safeDate.setHours(12)); 19 | } 20 | 21 | function getSafeFormatter(options) { 22 | return (locale, date) => getFormatter(options)(locale, toSafeHour(date)); 23 | } 24 | 25 | const formatMonthOptions = { month: 'long' }; 26 | const formatShortMonthOptions = { month: 'short' }; 27 | 28 | export const formatMonth = getSafeFormatter(formatMonthOptions); 29 | export const formatShortMonth = getSafeFormatter(formatShortMonthOptions); 30 | -------------------------------------------------------------------------------- /src/components/Calender/v2/DatePicker/shared/dates.js: -------------------------------------------------------------------------------- 1 | import { 2 | getCenturyStart, 3 | getCenturyEnd, 4 | 5 | getDecadeStart, 6 | getDecadeEnd, 7 | 8 | getYearStart, 9 | getYearEnd, 10 | 11 | getMonthStart, 12 | getMonthEnd, 13 | 14 | getDayStart, 15 | getDayEnd, 16 | } from '@wojtekmaj/date-utils'; 17 | 18 | /** 19 | * Returns the beginning of a given range. 20 | * 21 | * @param {String} rangeType Range type (e.g. 'day') 22 | * @param {Date} date Date. 23 | */ 24 | export function getBegin(rangeType, date) { 25 | switch (rangeType) { 26 | case 'century': return getCenturyStart(date); 27 | case 'decade': return getDecadeStart(date); 28 | case 'year': return getYearStart(date); 29 | case 'month': return getMonthStart(date); 30 | case 'day': return getDayStart(date); 31 | default: throw new Error(`Invalid rangeType: ${rangeType}`); 32 | } 33 | } 34 | 35 | /** 36 | * Returns the end of a given range. 37 | * 38 | * @param {String} rangeType Range type (e.g. 'day') 39 | * @param {Date} date Date. 40 | */ 41 | export function getEnd(rangeType, date) { 42 | switch (rangeType) { 43 | case 'century': return getCenturyEnd(date); 44 | case 'decade': return getDecadeEnd(date); 45 | case 'year': return getYearEnd(date); 46 | case 'month': return getMonthEnd(date); 47 | case 'day': return getDayEnd(date); 48 | default: throw new Error(`Invalid rangeType: ${rangeType}`); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/components/Calender/v2/DatePicker/shared/dates.spec.js: -------------------------------------------------------------------------------- 1 | import { 2 | getBegin, 3 | getEnd, 4 | } from './dates'; 5 | 6 | describe('getBegin', () => { 7 | it('returns proper beginning of the century', () => { 8 | const date = new Date(2017, 0, 1); 9 | const beginOfCenturyDate = new Date(2001, 0, 1); 10 | 11 | const beginOfCentury = getBegin('century', date); 12 | 13 | expect(beginOfCentury).toEqual(beginOfCenturyDate); 14 | }); 15 | 16 | it('returns proper beginning of the decade', () => { 17 | const date = new Date(2017, 0, 1); 18 | const beginOfDecadeDate = new Date(2011, 0, 1); 19 | 20 | const beginOfDecade = getBegin('decade', date); 21 | 22 | expect(beginOfDecade).toEqual(beginOfDecadeDate); 23 | }); 24 | 25 | it('returns proper beginning of the year', () => { 26 | const date = new Date(2017, 0, 1); 27 | const beginOfYearDate = new Date(2017, 0, 1); 28 | 29 | const beginOfYear = getBegin('year', date); 30 | 31 | expect(beginOfYear).toEqual(beginOfYearDate); 32 | }); 33 | 34 | it('returns proper beginning of the month', () => { 35 | const date = new Date(2017, 0, 1); 36 | const beginOfMonthDate = new Date(2017, 0, 1); 37 | 38 | const monthRange = getBegin('month', date); 39 | 40 | expect(monthRange).toEqual(beginOfMonthDate); 41 | }); 42 | 43 | it('returns proper beginning of the day', () => { 44 | const date = new Date(2017, 0, 1); 45 | const beginOfDayDate = new Date(2017, 0, 1); 46 | 47 | const beginOfDay = getBegin('day', date); 48 | 49 | expect(beginOfDay).toEqual(beginOfDayDate); 50 | }); 51 | 52 | it('throws an error when given unrecognized range type', () => { 53 | const date = new Date(2017, 0, 1); 54 | 55 | expect(() => getBegin('hamster', date)).toThrow(); 56 | }); 57 | }); 58 | 59 | describe('getEnd', () => { 60 | it('returns proper end of the century', () => { 61 | const date = new Date(2017, 0, 1); 62 | const endOfCenturyDate = new Date(2100, 11, 31, 23, 59, 59, 999); 63 | 64 | const endOfCentury = getEnd('century', date); 65 | 66 | expect(endOfCentury).toEqual(endOfCenturyDate); 67 | }); 68 | 69 | it('returns proper end of the decade', () => { 70 | const date = new Date(2017, 0, 1); 71 | const endOfDecadeDate = new Date(2020, 11, 31, 23, 59, 59, 999); 72 | 73 | const endOfDecade = getEnd('decade', date); 74 | 75 | expect(endOfDecade).toEqual(endOfDecadeDate); 76 | }); 77 | 78 | it('returns proper end of the year', () => { 79 | const date = new Date(2017, 0, 1); 80 | const endOfYearDate = new Date(2017, 11, 31, 23, 59, 59, 999); 81 | 82 | const endOfYear = getEnd('year', date); 83 | 84 | expect(endOfYear).toEqual(endOfYearDate); 85 | }); 86 | 87 | it('returns proper end of the month', () => { 88 | const date = new Date(2017, 0, 1); 89 | const endOfMonthDate = new Date(2017, 0, 31, 23, 59, 59, 999); 90 | 91 | const monthRange = getEnd('month', date); 92 | 93 | expect(monthRange).toEqual(endOfMonthDate); 94 | }); 95 | 96 | it('returns proper end of the day', () => { 97 | const date = new Date(2017, 0, 1); 98 | const endOfDayDate = new Date(2017, 0, 1, 23, 59, 59, 999); 99 | 100 | const endOfDay = getEnd('day', date); 101 | 102 | expect(endOfDay).toEqual(endOfDayDate); 103 | }); 104 | 105 | it('throws an error when given unrecognized range type', () => { 106 | const date = new Date(2017, 0, 1); 107 | 108 | expect(() => getEnd('hamster', date)).toThrow(); 109 | }); 110 | }); 111 | -------------------------------------------------------------------------------- /src/components/Calender/v2/DatePicker/shared/propTypes.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | 3 | const allViews = ['century', 'decade', 'year', 'month']; 4 | const allValueTypes = [...allViews.slice(1), 'day']; 5 | 6 | export const isValueType = PropTypes.oneOf(allValueTypes); 7 | 8 | export const isMinDate = (props, propName, componentName) => { 9 | const { [propName]: minDate } = props; 10 | 11 | if (!minDate) { 12 | return null; 13 | } 14 | 15 | if (!(minDate instanceof Date)) { 16 | return new Error(`Invalid prop \`${propName}\` of type \`${typeof minDate}\` supplied to \`${componentName}\`, expected instance of \`Date\`.`); 17 | } 18 | 19 | const { maxDate } = props; 20 | 21 | if (maxDate && minDate > maxDate) { 22 | return new Error(`Invalid prop \`${propName}\` of type \`${typeof minDate}\` supplied to \`${componentName}\`, minDate cannot be larger than maxDate.`); 23 | } 24 | 25 | return null; 26 | }; 27 | 28 | export const isMaxDate = (props, propName, componentName) => { 29 | const { [propName]: maxDate } = props; 30 | 31 | if (!maxDate) { 32 | return null; 33 | } 34 | 35 | if (!(maxDate instanceof Date)) { 36 | return new Error(`Invalid prop \`${propName}\` of type \`${typeof maxDate}\` supplied to \`${componentName}\`, expected instance of \`Date\`.`); 37 | } 38 | 39 | const { minDate } = props; 40 | 41 | if (minDate && maxDate < minDate) { 42 | return new Error(`Invalid prop \`${propName}\` of type \`${typeof maxDate}\` supplied to \`${componentName}\`, maxDate cannot be smaller than minDate.`); 43 | } 44 | 45 | return null; 46 | }; 47 | -------------------------------------------------------------------------------- /src/components/Calender/v2/DatePicker/shared/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Calls a function, if it's defined, with specified arguments 3 | * @param {Function} fn 4 | * @param {Object} args 5 | */ 6 | export function callIfDefined(fn, ...args) { 7 | if (fn && typeof fn === 'function') { 8 | fn(...args); 9 | } 10 | } 11 | 12 | function isValidNumber(num) { 13 | return num !== null && num !== false && !Number.isNaN(Number(num)); 14 | } 15 | 16 | export function safeMin(...args) { 17 | return Math.min(...args.filter(isValidNumber)); 18 | } 19 | 20 | export function safeMax(...args) { 21 | return Math.max(...args.filter(isValidNumber)); 22 | } 23 | -------------------------------------------------------------------------------- /src/components/Calender/v2/DatePicker/shared/utils.spec.js: -------------------------------------------------------------------------------- 1 | import { 2 | callIfDefined, 3 | safeMin, 4 | safeMax, 5 | } from './utils'; 6 | 7 | describe('callIfDefined', () => { 8 | it('calls given function if defined', () => { 9 | const fn = jest.fn(); 10 | const arg1 = 'hi'; 11 | const arg2 = 'hello'; 12 | 13 | callIfDefined(fn, arg1, arg2); 14 | 15 | expect(fn).toHaveBeenCalledWith(arg1, arg2); 16 | }); 17 | }); 18 | 19 | describe('safeMin', () => { 20 | it('returns Infinity given no values', () => { 21 | const result = safeMin(); 22 | 23 | expect(result).toBe(Infinity); 24 | }); 25 | 26 | it('returns the smallest value given valid numbers', () => { 27 | const result = safeMin(3, 4, 5); 28 | 29 | expect(result).toBe(3); 30 | }); 31 | 32 | it('returns the smallest value given valid numbers with zero', () => { 33 | const result = safeMin(0, 1, 2); 34 | 35 | expect(result).toBe(0); 36 | }); 37 | 38 | it('returns the smallest value given valid number and null', () => { 39 | const result = safeMin(1, 2, null); 40 | 41 | expect(result).toBe(1); 42 | }); 43 | 44 | it('returns the smallest value given valid number and undefined', () => { 45 | const result = safeMin(1, 2, undefined); 46 | 47 | expect(result).toBe(1); 48 | }); 49 | 50 | it('returns the smallest value given valid numbers as strings', () => { 51 | const result = safeMin('1', '2'); 52 | 53 | expect(result).toBe(1); 54 | }); 55 | }); 56 | 57 | describe('safeMax', () => { 58 | it('returns -Infinity given no values', () => { 59 | const result = safeMax(); 60 | 61 | expect(result).toBe(-Infinity); 62 | }); 63 | 64 | it('returns the largest value given valid numbers', () => { 65 | const result = safeMax(3, 4, 5); 66 | 67 | expect(result).toBe(5); 68 | }); 69 | 70 | it('returns the largest value given valid numbers with zero', () => { 71 | const result = safeMax(-2, -1, 0); 72 | 73 | expect(result).toBe(0); 74 | }); 75 | 76 | it('returns the largest value given valid number and null', () => { 77 | const result = safeMax(3, 4, null); 78 | 79 | expect(result).toBe(4); 80 | }); 81 | 82 | it('returns the largest value given valid number and undefined', () => { 83 | const result = safeMax(3, 4, undefined); 84 | 85 | expect(result).toBe(4); 86 | }); 87 | 88 | it('returns the largest value given valid numbers as strings', () => { 89 | const result = safeMax('3', '4'); 90 | 91 | expect(result).toBe(4); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /src/components/Card/index.js: -------------------------------------------------------------------------------- 1 | import React, { useMemo, useContext } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classNames from 'classnames'; 4 | 5 | const Context = React.createContext(); 6 | const propTypes = { 7 | /** 8 | * When this prop is set, it creates a Card with a Card.Body inside 9 | * passing the children directly to it 10 | */ 11 | body: PropTypes.bool, 12 | /** 13 | * Card size variants 14 | * */ 15 | size: PropTypes.oneOf(['small', 'medium', 'large']), 16 | }; 17 | 18 | const defaultProps = { 19 | body: false, 20 | size: 'medium', 21 | }; 22 | 23 | const sizesClassName = { 24 | small: { 25 | content: 'u-paddingExtraSmall', 26 | title: 'u-text300', 27 | }, 28 | medium: { 29 | content: 'u-paddingSmall', 30 | title: 'u-text400', 31 | }, 32 | large: { 33 | content: 'u-paddingMedium', 34 | title: 'u-text600', 35 | }, 36 | }; 37 | 38 | const Card = React.forwardRef(({ className, body, size, children, as: Component = 'div', ...props }, ref) => { 39 | const context = useMemo(() => ({ size }), [size]); 40 | return ( 41 | 42 | 51 | {body ? {children} : children} 52 | 53 | 54 | ); 55 | }); 56 | 57 | const CardBody = React.forwardRef(({ className, as: Component = 'div', ...props }, ref) => { 58 | const { size } = useContext(Context); 59 | return ( 60 | 69 | ); 70 | }); 71 | const CardHeader = React.forwardRef(({ className, as: Component = 'div', ...props }, ref) => { 72 | const { size } = useContext(Context); 73 | return ( 74 | 83 | ); 84 | }); 85 | const CardTitle = React.forwardRef(({ className, as: Component = 'div', ...props }, ref) => { 86 | const { size } = useContext(Context); 87 | return ( 88 | 97 | ); 98 | }); 99 | 100 | 101 | Card.displayName = 'Card'; 102 | Card.propTypes = propTypes; 103 | Card.defaultProps = defaultProps; 104 | Card.Header = CardHeader; 105 | Card.Title = CardTitle; 106 | Card.Body = CardBody; 107 | export default Card; 108 | -------------------------------------------------------------------------------- /src/components/Carousel/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classNames from 'classnames'; 4 | import SlickBase from 'react-slick'; 5 | import createBlock from 'utils/createBlock'; 6 | 7 | const propTypes = { 8 | /** 9 | * The indicator inside content 10 | */ 11 | dotInside: PropTypes.bool, 12 | /** [Define settings](https://react-slick.neostack.com/docs/api) */ 13 | settings: PropTypes.object, 14 | }; 15 | 16 | const defaultProps = { 17 | dotInside: false, 18 | settings: { 19 | infinite: true, 20 | dots: true, 21 | }, 22 | }; 23 | 24 | const Carousel = React.forwardRef(({ className, dotInside, settings, ...props }, ref) => ( 25 | 35 | )); 36 | 37 | const Item = createBlock('Carousel-item u-lineHeightReset'); 38 | 39 | Carousel.displayName = 'Carousel'; 40 | Carousel.defaultProps = defaultProps; 41 | Carousel.propTypes = propTypes; 42 | Carousel.Item = Item; 43 | export default Carousel; 44 | -------------------------------------------------------------------------------- /src/components/ChatBox/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from 'classnames'; 3 | import createBlock from 'utils/createBlock'; 4 | 5 | const propTypes = { 6 | 7 | }; 8 | const defaultProps = { 9 | 10 | }; 11 | 12 | const ChatBox = React.forwardRef(({ className, ...props }, ref) => ( 13 |
    22 | )); 23 | 24 | const List = React.forwardRef(({ className, children, innerClassName, ...props }, ref) => ( 25 |
    33 |
    39 | {children} 40 |
    41 |
    42 | )); 43 | 44 | const Context = createBlock('ChatBox-context u-positionRelative'); 45 | const Notice = React.forwardRef(({ className, children, ...props }, ref) => ( 46 |
    54 |
    55 | {children} 56 |
    57 |
    58 | )); 59 | 60 | const Info = createBlock('ChatBox-info'); 61 | const Attachment = createBlock('ChatBox-attachment u-paddingExtraSmall u-borderTop'); 62 | ChatBox.List = List; 63 | ChatBox.Attachment = Attachment; 64 | ChatBox.Info = Info; 65 | ChatBox.Context = Context; 66 | ChatBox.Notice = Notice; 67 | ChatBox.displayName = 'ChatBox'; 68 | ChatBox.defaultProps = defaultProps; 69 | ChatBox.propTypes = propTypes; 70 | 71 | export default ChatBox; 72 | -------------------------------------------------------------------------------- /src/components/Counter/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classNames from 'classnames'; 4 | import Icon from 'components/Icon'; 5 | 6 | const propTypes = { 7 | /** The Counter visual variant */ 8 | variant: PropTypes.oneOf([ 9 | 'primary', 10 | 'secondary', 11 | 'accent', 12 | 'information', 13 | 'warning', 14 | 'positive', 15 | 'negative', 16 | 'white', 17 | ]), 18 | /** Custom label */ 19 | label: PropTypes.oneOfType([ 20 | PropTypes.string, 21 | PropTypes.func, 22 | ]), 23 | /** Custom number */ 24 | number: PropTypes.oneOfType([ 25 | PropTypes.string, 26 | PropTypes.func, 27 | ]), 28 | /** The icon to display. The name can get from Component `Icon` */ 29 | iconLeft: PropTypes.oneOfType([ 30 | PropTypes.string, 31 | PropTypes.func, 32 | ]), 33 | }; 34 | 35 | const defaultProps = { 36 | iconLeft: 'time', 37 | variant: 'secondary', 38 | }; 39 | 40 | const variantsClassName = { 41 | primary: { 42 | text: 'u-textPrimary', 43 | icon: 'u-textPrimary', 44 | }, 45 | secondary: { 46 | text: 'u-textDark', 47 | icon: 'u-textGray', 48 | }, 49 | accent: { 50 | text: 'u-textAccent', 51 | icon: 'u-textAccent', 52 | }, 53 | warning: { 54 | text: 'u-textWarning', 55 | icon: 'u-textWarning', 56 | }, 57 | information: { 58 | text: 'u-textInformation', 59 | icon: 'u-textInformation', 60 | }, 61 | positive: { 62 | text: 'u-textPositive', 63 | icon: 'u-textPositive', 64 | }, 65 | negative: { 66 | text: 'u-textNegative', 67 | icon: 'u-textNegative', 68 | }, 69 | white: { 70 | text: 'u-textWhite', 71 | icon: 'u-textWhite', 72 | }, 73 | }; 74 | 75 | const Counter = React.forwardRef(({ className, children, iconLeft, label, number, variant, as: Component = 'div', ...props }, ref) => ( 76 | 85 | {iconLeft && ( 86 | 87 | {typeof (iconLeft) === 'function' 88 | ? iconLeft() 89 | : ( 90 | 97 | ) 98 | } 99 | 100 | )} 101 | 102 | {label && ( 103 | typeof (label) === 'function' 104 | ? label() 105 | : ( 106 | 111 | {label} 112 | 113 | ) 114 | 115 | )} 116 | {number && ( 117 | 118 | {typeof (number) === 'function' 119 | ? number() 120 | : ( 121 | 126 | {number} 127 | 128 | ) 129 | } 130 | 131 | )} 132 | {children} 133 | 134 | )); 135 | 136 | 137 | Counter.displayName = 'Counter'; 138 | Counter.defaultProps = defaultProps; 139 | Counter.propTypes = propTypes; 140 | export default Counter; 141 | -------------------------------------------------------------------------------- /src/components/Dropdown/Button.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import classNames from 'classnames'; 3 | import useMergedRefs from '@restart/hooks/useMergedRefs'; 4 | import PropTypes from 'prop-types'; 5 | import Button from 'components/Button'; 6 | import Icon from 'components/Icon'; 7 | import DropdownContext from './Context'; 8 | import { useToggle } from './Toggle'; 9 | 10 | const propTypes = { 11 | /** You can use a custom element type for this component. */ 12 | as: PropTypes.elementType, 13 | /** 14 | * Define size of caret icon 15 | * @default 'extraSmall' 16 | * */ 17 | caret: PropTypes.string, 18 | }; 19 | const defaultProps = { 20 | as: Button, 21 | }; 22 | const DropButton = React.forwardRef(({ className, children, caret, as: Component, ...props }, ref) => { 23 | const [toggleProps, { toggle }] = useToggle(); 24 | const { drop } = useContext(DropdownContext); 25 | toggleProps.ref = useMergedRefs(toggleProps.ref, ref); 26 | 27 | return ( 28 | 38 | {children} 39 | {caret && ( 40 | 44 | 51 | 52 | )} 53 | 54 | ); 55 | }); 56 | 57 | DropButton.displayName = 'DropdownButton'; 58 | DropButton.propTypes = propTypes; 59 | DropButton.defaultProps = defaultProps; 60 | export default DropButton; 61 | -------------------------------------------------------------------------------- /src/components/Dropdown/Container.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import classNames from 'classnames'; 3 | import PropTypes from 'prop-types'; 4 | import { elementType } from 'prop-types-extra'; 5 | import usePopper from 'hooks/usePopper'; 6 | import useRootClose from 'hooks/useRootClose'; 7 | import DropdownContext from './Context'; 8 | 9 | 10 | const propTypes = { 11 | /** 12 | * You can use a custom element type for this component. 13 | * @default div 14 | * */ 15 | as: PropTypes.elementType, 16 | /** 17 | * A set of popper options and props passed directly to react-popper's Popper component. 18 | */ 19 | popperConfig: PropTypes.object, 20 | /** A `react-transition-group` Transition component used to animate the Message on dismissal. */ 21 | transition: elementType, 22 | /** Custom style */ 23 | additionalStyles: PropTypes.object, 24 | }; 25 | 26 | const defaultProps = { 27 | }; 28 | 29 | const Container = React.forwardRef(({ additionalStyles, ...props }, ref) => { 30 | const { 31 | className, 32 | flip, 33 | rootCloseEvent, 34 | children, 35 | popperConfig = {}, 36 | as: Component = 'div', 37 | shouldUsePopper = true, 38 | transition: Transition, 39 | } = props; 40 | const context = useContext(DropdownContext); 41 | 42 | const show = context.show == null ? props.show : context.show; 43 | const alignRight = context.alignRight == null ? props.alignRight : context.alignRight; 44 | const handleClose = (e) => { 45 | if (!context.toggle) return; 46 | context.toggle(false, e); 47 | }; 48 | const { drop, setContainer, containerElement, toggleElement } = context; 49 | 50 | let placement = alignRight ? 'bottom-end' : 'bottom-start'; 51 | if (drop === 'up') placement = alignRight ? 'top-end' : 'top-start'; 52 | else if (drop === 'right') placement = alignRight ? 'right-end' : 'right-start'; 53 | else if (drop === 'left') placement = alignRight ? 'left-end' : 'left-start'; 54 | const popper = usePopper(toggleElement, containerElement, { 55 | placement, 56 | enabled: !!(shouldUsePopper && show), 57 | eventsEnabled: !!show, 58 | modifiers: { 59 | flip: { enabled: !!flip }, 60 | ...popperConfig.modifiers, 61 | }, 62 | }); 63 | const containerProps = { 64 | ref: setContainer, 65 | style: { ...popper.styles, ...additionalStyles }, 66 | 'aria-labelledby': toggleElement && toggleElement.id, 67 | ...props, 68 | }; 69 | useRootClose(context.containerElement, handleClose, { 70 | clickTrigger: rootCloseEvent, 71 | disabled: !(show), 72 | }); 73 | const container = ( 74 | 83 | {children} 84 | 85 | ); 86 | if (!Transition) return show ? container : null; 87 | return ( 88 | 89 | {container} 90 | 91 | ); 92 | }); 93 | Container.displayName = 'DropdownContainer'; 94 | Container.defaultProps = defaultProps; 95 | Container.propTypes = propTypes; 96 | export default Container; 97 | -------------------------------------------------------------------------------- /src/components/Dropdown/Context.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const DropdownContext = React.createContext({ 4 | containerRef() {}, 5 | toggleRef() {}, 6 | onToggle() {}, 7 | show: null, 8 | drop: null, 9 | }); 10 | 11 | export default DropdownContext; 12 | -------------------------------------------------------------------------------- /src/components/Dropdown/Toggle.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, cloneElement } from 'react'; 2 | import classNames from 'classnames'; 3 | import PropTypes from 'prop-types'; 4 | 5 | import DropdownContext from './Context'; 6 | 7 | const propTypes = { 8 | /** Set a custom element for this component */ 9 | as: PropTypes.elementType, 10 | 11 | /** Children prop should only contain a single child, and is enforced as such */ 12 | children: PropTypes.element.isRequired, 13 | }; 14 | export function useToggle() { 15 | const { show, toggle, setToggle } = useContext(DropdownContext); 16 | return [ 17 | { 18 | ref: setToggle, 19 | 'aria-haspopup': true, 20 | 'aria-expanded': !!show, 21 | }, 22 | { show, toggle }, 23 | ]; 24 | } 25 | 26 | const Toggle = React.forwardRef(({ className, children, disabled, ...props }, ref) => { 27 | const [toggleProps, { toggle }] = useToggle(); 28 | 29 | return cloneElement(children, { 30 | className: classNames( 31 | 'Dropdown-toggle', 32 | disabled ? ' u-pointerEventsNone u-cursorNotAllow' : 'u-cursorPointer', 33 | className && className 34 | ), 35 | onClick: !disabled ? toggle : null, 36 | ref, 37 | ...props, 38 | ...toggleProps, 39 | children, 40 | }); 41 | }); 42 | 43 | Toggle.displayName = 'DropdownToggle'; 44 | Toggle.defaultProps = {}; 45 | Toggle.propTypes = propTypes; 46 | export default Toggle; 47 | -------------------------------------------------------------------------------- /src/components/Dropdown/index.js: -------------------------------------------------------------------------------- 1 | import React, { useMemo, useRef, useCallback } from 'react'; 2 | import classNames from 'classnames'; 3 | import { useUncontrolled } from 'uncontrollable'; 4 | import useCallbackRef from '@restart/hooks/useCallbackRef'; 5 | import useForceUpdate from '@restart/hooks/useForceUpdate'; 6 | import PropTypes from 'prop-types'; 7 | import createBlock from 'utils/createBlock'; 8 | import DropButton from './Button'; 9 | import Toggle from './Toggle'; 10 | import Container from './Container'; 11 | import DropdownContext from './Context'; 12 | 13 | 14 | const propTypes = { 15 | /** 16 | * You can use a custom element type for this component. 17 | * @default div 18 | * */ 19 | as: PropTypes.elementType, 20 | /** 21 | * Determines the direction and location of the Menu in relation to it's Toggle. 22 | * @default 'down' 23 | * */ 24 | drop: PropTypes.oneOf([ 25 | 'up', 26 | 'down', 27 | 'left', 28 | 'right', 29 | ]), 30 | /** 31 | * Allow Dropdown to flip in case of an overlapping on the reference element. For more information refer to 32 | * Popper.js's flip [docs](https://popper.js.org/popper-documentation.html#modifiers..flip.enabled). 33 | * @default true 34 | */ 35 | flip: PropTypes.bool, 36 | /** 37 | * Whether or not the Dropdown is visible. 38 | * @controllable onToggle 39 | * */ 40 | show: PropTypes.bool, 41 | /** 42 | * Align the menu to the right side of the Dropdown toggle 43 | * @default false 44 | * */ 45 | alignRight: PropTypes.bool, 46 | /** 47 | * A callback fired when the Dropdown wishes to change visibility. Called with the requested 48 | * `show` value, the DOM event, and the source that fired it: `'click'`,`'keydown'`,`'rootClose'`, or `'select'`. 49 | * 50 | * ```js 51 | * function( 52 | * isOpen: boolean, 53 | * event: SyntheticEvent, 54 | * metadata: { 55 | * source: 'select' | 'click' | 'rootClose' | 'keydown' 56 | * } 57 | * ): void 58 | * ``` 59 | * 60 | * @controllable show 61 | */ 62 | onToggle: PropTypes.func, 63 | }; 64 | const defaultProps = { 65 | 66 | }; 67 | const Dropdown = React.forwardRef((uncontrolledProps, ref) => { 68 | const { 69 | drop, 70 | show, 71 | className, 72 | alignRight, 73 | onToggle, 74 | as: Component = 'div', 75 | ...props 76 | } = useUncontrolled(uncontrolledProps, { show: 'onToggle' }); 77 | const forceUpdate = useForceUpdate(); 78 | const [toggleElement, setToggle] = useCallbackRef(); 79 | const containerRef = useRef(); 80 | const containerElement = containerRef.current; 81 | const setContainer = useCallback( 82 | (ref) => { 83 | containerRef.current = ref; 84 | // ensure that a menu set triggers an update for consumers 85 | forceUpdate(); 86 | }, 87 | [forceUpdate], 88 | ); 89 | 90 | const toggle = useCallback( 91 | (event) => { 92 | onToggle(!show, event); 93 | }, 94 | [onToggle, show], 95 | ); 96 | const context = useMemo( 97 | () => ({ 98 | toggle, 99 | drop, 100 | show, 101 | alignRight, 102 | containerElement, 103 | toggleElement, 104 | setContainer, 105 | setToggle, 106 | }), 107 | [ 108 | toggle, 109 | drop, 110 | show, 111 | alignRight, 112 | containerElement, 113 | toggleElement, 114 | setContainer, 115 | setToggle, 116 | ], 117 | ); 118 | return ( 119 | 120 | 129 | 130 | ); 131 | }); 132 | const Item = createBlock('Dropdown-item u-flex u-paddingHorizontalSmall u-paddingVerticalTiny hover:u-backgroundLightest'); 133 | Dropdown.Item = Item; 134 | Dropdown.Container = Container; 135 | Dropdown.Button = DropButton; 136 | Dropdown.Toggle = Toggle; 137 | Dropdown.propTypes = propTypes; 138 | Dropdown.defaultProps = defaultProps; 139 | Dropdown.displayName = 'Dropdown'; 140 | export default Dropdown; 141 | -------------------------------------------------------------------------------- /src/components/EmptyState/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classNames from 'classnames'; 4 | import createBlock from '../../utils/createBlock'; 5 | import { PluginType } from '../../constants/common'; 6 | import Plugins from '../../plugins'; 7 | 8 | const propTypes = { 9 | /** The EmptyState visual name, should be provide via an AssetPlugin with prefix "emptyState" */ 10 | name: PropTypes.string, 11 | /** Providing a `src` will render an `` element */ 12 | src: PropTypes.string, 13 | /** Providing a alt if `src` exits */ 14 | alt: PropTypes.string, 15 | /** Set the width of the logo */ 16 | width: PropTypes.number, 17 | /** Set the height of the logo */ 18 | height: PropTypes.number, 19 | }; 20 | const defaultProps = { 21 | width: 240, 22 | alt: 'EmptyState', 23 | }; 24 | const EmptyState = React.forwardRef(({ className, children, name, src, fileType, alt, height, width, as: Component = 'div', ...props }, ref) => { 25 | let nameOri = name; 26 | let srcOri = src; 27 | if (srcOri) { 28 | nameOri = false; 29 | } else if (nameOri) { 30 | srcOri = Plugins 31 | .getPlugins(PluginType.ASSET) 32 | .traverseCall('getAsset', 'emptyState', nameOri) 33 | .find(asset => !!asset); 34 | } 35 | return ( 36 | 45 | {srcOri && ( 46 | {alt} 47 | )} 48 | {children} 49 | 50 | ); 51 | }); 52 | 53 | const Heading = createBlock('EmptyState-heading u-marginTopSmall u-text600 u-fontMedium u-textLight'); 54 | const Description = createBlock('EmptyState-description u-marginBottomSmall u-textLight'); 55 | 56 | EmptyState.Heading = Heading; 57 | EmptyState.Description = Description; 58 | EmptyState.displayName = 'EmptyState'; 59 | EmptyState.defaultProps = defaultProps; 60 | EmptyState.propTypes = propTypes; 61 | export default EmptyState; 62 | -------------------------------------------------------------------------------- /src/components/Fade/index.js: -------------------------------------------------------------------------------- 1 | //fork react-bootstrap/src/Fade.js 2 | import classNames from 'classnames'; 3 | import React, { useCallback } from 'react'; 4 | import PropTypes from 'prop-types'; 5 | import Transition, { 6 | ENTERED, 7 | ENTERING, 8 | } from 'react-transition-group/Transition'; 9 | import triggerBrowserReflow from 'utils/triggerBrowserReflow'; 10 | 11 | const propTypes = { 12 | /** 13 | * Show the component; triggers the fade in or fade out animation 14 | */ 15 | in: PropTypes.bool, 16 | 17 | /** 18 | * Wait until the first "enter" transition to mount the component (add it to the DOM) 19 | */ 20 | mountOnEnter: PropTypes.bool, 21 | 22 | /** 23 | * Unmount the component (remove it from the DOM) when it is faded out 24 | */ 25 | unmountOnExit: PropTypes.bool, 26 | 27 | /** 28 | * Run the fade in animation when the component mounts, if it is initially 29 | * shown 30 | */ 31 | appear: PropTypes.bool, 32 | 33 | /** 34 | * Duration of the fade animation in milliseconds, to ensure that finishing 35 | * callbacks are fired even if the original browser transition end events are 36 | * canceled 37 | */ 38 | timeout: PropTypes.number, 39 | 40 | /** 41 | * Callback fired before the component fades in 42 | */ 43 | onEnter: PropTypes.func, 44 | /** 45 | * Callback fired after the component starts to fade in 46 | */ 47 | onEntering: PropTypes.func, 48 | /** 49 | * Callback fired after the has component faded in 50 | */ 51 | onEntered: PropTypes.func, 52 | /** 53 | * Callback fired before the component fades out 54 | */ 55 | onExit: PropTypes.func, 56 | /** 57 | * Callback fired after the component starts to fade out 58 | */ 59 | onExiting: PropTypes.func, 60 | /** 61 | * Callback fired after the component has faded out 62 | */ 63 | onExited: PropTypes.func, 64 | }; 65 | 66 | const defaultProps = { 67 | in: false, 68 | timeout: 300, 69 | mountOnEnter: false, 70 | unmountOnExit: false, 71 | appear: false, 72 | }; 73 | 74 | const fadeStyles = { 75 | [ENTERING]: 'Show js-entering', 76 | [ENTERED]: 'Show js-entered', 77 | }; 78 | 79 | const Fade = React.forwardRef(({ className, children, ...props }, ref) => { 80 | const handleEnter = useCallback( 81 | (node) => { 82 | triggerBrowserReflow(node); 83 | if (props.onEnter) props.onEnter(node); 84 | }, 85 | [props], 86 | ); 87 | 88 | return ( 89 | 95 | {(status, innerProps) => React.cloneElement(children, { 96 | ...innerProps, 97 | className: classNames( 98 | 'Fade', 99 | className, 100 | children.props.className, 101 | fadeStyles[status], 102 | ), 103 | }) 104 | } 105 | 106 | ); 107 | }); 108 | 109 | Fade.propTypes = propTypes; 110 | Fade.defaultProps = defaultProps; 111 | Fade.displayName = 'Fade'; 112 | 113 | export default Fade; 114 | -------------------------------------------------------------------------------- /src/components/Form/Check.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classNames from 'classnames'; 4 | import warning from 'warning'; 5 | import Context from './Context'; 6 | 7 | const propTypes = { 8 | /** 9 | * The type of checkable. 10 | * @type {('radio' | 'checkbox' | 'checkbox_button')} 11 | */ 12 | type: PropTypes.oneOf(['radio', 'checkbox', 'checkbox_button']).isRequired, 13 | /** A HTML id attribute, necessary for proper form accessibility. */ 14 | id: PropTypes.string.isRequired, 15 | /** Custom label */ 16 | label: PropTypes.oneOfType([ 17 | PropTypes.string, 18 | PropTypes.func, 19 | ]), 20 | /** 21 | * Render inline `` 22 | * @default false 23 | * */ 24 | inline: PropTypes.bool, 25 | /** 26 | * Add "valid" validation styles to the control 27 | * @default false 28 | * */ 29 | isValid: PropTypes.bool, 30 | /** 31 | * Add "invalid" validation styles to the control and accompanying label 32 | * @default false 33 | * */ 34 | isInvalid: PropTypes.bool, 35 | /** 36 | * The underlying HTML element to use when rendering the Form.Check. 37 | * @default input 38 | */ 39 | /** 40 | * Input size variants 41 | * @default 'medium' 42 | * */ 43 | sizeInput: PropTypes.oneOf([ 44 | 'small', 45 | 'medium', 46 | 'large', 47 | ]), 48 | as: PropTypes.elementType, 49 | }; 50 | 51 | const defaultProps = { 52 | // eslint-disable-next-line react/default-props-match-prop-types 53 | type: 'checkbox', 54 | }; 55 | 56 | const Check = React.forwardRef(({ className, sizeInput, type, id, label, inline, isValid, isInvalid, disabled, as: Component = 'div', ...props }, ref) => { 57 | const { controlId, disabledControl, sizeControl } = useContext(Context); 58 | 59 | warning( 60 | controlId == null || !id, 61 | '`controlId` is ignored on `` when `id` is specified.', 62 | ); 63 | const disabledOri = disabled || disabledControl; 64 | const sizeInputSet = sizeInput || sizeControl; 65 | const idSet = id || controlId; 66 | return ( 67 | 82 | 90 | 103 | 104 | ); 105 | }); 106 | 107 | Check.displayName = 'FormCheck'; 108 | Check.defaultProps = defaultProps; 109 | Check.propTypes = propTypes; 110 | export default Check; 111 | -------------------------------------------------------------------------------- /src/components/Form/Context.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Context = React.createContext( 4 | { 5 | controlId: undefined, 6 | sizeControl: undefined, 7 | requiredControl: undefined, 8 | disabledControl: undefined, 9 | }); 10 | 11 | export default Context; 12 | -------------------------------------------------------------------------------- /src/components/Form/Feedback.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classNames from 'classnames'; 4 | 5 | const propTypes = { 6 | /** Specify whether the feedback is for valid or invalid fields */ 7 | type: PropTypes.oneOf(['valid', 'invalid']), 8 | /** 9 | * Set Form.Feedback visible 10 | */ 11 | visible: PropTypes.bool, 12 | /** 13 | * You can use a custom element type for this component. 14 | * @default div 15 | * */ 16 | as: PropTypes.elementType, 17 | }; 18 | 19 | const defaultProps = { 20 | type: 'valid', 21 | visible: false, 22 | }; 23 | 24 | const Feedback = React.forwardRef(({ className, type, visible, as: Component = 'div', ...props }, ref) => ( 25 | 35 | )); 36 | Feedback.displayName = 'FormFeedback'; 37 | Feedback.propTypes = propTypes; 38 | Feedback.defaultProps = defaultProps; 39 | 40 | export default Feedback; 41 | -------------------------------------------------------------------------------- /src/components/Form/File.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classNames from 'classnames'; 4 | import warning from 'warning'; 5 | import Context from './Context'; 6 | 7 | const propTypes = { 8 | /** 9 | * The underlying HTML element to use when rendering the Form.File. 10 | * @default div 11 | * */ 12 | as: PropTypes.elementType, 13 | /** Uses controlId from `` if not explicitly specified. */ 14 | id: PropTypes.string, 15 | /** 16 | * Uses sizeControl from `` if not explicitly specified. 17 | * @default 'medium' 18 | * */ 19 | sizeInput: PropTypes.oneOf([ 20 | 'small', 21 | 'medium', 22 | 'large', 23 | ]), 24 | /** File name */ 25 | fileName: PropTypes.string, 26 | /** Custom browse button text */ 27 | browseText: PropTypes.string, 28 | /** 29 | * Add "invalid" validation styles to the control and accompanying label 30 | * @default false 31 | * */ 32 | isValid: PropTypes.bool, 33 | /** 34 | * Add "valid" validation styles to the control 35 | * @default false 36 | * */ 37 | isInvalid: PropTypes.bool, 38 | /** Remove border all state */ 39 | isBorderNone: PropTypes.bool, 40 | /** Reset background to transparent*/ 41 | isBackgroundReset: PropTypes.bool, 42 | /** The underlying HTML element to use when rendering the Form.Input. */ 43 | placeholder: PropTypes.string, 44 | }; 45 | 46 | const defaultProps = { 47 | browseText: 'Browse', 48 | placeholder: '', 49 | }; 50 | 51 | const File = React.forwardRef(({ className, sizeInput, id, fileName, placeholder, browseText, isValid, isInvalid, isBorderNone, isBackgroundReset, as: Component = 'div', ...props }, ref) => { 52 | const { sizeControl, controlId } = useContext(Context); 53 | const sizeInputSet = sizeInput || sizeControl; 54 | const idSet = id || controlId; 55 | 56 | warning( 57 | controlId == null || !id, 58 | '`controlId` is ignored on `` when `id` is specified.', 59 | ); 60 | return ( 61 | 76 | 83 | 96 | 97 | 98 | ); 99 | }); 100 | 101 | File.displayName = 'FormFile'; 102 | File.defaultProps = defaultProps; 103 | File.propTypes = propTypes; 104 | export default File; 105 | -------------------------------------------------------------------------------- /src/components/Form/Group.js: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classNames from 'classnames'; 4 | import Context from './Context'; 5 | 6 | const propTypes = { 7 | /** Sets id on `` and htmlFor on ``. */ 8 | controlId: PropTypes.string, 9 | /** 10 | * Sets sizeInput on `` and sizeLabel on ``. 11 | * @default 'medium' 12 | * @type {('small'|'medium'|'large')} 13 | * */ 14 | sizeControl: PropTypes.string, 15 | /** 16 | * Sets required on ``. 17 | * @default false 18 | * */ 19 | requiredControl: PropTypes.bool, 20 | /** 21 | * You can use a custom element type for this component. 22 | * @default div 23 | * */ 24 | as: PropTypes.elementType, 25 | }; 26 | 27 | const defaultProps = { 28 | }; 29 | 30 | const Group = React.forwardRef(({ className, controlId, sizeControl, disabledControl, requiredControl, as: Component = 'div', ...props }, ref) => { 31 | const context = useMemo(() => ({ controlId, sizeControl, requiredControl, disabledControl }), [controlId, sizeControl, requiredControl, disabledControl]); 32 | return ( 33 | 34 | 43 | 44 | ); 45 | } 46 | ); 47 | Group.displayName = 'FormGroup'; 48 | Group.propTypes = propTypes; 49 | Group.defaultProps = defaultProps; 50 | export default Group; 51 | -------------------------------------------------------------------------------- /src/components/Form/Input.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classNames from 'classnames'; 4 | import warning from 'warning'; 5 | import Context from './Context'; 6 | 7 | const propTypes = { 8 | /** The HTML input type, which is only relevant if as is 'input' (the default). */ 9 | type: PropTypes.string, 10 | /** 11 | * The `value` attribute of underlying input 12 | * 13 | * @controllable onChange 14 | * */ 15 | value: PropTypes.oneOfType([ 16 | PropTypes.number, 17 | PropTypes.string, 18 | ]), 19 | /** Uses controlId from `` if not explicitly specified. */ 20 | id: PropTypes.string, 21 | /** 22 | * Make the control disabled 23 | * @default false 24 | * */ 25 | disabled: PropTypes.bool, 26 | /** 27 | * Uses requiredControl from `` if not explicitly specified. 28 | * @default false 29 | * */ 30 | required: PropTypes.bool, 31 | /** 32 | * Make the control readonly 33 | * @default false 34 | * */ 35 | readOnly: PropTypes.bool, 36 | /** 37 | * Input size variants 38 | * @default 'medium' 39 | * */ 40 | sizeInput: PropTypes.oneOf([ 41 | 'small', 42 | 'medium', 43 | 'large', 44 | ]), 45 | /** 46 | * Add "valid" validation styles to the control 47 | * @default false 48 | * */ 49 | isValid: PropTypes.bool, 50 | /** 51 | * Add "invalid" validation styles to the control and accompanying label 52 | * @default false 53 | * */ 54 | isInvalid: PropTypes.bool, 55 | /** Remove border all state */ 56 | isBorderNone: PropTypes.bool, 57 | 58 | /** Reset background to transparent*/ 59 | isBackgroundReset: PropTypes.bool, 60 | /** 61 | * The underlying HTML element to use when rendering the FormControl. 62 | * 63 | * @type {('input'|'textarea')} 64 | * @default input 65 | */ 66 | as: PropTypes.elementType, 67 | }; 68 | 69 | const defaultProps = { 70 | }; 71 | const Input = React.forwardRef(({ className, sizeInput, required, id, type, disabled, isValid, isInvalid, isBorderNone, isBackgroundReset, as: Component = 'input', ...props }, ref) => { 72 | const { controlId, sizeControl, requiredControl, disabledControl } = useContext(Context); 73 | warning( 74 | controlId == null || !id, 75 | '`controlId` is ignored on `` when `id` is specified.', 76 | ); 77 | const sizeInputSet = sizeInput || sizeControl; 78 | const requiredSet = required || requiredControl; 79 | const disabledOri = disabled || disabledControl; 80 | return ( 81 | 100 | ); 101 | }); 102 | Input.displayName = 'FormInput'; 103 | Input.propTypes = propTypes; 104 | Input.defaultProps = defaultProps; 105 | export default Input; 106 | -------------------------------------------------------------------------------- /src/components/Form/InputGroup.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import classNames from 'classnames'; 3 | import PropTypes from 'prop-types'; 4 | import createBlock from 'utils/createBlock'; 5 | import Context from './Context'; 6 | 7 | const propTypes = { 8 | /** 9 | * You can use a custom element type for this component. 10 | * @default div 11 | * */ 12 | as: PropTypes.elementType, 13 | }; 14 | 15 | const defaultProps = { 16 | }; 17 | 18 | const InputGroup = React.forwardRef(({ className, as: Component = 'div', ...props }, ref) => { 19 | const { sizeControl } = useContext(Context); 20 | return ( 21 | 31 | ); 32 | }); 33 | 34 | const Append = createBlock('FormInputGroup-append u-flex'); 35 | const Prepend = createBlock('FormInputGroup-prepend u-flex '); 36 | const Text = createBlock('FormInputGroup-text u-flex u-alignItemsCenter u-textGray u-textCenter u-backgroundLightest u-textNoWrap'); 37 | 38 | InputGroup.Text = Text; 39 | InputGroup.Append = Append; 40 | InputGroup.Prepend = Prepend; 41 | InputGroup.displayName = 'FormInputGroup'; 42 | InputGroup.defaultProps = defaultProps; 43 | InputGroup.propTypes = propTypes; 44 | export default InputGroup; 45 | -------------------------------------------------------------------------------- /src/components/Form/Label.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classNames from 'classnames'; 4 | import warning from 'warning'; 5 | import Context from './Context'; 6 | 7 | const propTypes = { 8 | /** 9 | * Uses sizeControl from `` if not explicitly specified. 10 | * @default 'medium' 11 | * */ 12 | sizeLabel: PropTypes.oneOf([ 13 | 'small', 14 | 'medium', 15 | 'large', 16 | ]), 17 | /** 18 | * Uses requiredId from `` if not explicitly specified. 19 | * @default false 20 | * */ 21 | required: PropTypes.bool, 22 | /** Uses controlId from `` if not explicitly specified. */ 23 | htmlFor: PropTypes.string, 24 | }; 25 | 26 | const defaultProps = { 27 | }; 28 | 29 | const labelSizes = { 30 | small: 'u-text200', 31 | medium: '', 32 | large: '', 33 | }; 34 | const Label = React.forwardRef(({ className, sizeLabel, required, htmlFor, as: Component = 'label', ...props }, ref) => { 35 | const { controlId, sizeControl, requiredControl } = useContext(Context); 36 | 37 | warning( 38 | controlId == null || !htmlFor, 39 | '`controlId` is ignored on `` when `htmlFor` is specified.', 40 | ); 41 | const htmlForSet = htmlFor || controlId; 42 | const requiredSet = required || requiredControl; 43 | const sizeLabelSet = sizeLabel || sizeControl; 44 | return ( 45 | 58 | ); 59 | }); 60 | Label.displayName = 'FormLabel'; 61 | Label.propTypes = propTypes; 62 | Label.defaultProps = defaultProps; 63 | 64 | export default Label; 65 | -------------------------------------------------------------------------------- /src/components/Form/Select.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classNames from 'classnames'; 4 | import warning from 'warning'; 5 | import Icon from 'components/Icon'; 6 | import Context from './Context'; 7 | 8 | const propTypes = { 9 | /** 10 | * The `value` attribute of underlying input 11 | * 12 | * @controllable onChange 13 | * */ 14 | value: PropTypes.oneOfType([ 15 | PropTypes.number, 16 | PropTypes.string, 17 | ]), 18 | /** Uses controlId from `` if not explicitly specified. */ 19 | id: PropTypes.string, 20 | /** 21 | * Make the control disabled 22 | * @default false 23 | * */ 24 | disabled: PropTypes.bool, 25 | /** 26 | * Uses requiredControl from `` if not explicitly specified. 27 | * @default false 28 | * */ 29 | required: PropTypes.bool, 30 | /** 31 | * Input size variants 32 | * @default 'medium' 33 | * */ 34 | sizeInput: PropTypes.oneOf([ 35 | 'small', 36 | 'medium', 37 | 'large', 38 | ]), 39 | /** 40 | * Add "valid" validation styles to the control 41 | * @default false 42 | * */ 43 | isValid: PropTypes.bool, 44 | /** 45 | * Add "invalid" validation styles to the control and accompanying label 46 | * @default false 47 | * */ 48 | isInvalid: PropTypes.bool, 49 | /** Remove border all state */ 50 | isBorderNone: PropTypes.bool, 51 | /** Reset background to transparent*/ 52 | isBackgroundReset: PropTypes.bool, 53 | }; 54 | 55 | const defaultProps = { 56 | }; 57 | const Select = React.forwardRef(({ className, sizeInput, required, multiple, id, type, disabled, isValid, isInvalid, isBorderNone, isBackgroundReset, as: Component = 'div', ...props }, ref) => { 58 | const { controlId, sizeControl, requiredControl, disabledControl } = useContext(Context); 59 | warning( 60 | controlId == null || !id, 61 | '`controlId` is ignored on `` when `id` is specified.', 62 | ); 63 | const sizeInputSet = sizeInput || sizeControl; 64 | const requiredSet = required || requiredControl; 65 | const disabledOri = disabled || disabledControl; 66 | const [isFocus, setFocus] = useState(false); 67 | return ( 68 | 84 |