├── .babelrc.json
├── .editorconfig
├── .eslintrc
├── .github
├── chroma@2x.png
├── lifeomic-probot.yml
└── workflows
│ ├── code-scanning-2022-06-29.yml
│ ├── pr-branch-build.yml
│ ├── release.yml
│ └── storybook.yml
├── .gitignore
├── .storybook
├── logo.svg
├── main.js
├── manager-head.html
├── preview-head.html
└── preview.js
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── jest.config.js
├── lint-staged.config.js
├── package.json
├── prettier.config.js
├── public
├── android-chrome-192x192.png
├── android-chrome-512x512.png
├── apple-touch-icon.png
├── chroma@2x.png
├── storybook-16x16.png
├── storybook-32x32.png
└── storybook.ico
├── release.config.js
├── src
├── assets
│ ├── Insights.tsx
│ ├── Logo.tsx
│ ├── PageLayoutDefaultHero.svg
│ ├── Subjects.tsx
│ ├── example-avatar-1.jpg
│ └── example-avatar-2.jpg
├── colors
│ ├── black.ts
│ ├── blue.ts
│ ├── charcoal.ts
│ ├── colorOptions.tsx
│ ├── darkGraphite.ts
│ ├── graphite.ts
│ ├── green.ts
│ ├── grey.ts
│ ├── navy.ts
│ ├── orange.ts
│ ├── purple.ts
│ ├── red.ts
│ ├── text.ts
│ ├── types.ts
│ └── yellow.ts
├── components
│ ├── Alert
│ │ ├── Alert.stories.tsx
│ │ ├── Alert.test.tsx
│ │ ├── Alert.tsx
│ │ ├── AlertActionsRow.test.tsx
│ │ ├── AlertActionsRow.tsx
│ │ ├── AlertBody.test.tsx
│ │ ├── AlertBody.tsx
│ │ ├── AlertIcon.test.tsx
│ │ ├── AlertIcon.tsx
│ │ └── index.ts
│ ├── Avatar
│ │ ├── Avatar.stories.tsx
│ │ ├── Avatar.test.tsx
│ │ ├── Avatar.tsx
│ │ ├── AvatarBadge.test.tsx
│ │ ├── AvatarBadge.tsx
│ │ ├── AvatarSizeContext.tsx
│ │ └── index.ts
│ ├── Box
│ │ ├── Box.stories.tsx
│ │ ├── Box.test.tsx
│ │ ├── Box.tsx
│ │ └── index.ts
│ ├── Breadcrumbs
│ │ ├── BreadCrumbNav.test.tsx
│ │ ├── Breadcrumb.test.tsx
│ │ ├── Breadcrumb.tsx
│ │ ├── BreadcrumbNav.tsx
│ │ ├── Breadcrumbs.stories.tsx
│ │ ├── Breadcrumbs.test.tsx
│ │ ├── Breadcrumbs.tsx
│ │ └── index.ts
│ ├── Button
│ │ ├── Button.stories.tsx
│ │ ├── Button.test.tsx
│ │ ├── Button.tsx
│ │ └── index.ts
│ ├── ButtonFilePicker
│ │ ├── ButtonFilePicker.stories.tsx
│ │ ├── ButtonFilePicker.test.tsx
│ │ ├── ButtonFilePicker.tsx
│ │ └── index.ts
│ ├── ButtonFloat
│ │ ├── ButtonFloat.stories.tsx
│ │ ├── ButtonFloat.test.tsx
│ │ ├── ButtonFloat.tsx
│ │ └── index.ts
│ ├── ButtonLink
│ │ ├── ButtonLink.stories.tsx
│ │ ├── ButtonLink.test.tsx
│ │ ├── ButtonLink.tsx
│ │ └── index.ts
│ ├── ButtonUnstyled
│ │ ├── ButtonUnstyled.stories.tsx
│ │ ├── ButtonUnstyled.test.tsx
│ │ ├── ButtonUnstyled.tsx
│ │ └── index.ts
│ ├── Checkbox
│ │ ├── Checkbox.stories.tsx
│ │ ├── Checkbox.test.tsx
│ │ ├── Checkbox.tsx
│ │ └── index.ts
│ ├── Chip
│ │ ├── Chip.stories.tsx
│ │ ├── Chip.test.tsx
│ │ ├── Chip.tsx
│ │ └── index.ts
│ ├── ColorPicker
│ │ ├── ColorPicker.stories.tsx
│ │ ├── ColorPicker.test.tsx
│ │ ├── ColorPicker.tsx
│ │ └── index.ts
│ ├── DayPicker
│ │ ├── DayPicker.stories.tsx
│ │ ├── DayPicker.test.tsx
│ │ ├── DayPicker.tsx
│ │ └── index.ts
│ ├── DescriptionList
│ │ ├── DescriptionDetails.tsx
│ │ ├── DescriptionDivider.tsx
│ │ ├── DescriptionList.stories.tsx
│ │ ├── DescriptionList.test.tsx
│ │ ├── DescriptionList.tsx
│ │ ├── DescriptionListGroupHeading.tsx
│ │ ├── DescriptionTerm.tsx
│ │ └── index.ts
│ ├── Divider
│ │ ├── Divider.stories.tsx
│ │ ├── Divider.test.tsx
│ │ ├── Divider.tsx
│ │ └── index.ts
│ ├── DotLoader
│ │ ├── DotLoader.stories.tsx
│ │ ├── DotLoader.test.tsx
│ │ ├── DotLoader.tsx
│ │ └── index.ts
│ ├── ExpansionPanel
│ │ ├── ExpansionPanel.stories.tsx
│ │ ├── ExpansionPanel.test.tsx
│ │ ├── ExpansionPanel.tsx
│ │ └── index.ts
│ ├── FormBox
│ │ ├── FormBox.stories.tsx
│ │ ├── FormBox.test.tsx
│ │ ├── FormBox.tsx
│ │ └── index.ts
│ ├── Header
│ │ ├── Header.stories.tsx
│ │ ├── Header.test.tsx
│ │ ├── Header.tsx
│ │ └── index.ts
│ ├── IconButton
│ │ ├── IconButton.stories.tsx
│ │ ├── IconButton.test.tsx
│ │ ├── IconButton.tsx
│ │ └── index.ts
│ ├── IconButtonFloat
│ │ ├── IconButtonFloat.stories.tsx
│ │ ├── IconButtonFloat.test.tsx
│ │ ├── IconButtonFloat.tsx
│ │ └── index.ts
│ ├── IconButtonLink
│ │ ├── IconButtonLink.stories.tsx
│ │ ├── IconButtonLink.test.tsx
│ │ ├── IconButtonLink.tsx
│ │ └── index.ts
│ ├── IconTile
│ │ ├── IconTile.stories.tsx
│ │ ├── IconTile.test.tsx
│ │ ├── IconTile.tsx
│ │ ├── IconTileBadge.test.tsx
│ │ ├── IconTileBadge.tsx
│ │ ├── IconTileContent.test.tsx
│ │ ├── IconTileContent.tsx
│ │ ├── IconTileHero.test.tsx
│ │ ├── IconTileHero.tsx
│ │ └── index.ts
│ ├── InfiniteScroll
│ │ ├── InfiniteScroll.stories.tsx
│ │ ├── InfiniteScroll.test.tsx
│ │ ├── InfiniteScroll.tsx
│ │ └── index.ts
│ ├── KeymapHelp
│ │ ├── KeymapHelp.stories.tsx
│ │ ├── KeymapHelp.test.tsx
│ │ ├── KeymapHelp.tsx
│ │ └── index.ts
│ ├── LayoutManager
│ │ ├── LayoutManager.stories.tsx
│ │ ├── LayoutManager.test.tsx
│ │ ├── LayoutManager.tsx
│ │ ├── LayoutManagerContext.ts
│ │ ├── canAccessLocalStorage.ts
│ │ └── index.ts
│ ├── LinearProgress
│ │ ├── LinearProgress.stories.tsx
│ │ ├── LinearProgress.test.tsx
│ │ ├── LinearProgress.tsx
│ │ └── index.ts
│ ├── Link
│ │ ├── Link.stories.tsx
│ │ ├── Link.test.tsx
│ │ ├── Link.tsx
│ │ └── index.ts
│ ├── List
│ │ ├── List.stories.tsx
│ │ ├── List.test.tsx
│ │ ├── List.tsx
│ │ ├── ListGroupHeading.tsx
│ │ ├── ListItem.tsx
│ │ └── index.ts
│ ├── Menu
│ │ ├── Menu.stories.tsx
│ │ ├── Menu.test.tsx
│ │ ├── Menu.tsx
│ │ ├── MenuButton.test.tsx
│ │ ├── MenuButton.tsx
│ │ ├── MenuGroupHeading.tsx
│ │ ├── MenuItem.stories.tsx
│ │ ├── MenuItem.tsx
│ │ └── index.ts
│ ├── Modal
│ │ ├── Modal.stories.tsx
│ │ ├── Modal.test.tsx
│ │ ├── Modal.tsx
│ │ ├── ModalActions.tsx
│ │ ├── helpers.ts
│ │ └── index.ts
│ ├── NumberFormat
│ │ ├── PercentFormatField.stories.tsx
│ │ ├── PhoneNumberFormatField.stories.tsx
│ │ ├── PhoneNumberFormatField.test.tsx
│ │ ├── PhoneNumberFormatField.tsx
│ │ ├── PriceFormatField.stories.tsx
│ │ ├── UnitNumberFormatField.stories.tsx
│ │ ├── UnitNumberFormatField.test.tsx
│ │ ├── UnitNumberFormatField.tsx
│ │ └── index.ts
│ ├── PageHeader
│ │ ├── PageHeader.test.tsx
│ │ ├── PageHeader.tsx
│ │ └── index.ts
│ ├── PageLayout
│ │ ├── PageLayout.stories.tsx
│ │ ├── PageLayout.test.tsx
│ │ ├── PageLayout.tsx
│ │ ├── hero.svg
│ │ └── index.ts
│ ├── Paper
│ │ ├── Paper.stories.tsx
│ │ ├── Paper.test.tsx
│ │ ├── Paper.tsx
│ │ └── index.ts
│ ├── Pill
│ │ ├── Pill.stories.tsx
│ │ ├── Pill.test.tsx
│ │ ├── Pill.tsx
│ │ └── index.ts
│ ├── Popover
│ │ ├── Popover.stories.tsx
│ │ ├── Popover.test.tsx
│ │ ├── Popover.tsx
│ │ ├── PopoverActions.test.tsx
│ │ ├── PopoverActions.tsx
│ │ ├── PopoverContent.test.tsx
│ │ ├── PopoverContent.tsx
│ │ ├── PopoverItem.test.tsx
│ │ ├── PopoverItem.tsx
│ │ ├── PopoverList.test.tsx
│ │ ├── PopoverList.tsx
│ │ └── index.ts
│ ├── PrimaryNavigation
│ │ ├── PrimaryNavigation.stories.tsx
│ │ ├── PrimaryNavigation.test.tsx
│ │ ├── PrimaryNavigation.tsx
│ │ ├── PrimaryNavigationExpansionItem.test.tsx
│ │ ├── PrimaryNavigationExpansionItem.tsx
│ │ ├── PrimaryNavigationItem.test.tsx
│ │ ├── PrimaryNavigationItem.tsx
│ │ ├── PrimaryNavigationSubItem.test.tsx
│ │ ├── PrimaryNavigationSubItem.tsx
│ │ ├── _private
│ │ │ ├── NavOrExternalLink.tsx
│ │ │ └── common.ts
│ │ └── index.ts
│ ├── Radio
│ │ ├── Radio.stories.tsx
│ │ ├── Radio.test.tsx
│ │ ├── Radio.tsx
│ │ ├── RadioGroup.stories.tsx
│ │ ├── RadioGroup.test.tsx
│ │ ├── RadioGroup.tsx
│ │ ├── RadioGroupMinimal.stories.tsx
│ │ ├── RadioGroupMinimal.test.tsx
│ │ ├── RadioGroupMinimal.tsx
│ │ ├── index.ts
│ │ └── useRadioGroup.ts
│ ├── SearchField
│ │ ├── SearchField.stories.tsx
│ │ ├── SearchField.test.tsx
│ │ ├── SearchField.tsx
│ │ ├── clipPaths.ts
│ │ └── index.ts
│ ├── SecondaryNavigation
│ │ ├── SecondaryNavigation.stories.tsx
│ │ ├── SecondaryNavigation.test.tsx
│ │ ├── SecondaryNavigation.tsx
│ │ ├── SecondaryNavigationItem.stories.tsx
│ │ ├── SecondaryNavigationItem.test.tsx
│ │ ├── SecondaryNavigationItem.tsx
│ │ └── index.ts
│ ├── Select
│ │ ├── ComboBox.stories.tsx
│ │ ├── ComboBox.test.tsx
│ │ ├── ComboBox.tsx
│ │ ├── GroupHeading.test.tsx
│ │ ├── GroupHeading.tsx
│ │ ├── RoverOption.test.tsx
│ │ ├── RoverOption.tsx
│ │ ├── Select.stories.tsx
│ │ ├── Select.test.tsx
│ │ ├── Select.tsx
│ │ ├── SelectOption.test.tsx
│ │ ├── SelectOption.tsx
│ │ ├── index.ts
│ │ └── useWindowSize.ts
│ ├── Skeleton
│ │ ├── Skeleton.stories.tsx
│ │ ├── Skeleton.test.tsx
│ │ ├── Skeleton.tsx
│ │ └── index.ts
│ ├── SlideOver
│ │ ├── Actions.test.tsx
│ │ ├── Actions.tsx
│ │ ├── Body.test.tsx
│ │ ├── Body.tsx
│ │ ├── Header.test.tsx
│ │ ├── Header.tsx
│ │ ├── SlideOver.stories.tsx
│ │ ├── SlideOver.test.tsx
│ │ ├── SlideOver.tsx
│ │ └── index.ts
│ ├── Slider
│ │ ├── Slider.stories.tsx
│ │ ├── Slider.test.tsx
│ │ ├── Slider.tsx
│ │ └── index.ts
│ ├── SmallTile
│ │ ├── SmallTile.stories.tsx
│ │ ├── SmallTile.test.tsx
│ │ ├── SmallTile.tsx
│ │ ├── SmallTileContent.test.tsx
│ │ ├── SmallTileContent.tsx
│ │ ├── SmallTileFooter.test.tsx
│ │ ├── SmallTileFooter.tsx
│ │ └── index.ts
│ ├── Snackbar
│ │ ├── Snackbar.stories.tsx
│ │ ├── Snackbar.test.tsx
│ │ ├── Snackbar.tsx
│ │ └── index.ts
│ ├── SpinButton
│ │ ├── SpinButton.stories.tsx
│ │ ├── SpinButton.test.tsx
│ │ ├── SpinButton.tsx
│ │ └── index.tsx
│ ├── Stepper
│ │ ├── Step.test.tsx
│ │ ├── Step.tsx
│ │ ├── StepConnector.test.tsx
│ │ ├── StepConnector.tsx
│ │ ├── Stepper.stories.tsx
│ │ ├── Stepper.test.tsx
│ │ ├── Stepper.tsx
│ │ └── index.ts
│ ├── TableModule
│ │ ├── TableActionDivider.test.tsx
│ │ ├── TableActionDivider.tsx
│ │ ├── TableHeaderCell.test.tsx
│ │ ├── TableHeaderCell.tsx
│ │ ├── TableModule.stories.tsx
│ │ ├── TableModule.test.tsx
│ │ ├── TableModule.tsx
│ │ ├── TableModuleActions.test.tsx
│ │ ├── TableModuleActions.tsx
│ │ ├── TableModuleCell.test.tsx
│ │ ├── TableModuleCell.tsx
│ │ ├── TableModuleRow.test.tsx
│ │ ├── TableModuleRow.tsx
│ │ ├── config.ts
│ │ ├── configSort.ts
│ │ ├── index.ts
│ │ ├── storyData.ts
│ │ └── types.ts
│ ├── Tabs
│ │ ├── Tab.tsx
│ │ ├── TabList.tsx
│ │ ├── TabPanel.tsx
│ │ ├── Tabs.stories.tsx
│ │ ├── Tabs.test.tsx
│ │ ├── Tabs.tsx
│ │ ├── TabsContext.tsx
│ │ ├── index.ts
│ │ └── types.ts
│ ├── Text
│ │ ├── Text.stories.tsx
│ │ ├── Text.test.tsx
│ │ ├── Text.tsx
│ │ └── index.ts
│ ├── TextArea
│ │ ├── TextArea.stories.tsx
│ │ ├── TextArea.test.tsx
│ │ ├── TextArea.tsx
│ │ └── index.ts
│ ├── TextField
│ │ ├── TextField.stories.tsx
│ │ ├── TextField.test.tsx
│ │ ├── TextField.tsx
│ │ └── index.ts
│ ├── Toggle
│ │ ├── Toggle.stories.tsx
│ │ ├── Toggle.test.tsx
│ │ ├── Toggle.tsx
│ │ └── index.ts
│ ├── Tooltip
│ │ ├── Tooltip.stories.tsx
│ │ ├── Tooltip.test.tsx
│ │ ├── Tooltip.tsx
│ │ └── index.ts
│ ├── _private
│ │ ├── ConditionalWrapper.tsx
│ │ ├── LinkOrExternalLink.tsx
│ │ ├── UniqueId.ts
│ │ ├── forms
│ │ │ ├── FormElementUtils.ts
│ │ │ ├── FormErrorMessage.tsx
│ │ │ ├── FormHelpMessage.tsx
│ │ │ └── index.ts
│ │ └── notificationTypes.ts
│ └── index.ts
├── hooks
│ ├── events
│ │ ├── useInfiniteScroll.tsx
│ │ ├── useWindowSize.test.tsx
│ │ └── useWindowSize.tsx
│ └── utils
│ │ ├── useInterval.test.tsx
│ │ └── useInterval.tsx
├── jest.js
├── styles
│ ├── __snapshots__
│ │ └── createTheme.test.ts.snap
│ ├── createBoxShadows.ts
│ ├── createPalette.ts
│ ├── createStyles.ts
│ ├── createTheme.test.ts
│ ├── createTheme.ts
│ ├── createTypography.ts
│ ├── createZIndex.ts
│ ├── index.ts
│ ├── makeStyles.ts
│ ├── overrides
│ │ ├── ChromaComponents.ts
│ │ ├── ChromaOverrides.ts
│ │ ├── ChromaProps.ts
│ │ ├── MuiButton.ts
│ │ ├── MuiTab.ts
│ │ ├── MuiTabs.ts
│ │ ├── MuiTooltip.ts
│ │ └── index.ts
│ ├── screenreaderOnly.ts
│ ├── useMediaQuery.tsx
│ ├── utils
│ │ ├── colorManipulator.test.ts
│ │ └── colorManipulator.ts
│ ├── withStyles.ts
│ └── withTheme.ts
├── testUtils
│ ├── IconComponent.tsx
│ ├── getTestProps.ts
│ └── renderWithTheme.tsx
├── typeUtils.ts
├── typings
│ └── assets.d.ts
└── utils
│ ├── event-handlers.ts
│ ├── index.ts
│ └── warning.ts
├── stories
├── _recipes
│ ├── formik.stories.mdx
│ ├── react-hook-form.stories.mdx
│ └── redux-form.stories.mdx
├── assets
│ ├── legoman.jpg
│ └── sampleBackground.svg
├── styles
│ ├── boxShadows
│ │ └── boxShadows.stories.tsx
│ ├── color
│ │ ├── ColorValue.tsx
│ │ ├── Palette.tsx
│ │ ├── PaletteGrid.tsx
│ │ └── color.stories.tsx
│ └── theme
│ │ └── theme.stories.tsx
└── typings
│ └── assets.d.ts
├── test
├── documentRangeMock.js
├── emptyMock.js
├── imageMock.js
└── mockMuiStyles.js
├── tsconfig.build.json
├── tsconfig.docs.json
├── tsconfig.json
└── yarn.lock
/.babelrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "sourceType": "unambiguous",
3 | "presets": [
4 | [
5 | "@babel/preset-env",
6 | {
7 | "targets": {
8 | "chrome": 100,
9 | "safari": 15,
10 | "firefox": 91
11 | }
12 | }
13 | ],
14 | "@babel/preset-typescript",
15 | "@babel/preset-react"
16 | ],
17 | "plugins": []
18 | }
19 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | insert_final_newline = true
7 | trim_trailing_whitespace = true
8 |
9 | [*.{js,json,yml}]
10 | indent_size = 2
11 | indent_style = space
12 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "env": {
4 | "browser": true,
5 | "node": true,
6 | "es2020": true
7 | },
8 | "settings": {
9 | "react": {
10 | "version": "detect"
11 | }
12 | },
13 | "parserOptions": {
14 | "sourceType": "module",
15 | "ecmaFeatures": {
16 | "jsx": true,
17 | "modules": true
18 | },
19 | "ecmaVersion": 2020
20 | },
21 | "extends": [
22 | "plugin:react/recommended",
23 | "prettier",
24 | "plugin:prettier/recommended"
25 | ],
26 | "ignorePatterns": ["node_modules/", "dist/"],
27 | "parser": "@typescript-eslint/parser",
28 | "plugins": ["react", "react-hooks", "prettier", "jsx-a11y"],
29 | "rules": {
30 | "@typescript-eslint/no-empty-interface": "off",
31 | "react-hooks/exhaustive-deps": "warn",
32 | "react-hooks/rules-of-hooks": "error",
33 | "react/display-name": "off",
34 | "react/jsx-no-target-blank": "error",
35 | "react/jsx-uses-react": "error",
36 | "react/jsx-uses-vars": "error",
37 | "react/prop-types": "off",
38 | "jsx-a11y/no-static-element-interactions": "error",
39 | "jsx-a11y/aria-role": "error",
40 | "jsx-a11y/no-autofocus": "error"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/.github/chroma@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lifeomic/chroma-react/7f253614eb17adef57db4c4f197017c91f40efb5/.github/chroma@2x.png
--------------------------------------------------------------------------------
/.github/lifeomic-probot.yml:
--------------------------------------------------------------------------------
1 | enforceSemanticCommits: true
2 | requestIndividualReviewersFromTeam: '@lifeomic/chroma'
3 | reportWorkflowFailures:
4 | Release:
5 | # This is #open-source
6 | slackChannel: C04928K3H5Z
7 |
--------------------------------------------------------------------------------
/.github/workflows/pr-branch-build.yml:
--------------------------------------------------------------------------------
1 | name: PR Branch Build and Test
2 |
3 | on:
4 | push:
5 | branches-ignore:
6 | - master
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 | strategy:
12 | matrix:
13 | include:
14 | - group: 1
15 | node-version: 16.x
16 | run: |
17 | yarn test --ci --reporters="github-actions" --color
18 | yarn build
19 | - group: 2
20 | node-version: 18.x
21 | run: |
22 | yarn test --ci --reporters="github-actions" --color
23 | yarn build
24 | - group: 3
25 | node-version: 20.x
26 | run: |
27 | yarn test --ci --reporters="github-actions" --color
28 | yarn build
29 | - group: 4
30 | node-version: 18.x
31 | run: |
32 | yarn run docs:build
33 | steps:
34 | - uses: actions/checkout@v3
35 | - uses: actions/setup-node@v3
36 | with:
37 | node-version: ${{ matrix.node-version }}
38 | cache-dependency-path: 'yarn.lock'
39 | cache: 'yarn'
40 | - name: Lint
41 | run: |
42 | yarn
43 | yarn lint
44 | - name: Test
45 | run: ${{ matrix.run }}
46 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 | environment: npm
12 | steps:
13 | - uses: actions/checkout@v3
14 | - uses: actions/setup-node@v3
15 | with:
16 | node-version: 16
17 | cache: 'yarn'
18 | - name: Lint, test, and build
19 | run: |
20 | yarn
21 | yarn lint
22 | yarn test --silent
23 | yarn build
24 | - name: Publish
25 | env:
26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
27 | NPM_TOKEN: ${{secrets.LIFEOMIC_NPM_TOKEN}}
28 | run: |
29 | yarn build
30 | npx semantic-release
31 |
--------------------------------------------------------------------------------
/.github/workflows/storybook.yml:
--------------------------------------------------------------------------------
1 | name: Deploy Storybook to GitHub Pages
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v2
13 | - name: Exit if not on master branch
14 | if: endsWith(github.ref, 'master') == false
15 | run: exit 0
16 | - uses: actions/setup-node@v1
17 | with:
18 | node-version: 16
19 | registry-url: https://registry.npmjs.org
20 | - name: Install deps and deploy to GitHub Pages
21 | run: |
22 | yarn
23 | yarn docs:deploy -- --ci
24 | env:
25 | GH_TOKEN: ${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .idea/
3 | .iml/
4 | .npmrc
5 | .nyc_output/
6 | .vscode/
7 | .yarnclean
8 | .cache/
9 | *.log
10 | deploy/dist/
11 | deploy/node_modules/
12 | deploy/terraform/build/
13 | dist/
14 | node_modules/
15 | work/
16 | coverage/
17 | cjs/
18 | esm/
19 | docs/
20 | deploy/docs
21 | build/
22 |
23 | storybook-static/
24 |
--------------------------------------------------------------------------------
/.storybook/main.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | stories: [
3 | '../stories/**/*.stories.@(mdx|ts|tsx)',
4 | '../src/**/*.stories.@(ts|tsx)',
5 | ],
6 | addons: [
7 | '@storybook/addon-links',
8 | '@storybook/addon-essentials',
9 | '@storybook/addon-a11y',
10 | '@storybook/addon-interactions',
11 | '@storybook/addon-docs',
12 | ],
13 | framework: {
14 | name: '@storybook/react-webpack5',
15 | options: {},
16 | },
17 | webpackFinal: async (config) => {
18 | config.module.rules.push({
19 | resolve: { fullySpecified: false },
20 | });
21 | return config;
22 | },
23 | docs: {
24 | autodocs: true,
25 | },
26 | };
27 |
--------------------------------------------------------------------------------
/.storybook/manager-head.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
11 |
17 |
23 |
29 |
--------------------------------------------------------------------------------
/.storybook/preview-head.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/.storybook/preview.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { create } from '@storybook/theming';
3 | import logo from './logo.svg';
4 | import { CssBaseline } from '@mui/material';
5 | import {
6 | createTheme,
7 | StyledEngineProvider,
8 | ThemeProvider,
9 | } from '../src/styles';
10 |
11 | export const parameters = {
12 | actions: { argTypesRegex: '^on[A-Z].*' },
13 | controls: {
14 | matchers: {
15 | color: /(background|color)$/i,
16 | date: /Date$/,
17 | },
18 | },
19 | options: {
20 | theme: create({
21 | base: 'light',
22 | brandTitle: 'Chroma Design System',
23 | brandImage: logo,
24 | }),
25 | isFullscreen: false,
26 | storySort: {
27 | method: 'alphabetical',
28 | },
29 | },
30 | backgrounds: {
31 | default: 'white',
32 | values: [
33 | {
34 | name: 'white',
35 | value: '#FFFFFF',
36 | },
37 | {
38 | name: 'dark',
39 | value: '#484049',
40 | },
41 | {
42 | name: 'blue',
43 | value: '#006eb7',
44 | },
45 | ],
46 | },
47 | };
48 |
49 | export const decorators = [
50 | (story) => (
51 |
52 |
53 |
54 |
55 | {story()}
56 |
57 |
58 |
59 | ),
60 | ];
61 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms.
4 |
5 | ## How can I contribute?
6 |
7 | If you're interested in contributing to Chroma, we encourage you to submit a pull request!
8 |
9 | Make sure at least one commit in your pull request matches [the conventional commits spec](https://www.conventionalcommits.org/en/v1.0.0/), so that your change
10 | can be versioned correctly.
11 |
12 | ## Releases
13 |
14 | This project releases using [`semantic-release`](https://github.com/semantic-release/semantic-release). Merged PRs are released immediately based on commit messages matching [the conventional commits spec](https://www.conventionalcommits.org/en/v1.0.0/).
15 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 LifeOmic
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | testEnvironment: 'jsdom',
3 | transform: {
4 | '^.+\\.tsx?$': 'ts-jest',
5 | },
6 | testMatch: [
7 | '/src/**/*.test.{js,jsx,ts,tsx}',
8 | '/test/**/*.test.{js,jsx,ts,tsx}',
9 | ],
10 | moduleFileExtensions: ['js', 'json', 'ts', 'tsx', 'jsx', 'node', 'mjs'],
11 | clearMocks: true,
12 | collectCoverage: true,
13 | collectCoverageFrom: [
14 | '/src/components/**/*.{ts,tsx,js,jsx}',
15 | '/src/hooks/**/*.{ts,tsx,js,jsx}',
16 | '/src/styles/**/*.{ts,tsx,js,jsx}',
17 | '!/src/styles/overrides/*.{ts,tsx,js,jsx}',
18 | '!/src/components/index.ts',
19 | '!/src/styles/index.ts',
20 | '!/src/components/**/index.ts',
21 | ],
22 | testPathIgnorePatterns: ['/node_modules/', '/dist/'],
23 | setupFilesAfterEnv: [
24 | '@testing-library/jest-dom',
25 | '/test/mockMuiStyles.js',
26 | '/test/documentRangeMock.js',
27 | '/src/jest.js',
28 | ],
29 | coverageThreshold: {
30 | global: {
31 | branches: 80,
32 | statements: 80,
33 | functions: 80,
34 | lines: 80,
35 | },
36 | },
37 | coveragePathIgnorePatterns: ['.stories.tsx'],
38 | moduleNameMapper: {
39 | '\\.(jpg|jpeg|png|gif|svg)$': '/test/imageMock.js',
40 | '\\.(css)$': '/test/emptyMock.js',
41 | },
42 | reporters: ['default', 'github-actions'],
43 | };
44 |
--------------------------------------------------------------------------------
/lint-staged.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | '*.{ts,tsx}': [
3 | 'prettier --write',
4 | 'eslint --ext .ts,.tsx.js,.jsx . --fix',
5 | 'git add',
6 | ],
7 | };
8 |
--------------------------------------------------------------------------------
/prettier.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | trailingComma: 'es5',
3 | endOfLine: 'lf',
4 | tabWidth: 2,
5 | semi: true,
6 | singleQuote: true,
7 | arrowParens: 'always',
8 | bracketSpacing: true,
9 | };
10 |
--------------------------------------------------------------------------------
/public/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lifeomic/chroma-react/7f253614eb17adef57db4c4f197017c91f40efb5/public/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lifeomic/chroma-react/7f253614eb17adef57db4c4f197017c91f40efb5/public/android-chrome-512x512.png
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lifeomic/chroma-react/7f253614eb17adef57db4c4f197017c91f40efb5/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/chroma@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lifeomic/chroma-react/7f253614eb17adef57db4c4f197017c91f40efb5/public/chroma@2x.png
--------------------------------------------------------------------------------
/public/storybook-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lifeomic/chroma-react/7f253614eb17adef57db4c4f197017c91f40efb5/public/storybook-16x16.png
--------------------------------------------------------------------------------
/public/storybook-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lifeomic/chroma-react/7f253614eb17adef57db4c4f197017c91f40efb5/public/storybook-32x32.png
--------------------------------------------------------------------------------
/public/storybook.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lifeomic/chroma-react/7f253614eb17adef57db4c4f197017c91f40efb5/public/storybook.ico
--------------------------------------------------------------------------------
/release.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | branches: ['master'],
3 | plugins: [
4 | ['@semantic-release/commit-analyzer', { preset: 'conventionalcommits' }],
5 | ['@semantic-release/npm', { pkgRoot: 'dist/' }],
6 | [
7 | '@semantic-release/github',
8 | {
9 | // Setting this to false disables the default behavior
10 | // of opening a GitHub issue when a release fails.
11 | // We can enable this later if we want.
12 | failComment: false,
13 | },
14 | ],
15 | ],
16 | };
17 |
--------------------------------------------------------------------------------
/src/assets/Insights.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | export const Insights: React.FC> = (props) => (
4 |
45 | );
46 |
--------------------------------------------------------------------------------
/src/assets/Subjects.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | export const Subjects: React.FC> = (props) => (
4 |
45 | );
46 |
--------------------------------------------------------------------------------
/src/assets/example-avatar-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lifeomic/chroma-react/7f253614eb17adef57db4c4f197017c91f40efb5/src/assets/example-avatar-1.jpg
--------------------------------------------------------------------------------
/src/assets/example-avatar-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lifeomic/chroma-react/7f253614eb17adef57db4c4f197017c91f40efb5/src/assets/example-avatar-2.jpg
--------------------------------------------------------------------------------
/src/colors/black.ts:
--------------------------------------------------------------------------------
1 | import gray from './grey';
2 | import { Color, SimplePaletteColorOptions } from './types';
3 |
4 | const shades: Color = {
5 | 50: '#eeeeee',
6 | 100: '#d1d5d8',
7 | 200: '#b2b9c0',
8 | 300: '#929da9',
9 | 400: '#7b8996',
10 | 500: '#637585',
11 | 600: '#576674',
12 | 700: '#47535e',
13 | 800: '#384049',
14 | 900: '#262c32',
15 | };
16 |
17 | const black: Color & SimplePaletteColorOptions = {
18 | ...shades,
19 | main: shades[900],
20 | light: shades[700],
21 | contrastText: gray[200],
22 | };
23 |
24 | export default black;
25 |
--------------------------------------------------------------------------------
/src/colors/blue.ts:
--------------------------------------------------------------------------------
1 | import text from './text';
2 | import { Color, SimplePaletteColorOptions } from './types';
3 |
4 | const shades: Color = {
5 | 50: '#def4fc',
6 | 100: '#aae3f8',
7 | 200: '#6dd1f4',
8 | 300: '#0abff1',
9 | 400: '#00b2f1',
10 | 500: '#00a4ef',
11 | 600: '#0096e1',
12 | 700: '#0083ce',
13 | 800: '#006eb7',
14 | 900: '#00539a',
15 | };
16 |
17 | const blue: SimplePaletteColorOptions & Color = {
18 | ...shades,
19 | main: shades[800],
20 | light: shades[300],
21 | contrastText: text.contrast,
22 | };
23 |
24 | export default blue;
25 |
--------------------------------------------------------------------------------
/src/colors/charcoal.ts:
--------------------------------------------------------------------------------
1 | import text from './text';
2 | import { Color, SimplePaletteColorOptions } from './types';
3 |
4 | const shades: Color = {
5 | 50: '#ebebeb',
6 | 100: '#d6d7d8',
7 | 200: '#c2c3c5',
8 | 300: '#aeafb1',
9 | 400: '#9a9c9e',
10 | 500: '#85888a',
11 | 600: '#717477',
12 | 700: '#5d6063',
13 | 800: '#494d50',
14 | 900: '#35393d',
15 | };
16 |
17 | const charcoal: SimplePaletteColorOptions & Color = {
18 | ...shades,
19 | main: shades[900],
20 | light: shades[600],
21 | contrastText: text.contrast,
22 | };
23 |
24 | export default charcoal;
25 |
--------------------------------------------------------------------------------
/src/colors/darkGraphite.ts:
--------------------------------------------------------------------------------
1 | import text from './text';
2 | import { Color, SimplePaletteColorOptions } from './types';
3 |
4 | const shades: Color = {
5 | 50: '#eeeef0',
6 | 100: '#dcdde1',
7 | 200: '#cbccd2',
8 | 300: '#b9bbc3',
9 | 400: '#a8aab4',
10 | 500: '#9799a5',
11 | 600: '#868996',
12 | 700: '#747787',
13 | 800: '#636779',
14 | 900: '#52566a',
15 | };
16 |
17 | const darkGraphite: SimplePaletteColorOptions & Color = {
18 | ...shades,
19 | main: shades[900],
20 | light: shades[600],
21 | contrastText: text.contrast,
22 | };
23 |
24 | export default darkGraphite;
25 |
--------------------------------------------------------------------------------
/src/colors/graphite.ts:
--------------------------------------------------------------------------------
1 | import text from './text';
2 | import { Color, SimplePaletteColorOptions } from './types';
3 |
4 | const shades: Color = {
5 | 50: '#f2f3f6',
6 | 100: '#e6e7ed',
7 | 200: '#dadbe4',
8 | 300: '#cdcfdb',
9 | 400: '#c1c4d2',
10 | 500: '#b5b8c9',
11 | 600: '#a9acc0',
12 | 700: '#9ca0b7',
13 | 800: '#9095af',
14 | 900: '#8489a6',
15 | };
16 |
17 | const graphite: SimplePaletteColorOptions & Color = {
18 | ...shades,
19 | main: shades[900],
20 | light: shades[600],
21 | contrastText: text.contrast,
22 | };
23 |
24 | export default graphite;
25 |
--------------------------------------------------------------------------------
/src/colors/green.ts:
--------------------------------------------------------------------------------
1 | import text from './text';
2 | import { Color, SimplePaletteColorOptions } from './types';
3 |
4 | const shades: Color = {
5 | 50: '#f0f9e8',
6 | 100: '#d9f0c5',
7 | 200: '#c0e59f',
8 | 300: '#a6db77',
9 | 400: '#91d357',
10 | 500: '#7dcb35',
11 | 600: '#6dba2d',
12 | 700: '#58a623',
13 | 800: '#387f15',
14 | 900: '#0f7000',
15 | };
16 |
17 | const green: SimplePaletteColorOptions & Color = {
18 | ...shades,
19 | main: shades[800],
20 | light: shades[500],
21 | dark: shades[900],
22 | contrastText: text.contrast,
23 | };
24 |
25 | export default green;
26 |
--------------------------------------------------------------------------------
/src/colors/grey.ts:
--------------------------------------------------------------------------------
1 | import { Color, SimplePaletteColorOptions } from './types';
2 |
3 | const shades: Color = {
4 | 50: '#fafafa',
5 | 100: '#f1f7f9',
6 | 200: '#f8f8f8',
7 | 300: '#e9e9e9',
8 | 400: '#dbdbdb',
9 | 500: '#cacaca',
10 | 600: '#9c9c9c',
11 | 700: '#595959',
12 | 800: '#35393d',
13 | 900: '#292c2f',
14 | };
15 |
16 | const grey: SimplePaletteColorOptions & Color = {
17 | ...shades,
18 | main: shades[900],
19 | light: shades[600],
20 | };
21 |
22 | export default grey;
23 |
--------------------------------------------------------------------------------
/src/colors/navy.ts:
--------------------------------------------------------------------------------
1 | import text from './text';
2 | import { Color, SimplePaletteColorOptions } from './types';
3 |
4 | const shades: Color = {
5 | 50: '#eceef0',
6 | 100: '#d9dde1',
7 | 200: '#c7ccd2',
8 | 300: '#b4bbc3',
9 | 400: '#a2abb4',
10 | 500: '#8f9aa5',
11 | 600: '#7d8996',
12 | 700: '#6a7887',
13 | 800: '#576878',
14 | 900: '#455769',
15 | };
16 |
17 | const navy: SimplePaletteColorOptions & Color = {
18 | ...shades,
19 | main: shades[900],
20 | light: shades[700],
21 | contrastText: text.contrast,
22 | };
23 |
24 | export default navy;
25 |
--------------------------------------------------------------------------------
/src/colors/orange.ts:
--------------------------------------------------------------------------------
1 | import text from './text';
2 | import { Color, SimplePaletteColorOptions } from './types';
3 |
4 | const shades: Color = {
5 | 50: '#fdf1e6',
6 | 100: '#fce4cc',
7 | 200: '#fbd7b3',
8 | 300: '#f9c999',
9 | 400: '#f8bc80',
10 | 500: '#f7af66',
11 | 600: '#f5a24d',
12 | 700: '#f49433',
13 | 800: '#f27a00',
14 | 900: '#b14800',
15 | };
16 |
17 | const orange: SimplePaletteColorOptions & Color = {
18 | ...shades,
19 | main: shades[800],
20 | light: shades[600],
21 | contrastText: text.contrast,
22 | };
23 |
24 | export default orange;
25 |
--------------------------------------------------------------------------------
/src/colors/purple.ts:
--------------------------------------------------------------------------------
1 | import text from './text';
2 | import { Color, SimplePaletteColorOptions } from './types';
3 |
4 | const shades: Color = {
5 | 50: '#f4e7fe',
6 | 100: '#e8cffe',
7 | 200: '#ddb8fe',
8 | 300: '#d2a0fe',
9 | 400: '#c789fe',
10 | 500: '#bc71fe',
11 | 600: '#b15afe',
12 | 700: '#a642fe',
13 | 800: '#9013fe',
14 | 900: '#5500c9',
15 | };
16 |
17 | const purple: SimplePaletteColorOptions & Color = {
18 | ...shades,
19 | main: shades[800],
20 | light: shades[600],
21 | contrastText: text.contrast,
22 | };
23 |
24 | export default purple;
25 |
--------------------------------------------------------------------------------
/src/colors/red.ts:
--------------------------------------------------------------------------------
1 | import text from './text';
2 | import { Color, SimplePaletteColorOptions } from './types';
3 |
4 | const shades: Color = {
5 | 50: '#fdeaef',
6 | 100: '#fccbd5',
7 | 200: '#eb969f',
8 | 300: '#e06e79',
9 | 400: '#eb495a',
10 | 500: '#f13343',
11 | 600: '#e22941',
12 | 700: '#d01e3a',
13 | 800: '#c31533',
14 | 900: '#b40027',
15 | };
16 |
17 | const red: SimplePaletteColorOptions & Color = {
18 | ...shades,
19 | main: shades[800],
20 | light: shades[600],
21 | dark: shades[900],
22 | contrastText: text.contrast,
23 | };
24 |
25 | export default red;
26 |
--------------------------------------------------------------------------------
/src/colors/text.ts:
--------------------------------------------------------------------------------
1 | import black from './black';
2 | import gray from './grey';
3 | import { TypeText } from './types';
4 |
5 | const text: TypeText & { hint: string } = {
6 | primary: black[900],
7 | secondary: black[700],
8 | hint: black[600],
9 | disabled: black[400],
10 | contrast: gray[200],
11 | dark: black[900],
12 | };
13 |
14 | export default text;
15 |
--------------------------------------------------------------------------------
/src/colors/types.ts:
--------------------------------------------------------------------------------
1 | import { Color as MUIColor } from '@mui/material';
2 | import {
3 | SimplePaletteColorOptions,
4 | TypeText as MuiTypeText,
5 | } from '@mui/material/styles/createPalette';
6 |
7 | export { SimplePaletteColorOptions };
8 |
9 | export type Color = Omit;
10 |
11 | export interface TypeText extends MuiTypeText {
12 | contrast: string;
13 | dark: string;
14 | }
15 |
--------------------------------------------------------------------------------
/src/colors/yellow.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ColorPartial,
3 | SimplePaletteColorOptions,
4 | } from '@mui/material/styles/createPalette';
5 | import text from './text';
6 |
7 | const shades: ColorPartial = {
8 | 50: '#fefce8',
9 | 100: '#fdfad1',
10 | 200: '#fcf7bb',
11 | 300: '#fcf5a4',
12 | 400: '#fbf38d',
13 | 500: '#faf076',
14 | 600: '#faee60',
15 | 700: '#f9eb49',
16 | 800: '#f7bf4d',
17 | 900: '#916800',
18 | };
19 |
20 | const yellow: SimplePaletteColorOptions & ColorPartial = {
21 | ...shades,
22 | main: shades[800]!,
23 | light: shades[600],
24 | contrastText: text.contrast,
25 | };
26 |
27 | export default yellow;
28 |
--------------------------------------------------------------------------------
/src/components/Alert/AlertActionsRow.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { renderWithTheme } from '../../testUtils/renderWithTheme';
3 | import { AlertActionsRow } from './index';
4 |
5 | const testId = 'AlertActionsRow';
6 |
7 | test('it renders an AlertActionsRow', async () => {
8 | const { findByTestId } = renderWithTheme(
9 |
10 | );
11 |
12 | const actionRow = await findByTestId(testId);
13 | expect(actionRow).toBeInTheDocument();
14 | expect(actionRow).toHaveClass('ChromaAlertActionsRow-root');
15 | });
16 |
17 | test('it applies the provided className', async () => {
18 | const { findByTestId } = renderWithTheme(
19 |
20 | );
21 |
22 | const actionRow = await findByTestId(testId);
23 | expect(actionRow).toHaveClass('custom-class-name');
24 | });
25 |
--------------------------------------------------------------------------------
/src/components/Alert/AlertActionsRow.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { makeStyles } from '../../styles';
3 | import { GetClasses } from '../../typeUtils';
4 | import clsx from 'clsx';
5 |
6 | export const AlertActionsRowStylesKey = 'ChromaAlertActionsRow';
7 |
8 | export const useStyles = makeStyles(
9 | () => ({
10 | root: {
11 | marginTop: '0.5rem',
12 | marginLeft: '-0.625rem',
13 | marginRight: '-0.625rem',
14 | '& > button, a': {
15 | '&:not(:last-child)': {
16 | marginRight: '0.25rem',
17 | },
18 | },
19 | },
20 | }),
21 | { name: AlertActionsRowStylesKey }
22 | );
23 |
24 | export type AlertActionsRowClasses = GetClasses;
25 |
26 | export interface AlertActionsRowProps {
27 | children?: React.ReactNode;
28 | className?: string;
29 | }
30 |
31 | export const AlertActionsRow: React.FC = ({
32 | className,
33 | children,
34 | ...rootProps
35 | }) => {
36 | const classes = useStyles({});
37 | return (
38 |
39 | {children}
40 |
41 | );
42 | };
43 |
--------------------------------------------------------------------------------
/src/components/Alert/AlertBody.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { renderWithTheme } from '../../testUtils/renderWithTheme';
3 | import { AlertBody } from './index';
4 |
5 | const testId = 'AlertBody';
6 |
7 | test('it renders an AlertBody', async () => {
8 | const { findByTestId } = renderWithTheme();
9 |
10 | const body = await findByTestId(testId);
11 | expect(body).toBeInTheDocument();
12 | expect(body).toHaveClass('ChromaAlertBody-root');
13 | });
14 |
15 | test('it applies the provided className', async () => {
16 | const { findByTestId } = renderWithTheme(
17 |
18 | );
19 |
20 | const body = await findByTestId(testId);
21 | expect(body).toHaveClass('custom-class-name');
22 | });
23 |
24 | test('it applies the space-between class', async () => {
25 | const { findByTestId } = renderWithTheme(
26 |
27 | );
28 |
29 | const body = await findByTestId(testId);
30 | expect(body).toHaveClass('ChromaAlertBody-spaceBetween');
31 | });
32 |
--------------------------------------------------------------------------------
/src/components/Alert/AlertIcon.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { IconComponent } from '../../testUtils/IconComponent';
3 | import { renderWithTheme } from '../../testUtils/renderWithTheme';
4 | import { AlertIcon } from './index';
5 |
6 | const testId = 'AlertIcon';
7 |
8 | test('it renders an AlertIcon', async () => {
9 | const { findByTestId, findByRole } = renderWithTheme(
10 |
11 | );
12 |
13 | const iconWrapper = await findByTestId(testId);
14 | expect(iconWrapper).toBeInTheDocument();
15 | expect(iconWrapper).toHaveClass('ChromaAlertIcon-root');
16 |
17 | const icon = await findByRole('img', { hidden: true });
18 | expect(icon).toBeInTheDocument();
19 | });
20 |
21 | test('it applies the provided className', async () => {
22 | const { findByTestId } = renderWithTheme(
23 |
24 | );
25 |
26 | const body = await findByTestId(testId);
27 | expect(body).toHaveClass('custom-class-name');
28 | });
29 |
--------------------------------------------------------------------------------
/src/components/Alert/AlertIcon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { makeStyles } from '../../styles';
3 | import { GetClasses } from '../../typeUtils';
4 | import clsx from 'clsx';
5 |
6 | export const AlertIconStylesKey = 'ChromaAlertIcon';
7 |
8 | export const useStyles = makeStyles(
9 | () => ({
10 | root: {
11 | color: 'inherit',
12 | display: 'flex',
13 | flexShrink: 0,
14 | marginTop: '-0.0625rem',
15 | marginRight: '0.75rem',
16 | height: '1.5rem',
17 | width: '1.5rem',
18 | },
19 | }),
20 | { name: AlertIconStylesKey }
21 | );
22 |
23 | export type AlertIconClasses = GetClasses;
24 |
25 | export interface AlertIconProps {
26 | children?: React.ReactNode;
27 | className?: string;
28 | icon?: React.MemoExoticComponent<
29 | (props: React.SVGProps) => JSX.Element
30 | >;
31 | }
32 |
33 | export const AlertIcon: React.FC = ({
34 | className,
35 | children,
36 | icon: Icon,
37 | ...rootProps
38 | }) => {
39 | const classes = useStyles({});
40 | return (
41 |
42 | {Icon && }
43 | {children}
44 |
45 | );
46 | };
47 |
--------------------------------------------------------------------------------
/src/components/Alert/index.ts:
--------------------------------------------------------------------------------
1 | export { Alert, AlertProps, AlertStylesKey, AlertClasses } from './Alert';
2 | export {
3 | AlertActionsRow,
4 | AlertActionsRowProps,
5 | AlertActionsRowStylesKey,
6 | AlertActionsRowClasses,
7 | } from './AlertActionsRow';
8 |
9 | export {
10 | AlertBody,
11 | AlertBodyProps,
12 | AlertBodyStylesKey,
13 | AlertBodyClasses,
14 | } from './AlertBody';
15 |
16 | export {
17 | AlertIcon,
18 | AlertIconProps,
19 | AlertIconStylesKey,
20 | AlertIconClasses,
21 | } from './AlertIcon';
22 |
--------------------------------------------------------------------------------
/src/components/Avatar/AvatarSizeContext.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { AvatarProps } from './Avatar';
3 |
4 | interface AvatarSizeValue {
5 | size?: AvatarProps['size'];
6 | }
7 |
8 | export const AvatarSizeContext = React.createContext({
9 | size: 1,
10 | });
11 |
12 | export const useAvatarSize = () => React.useContext(AvatarSizeContext);
13 |
--------------------------------------------------------------------------------
/src/components/Avatar/index.ts:
--------------------------------------------------------------------------------
1 | export { Avatar, AvatarProps, AvatarClasses, AvatarStylesKey } from './Avatar';
2 | export {
3 | AvatarBadge,
4 | AvatarBadgeProps,
5 | AvatarBadgeClasses,
6 | AvatarBadgeStylesKey,
7 | } from './AvatarBadge';
8 |
--------------------------------------------------------------------------------
/src/components/Box/index.ts:
--------------------------------------------------------------------------------
1 | export { Box, BoxClasses, BoxProps, BoxStylesKey } from './Box';
2 |
--------------------------------------------------------------------------------
/src/components/Breadcrumbs/BreadCrumbNav.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { renderWithTheme } from '../../testUtils/renderWithTheme';
3 | import { BreadcrumbNav } from './index';
4 |
5 | const testId = 'BreadcrumbNav';
6 |
7 | test('it renders a BreadcrumbNav', async () => {
8 | const { findByTestId } = renderWithTheme(
9 |
10 | );
11 | const breadcrumbNav = await findByTestId(testId);
12 | expect(breadcrumbNav).toBeInTheDocument();
13 | });
14 |
15 | test('it applies the provided className', async () => {
16 | const { findByTestId } = renderWithTheme(
17 |
18 | );
19 | const breadcrumbNav = await findByTestId(testId);
20 | expect(breadcrumbNav).toHaveClass('custom-class-name');
21 | });
22 |
--------------------------------------------------------------------------------
/src/components/Breadcrumbs/BreadcrumbNav.tsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx';
2 | import * as React from 'react';
3 | import { makeStyles } from '../../styles/index';
4 | import { GetClasses } from '../../typeUtils';
5 |
6 | export const BreadcrumbNavStylesKey = 'ChromaBreadcrumbNav';
7 |
8 | export const useStyles = makeStyles(
9 | (theme) => ({
10 | root: {},
11 | ol: {
12 | margin: theme.spacing(0),
13 | paddingLeft: theme.spacing(0),
14 | listStyle: 'none',
15 | },
16 | }),
17 | { name: BreadcrumbNavStylesKey }
18 | );
19 |
20 | export type BreadcrumbNavClasses = GetClasses;
21 |
22 | export interface BreadcrumbNavProps
23 | extends React.DetailedHTMLProps<
24 | React.HTMLAttributes,
25 | HTMLElement
26 | > {
27 | children?: React.ReactNode;
28 | }
29 |
30 | export const BreadcrumbNav: React.FC = ({
31 | className,
32 | children,
33 | ...rootProps
34 | }) => {
35 | const classes = useStyles({});
36 | return (
37 |
44 | );
45 | };
46 |
--------------------------------------------------------------------------------
/src/components/Breadcrumbs/Breadcrumbs.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StoryObj, Meta } from '@storybook/react';
3 | import { MemoryRouter } from 'react-router-dom';
4 |
5 | import { Breadcrumbs } from './Breadcrumbs';
6 |
7 | const meta: Meta = {
8 | component: Breadcrumbs,
9 | args: {
10 | crumbs: [
11 | {
12 | text: 'Parent',
13 | url: 'root',
14 | },
15 | {
16 | text: 'Child',
17 | url: 'root/child',
18 | },
19 | {
20 | text: 'GrandChild',
21 | url: 'root/child/grand',
22 | },
23 | ],
24 | },
25 | decorators: [(story) => {story()}],
26 | };
27 | export default meta;
28 | type Story = StoryObj;
29 |
30 | export const Default: Story = {};
31 |
32 | export const InverseDark = {
33 | parameters: {
34 | backgrounds: { default: 'dark' },
35 | },
36 | args: {
37 | color: 'inverse',
38 | },
39 | };
40 |
41 | export const InverseBlue = {
42 | parameters: {
43 | backgrounds: { default: 'blue' },
44 | },
45 |
46 | args: {
47 | color: 'inverse',
48 | },
49 | };
50 |
--------------------------------------------------------------------------------
/src/components/Breadcrumbs/Breadcrumbs.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { MemoryRouter } from 'react-router-dom';
3 | import { renderWithTheme } from '../../testUtils/renderWithTheme';
4 | import { Breadcrumbs, BreadcrumbsProps } from './index';
5 |
6 | const testId = 'Breadcrumbs';
7 |
8 | const getBaseProps = (): BreadcrumbsProps => ({
9 | crumbs: [
10 | { text: 'parent', url: 'p' },
11 | { text: 'child', url: 'c' },
12 | ],
13 | });
14 |
15 | test('it renders Breadcrumbs', async () => {
16 | const props = getBaseProps();
17 | const { findByTestId, findByText } = renderWithTheme(
18 |
19 |
20 |
21 | );
22 | const breadcrumbs = await findByTestId(testId);
23 | expect(breadcrumbs).toBeInTheDocument();
24 |
25 | const parentLink = await findByText(props.crumbs[0].text);
26 | expect(parentLink).toBeInTheDocument();
27 |
28 | const childLink = await findByText(props.crumbs[1].text);
29 | expect(childLink).toBeInTheDocument();
30 | });
31 |
32 | test('it applies the provided className', async () => {
33 | const props = getBaseProps();
34 | const { findByTestId } = renderWithTheme(
35 |
36 |
41 |
42 | );
43 | const breadcrumbs = await findByTestId(testId);
44 | expect(breadcrumbs).toHaveClass('custom-class-name');
45 | });
46 |
47 | test('it applies `color="inverse"`', async () => {
48 | const props = getBaseProps();
49 | const { findByTestId } = renderWithTheme(
50 |
51 |
52 |
53 | );
54 | const breadcrumbs = await findByTestId(testId);
55 | expect(breadcrumbs).toHaveClass(' ChromaBreadcrumbs-inverse');
56 | });
57 |
--------------------------------------------------------------------------------
/src/components/Breadcrumbs/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | Breadcrumbs,
3 | BreadcrumbsClasses,
4 | BreadcrumbsProps,
5 | BreadcrumbsStylesKey,
6 | } from './Breadcrumbs';
7 |
8 | export {
9 | Breadcrumb,
10 | BreadcrumbClasses,
11 | BreadcrumbProps,
12 | BreadcrumbStylesKey,
13 | } from './Breadcrumb';
14 |
15 | export {
16 | BreadcrumbNav,
17 | BreadcrumbNavClasses,
18 | BreadcrumbNavProps,
19 | BreadcrumbNavStylesKey,
20 | } from './BreadcrumbNav';
21 |
--------------------------------------------------------------------------------
/src/components/Button/index.ts:
--------------------------------------------------------------------------------
1 | export { Button, ButtonProps, ButtonStylesKey, ButtonClasses } from './Button';
2 |
--------------------------------------------------------------------------------
/src/components/ButtonFilePicker/ButtonFilePicker.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StoryObj, StoryFn, Meta } from '@storybook/react';
3 |
4 | import { ButtonFilePicker } from './ButtonFilePicker';
5 | import { Edit } from '@lifeomic/chromicons';
6 | const meta: Meta = {
7 | component: ButtonFilePicker,
8 | };
9 | export default meta;
10 | type Story = StoryObj;
11 |
12 | const Template: StoryFn = (args) => (
13 | Button File Picker
14 | );
15 |
16 | export const Default: Story = {
17 | render: Template,
18 | };
19 |
20 | export const Accept: Story = {
21 | render: Template,
22 | args: {
23 | accept: '.jpg,.jpeg',
24 | },
25 | parameters: {
26 | docs: {
27 | description: {
28 | story:
29 | 'A CSV list of the type of file extension(s) the file picker should accept.',
30 | },
31 | },
32 | },
33 | };
34 |
35 | export const Disabled: Story = {
36 | render: Template,
37 | args: {
38 | disabled: true,
39 | },
40 | };
41 |
42 | export const Icon: Story = {
43 | render: Template,
44 | args: {
45 | icon: Edit,
46 | },
47 | };
48 |
49 | export const InverseDark: Story = {
50 | render: Template,
51 | args: {
52 | color: 'inverse',
53 | },
54 | parameters: {
55 | backgrounds: { default: 'dark' },
56 | },
57 | };
58 |
59 | export const InverseBlue: Story = {
60 | render: Template,
61 | args: {
62 | color: 'inverse',
63 | },
64 | parameters: {
65 | backgrounds: { default: 'blue' },
66 | },
67 | };
68 |
--------------------------------------------------------------------------------
/src/components/ButtonFilePicker/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | ButtonFilePicker,
3 | ButtonFilePickerProps,
4 | ButtonFilePickerStylesKey,
5 | ButtonFilePickerClasses,
6 | } from './ButtonFilePicker';
7 |
--------------------------------------------------------------------------------
/src/components/ButtonFloat/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | ButtonFloat,
3 | ButtonFloatClasses,
4 | ButtonFloatProps,
5 | ButtonFloatStylesKey,
6 | } from './ButtonFloat';
7 |
--------------------------------------------------------------------------------
/src/components/ButtonLink/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | ButtonLink,
3 | ButtonLinkClasses,
4 | ButtonLinkProps,
5 | ButtonLinkStylesKey,
6 | } from './ButtonLink';
7 |
--------------------------------------------------------------------------------
/src/components/ButtonUnstyled/ButtonUnstyled.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StoryObj, StoryFn, Meta } from '@storybook/react';
3 |
4 | import { ButtonUnstyled } from './ButtonUnstyled';
5 |
6 | const meta: Meta = {
7 | component: ButtonUnstyled,
8 | argTypes: {
9 | onClick: { action: 'clicked' },
10 | },
11 | };
12 | export default meta;
13 | type Story = StoryObj;
14 |
15 | const Template: StoryFn = (args) => (
16 | Button Unstyled
17 | );
18 |
19 | export const Default: Story = {
20 | render: Template,
21 | };
22 |
--------------------------------------------------------------------------------
/src/components/ButtonUnstyled/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | ButtonUnstyled,
3 | ButtonUnstyledProps,
4 | ButtonUnstyledStylesKey,
5 | ButtonUnstyledClasses,
6 | } from './ButtonUnstyled';
7 |
--------------------------------------------------------------------------------
/src/components/Checkbox/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | Checkbox,
3 | CheckboxClasses,
4 | CheckboxProps,
5 | CheckboxStylesKey,
6 | } from './Checkbox';
7 |
--------------------------------------------------------------------------------
/src/components/Chip/Chip.stories.tsx:
--------------------------------------------------------------------------------
1 | import { StoryObj, Meta } from '@storybook/react';
2 |
3 | import { Chip } from './Chip';
4 |
5 | const meta: Meta = {
6 | component: Chip,
7 | args: {
8 | label: 'Chip',
9 | },
10 | };
11 | export default meta;
12 | type Story = StoryObj;
13 |
14 | export const Default: Story = {};
15 |
16 | export const OnDelete: Story = {
17 | parameters: {
18 | docs: {
19 | description: {
20 | story:
21 | 'The function to call when the removal button is clicked. Note that this does not ' +
22 | 'actually do anything to the Chip component, removing the element from the DOM is ' +
23 | 'up to the consumer to take care of.\n\n' +
24 | 'To display a Chip, but not display the removal button, do not provide this prop.',
25 | },
26 | },
27 | },
28 | };
29 |
30 | export const DisableDelete: Story = {
31 | args: {
32 | disableDelete: true,
33 | },
34 | };
35 |
--------------------------------------------------------------------------------
/src/components/Chip/index.ts:
--------------------------------------------------------------------------------
1 | export { Chip, ChipClasses, ChipProps, ChipStylesKey } from './Chip';
2 |
--------------------------------------------------------------------------------
/src/components/ColorPicker/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | ColorPicker,
3 | ColorPickerClasses,
4 | ColorPickerProps,
5 | ColorPickerStylesKey,
6 | isValidHexColor,
7 | } from './ColorPicker';
8 |
--------------------------------------------------------------------------------
/src/components/DayPicker/index.ts:
--------------------------------------------------------------------------------
1 | export * from './DayPicker';
2 |
--------------------------------------------------------------------------------
/src/components/DescriptionList/DescriptionDivider.tsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx';
2 | import * as React from 'react';
3 | import { makeStyles } from '../../styles';
4 | import { GetClasses } from '../../typeUtils';
5 |
6 | export const DescriptionDividerStylesKey = 'ChromaDescriptionDivider';
7 |
8 | export const useStyles = makeStyles(
9 | (theme) => ({
10 | root: {
11 | backgroundColor: theme.palette.divider,
12 | border: 'none',
13 | flexShrink: 0,
14 | gridColumn: '1/-1',
15 | margin: 0,
16 | height: 1,
17 | width: '100%',
18 | },
19 | }),
20 | { name: DescriptionDividerStylesKey }
21 | );
22 |
23 | export interface DescriptionDividerOwnProps
24 | extends React.DetailedHTMLProps<
25 | React.HTMLAttributes,
26 | HTMLLIElement
27 | > {}
28 |
29 | export type DescriptionDividerClasses = GetClasses;
30 |
31 | export interface DescriptionDividerProps extends DescriptionDividerOwnProps {}
32 |
33 | /**
34 | * A `DescriptionDivider` is a thin line that groups content in description lists. The divider
35 | * renders as an ``.
36 | *
37 | * ### Links
38 | *
39 | * - [Component Source](https://github.com/lifeomic/chroma-react/blob/master/src/components/DescriptionDivider/DescriptionDivider.tsx)
40 | * - [Story Source](https://github.com/lifeomic/chroma-react/blob/master/stories/components/DescriptionDivider/DescriptionDivider.stories.tsx)
41 | */
42 | export const DescriptionDivider = React.forwardRef<
43 | HTMLLIElement,
44 | DescriptionDividerProps
45 | >(({ className, ...rootProps }, ref) => {
46 | const classes = useStyles({});
47 |
48 | return (
49 |
50 | );
51 | });
52 |
--------------------------------------------------------------------------------
/src/components/DescriptionList/DescriptionListGroupHeading.tsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx';
2 | import * as React from 'react';
3 | import { makeStyles } from '../../styles';
4 | import { GetClasses } from '../../typeUtils';
5 | import { lighten } from '@mui/material/styles';
6 |
7 | export const DescriptionListGroupHeadingStylesKey = 'ChromaListGroupHeading';
8 |
9 | export const useStyles = makeStyles(
10 | (theme) => ({
11 | root: {
12 | borderBottom: `solid ${theme.pxToRem(3)} ${lighten(
13 | theme.palette.divider,
14 | 0.4
15 | )}`,
16 | color: theme.palette.text.hint,
17 | fontSize: theme.pxToRem(10),
18 | fontWeight: theme.typography.fontWeightBold,
19 | gridColumn: '1/-1',
20 | letterSpacing: theme.pxToRem(1),
21 | padding: theme.spacing(1.25, 0),
22 | marginTop: theme.spacing(1),
23 | textTransform: 'uppercase',
24 | '&:first-child': {
25 | marginTop: 0,
26 | paddingTop: 0,
27 | },
28 | },
29 | }),
30 | { name: DescriptionListGroupHeadingStylesKey }
31 | );
32 |
33 | export type DescriptionListGroupHeadingClasses = GetClasses;
34 |
35 | export type DescriptionListGroupHeadingProps = {
36 | className?: string;
37 | children?: React.ReactNode;
38 | };
39 |
40 | export const DescriptionListGroupHeading: React.FC = ({
41 | className,
42 | children,
43 | }) => {
44 | const classes = useStyles({});
45 | return {children};
46 | };
47 |
--------------------------------------------------------------------------------
/src/components/DescriptionList/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | DescriptionList,
3 | DescriptionListClasses,
4 | DescriptionListProps,
5 | DescriptionListStylesKey,
6 | } from './DescriptionList';
7 |
8 | export {
9 | DescriptionTerm,
10 | DescriptionTermClasses,
11 | DescriptionTermProps,
12 | DescriptionTermStylesKey,
13 | } from './DescriptionTerm';
14 |
15 | export {
16 | DescriptionDetails,
17 | DescriptionDetailsClasses,
18 | DescriptionDetailsProps,
19 | DescriptionDetailsStylesKey,
20 | } from './DescriptionDetails';
21 |
22 | export {
23 | DescriptionListGroupHeading,
24 | DescriptionListGroupHeadingClasses,
25 | DescriptionListGroupHeadingProps,
26 | DescriptionListGroupHeadingStylesKey,
27 | } from './DescriptionListGroupHeading';
28 |
29 | export {
30 | DescriptionDivider,
31 | DescriptionDividerClasses,
32 | DescriptionDividerProps,
33 | DescriptionDividerStylesKey,
34 | } from './DescriptionDivider';
35 |
--------------------------------------------------------------------------------
/src/components/Divider/Divider.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StoryObj, Meta } from '@storybook/react';
3 |
4 | import { Divider } from './Divider';
5 |
6 | const meta: Meta = {
7 | title: 'Layout/Divider',
8 | component: Divider,
9 | };
10 | export default meta;
11 | type Story = StoryObj;
12 |
13 | export const Default: Story = {};
14 |
15 | export const InverseDark: Story = {
16 | parameters: {
17 | backgrounds: { default: 'dark' },
18 | },
19 | args: {
20 | color: 'inverse',
21 | },
22 | };
23 |
24 | export const InverseBlue: Story = {
25 | parameters: {
26 | backgrounds: { default: 'blue' },
27 | },
28 | args: {
29 | color: 'inverse',
30 | },
31 | };
32 |
33 | export const Direction: Story = {
34 | args: {
35 | direction: 'row',
36 | },
37 | decorators: [
38 | (story: Function) => {story()}
,
39 | ],
40 | };
41 |
--------------------------------------------------------------------------------
/src/components/Divider/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | Divider,
3 | DividerProps,
4 | DividerClasses,
5 | DividerStylesKey,
6 | } from './Divider';
7 |
--------------------------------------------------------------------------------
/src/components/DotLoader/DotLoader.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StoryObj, StoryFn, Meta } from '@storybook/react';
3 | import { makeStyles } from '../../styles';
4 |
5 | import { DotLoader } from './DotLoader';
6 |
7 | const useStyles = makeStyles(() => ({
8 | styledLoader: {
9 | fill: 'black',
10 | },
11 | }));
12 |
13 | const meta: Meta = {
14 | title: 'Components/DotLoader',
15 | component: DotLoader,
16 | argTypes: {},
17 | } as Meta;
18 | export default meta;
19 | type Story = StoryObj;
20 |
21 | const Template: StoryFn = (args) => {
22 | const classes = useStyles({});
23 | return ;
24 | };
25 |
26 | export const Default: Story = {};
27 |
28 | export const Small: Story = {
29 | args: {
30 | size: 1,
31 | },
32 | };
33 |
34 | export const Styled: Story = {
35 | render: Template,
36 | };
37 |
--------------------------------------------------------------------------------
/src/components/DotLoader/DotLoader.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { renderWithTheme } from '../../testUtils/renderWithTheme';
3 | import { DotLoader } from './';
4 |
5 | const testId = 'DotLoader';
6 |
7 | test('it renders a DotLoader', async () => {
8 | const { findByTestId, findByRole } = renderWithTheme(
9 |
10 | );
11 |
12 | const root = await findByTestId(testId);
13 | expect(root).toBeInTheDocument();
14 |
15 | const ariaRole = await findByRole('progressbar');
16 | expect(ariaRole).toBeTruthy();
17 | });
18 |
19 | test('it renders a DotLoader with "size={0}"', async () => {
20 | const { findByTestId } = renderWithTheme(
21 |
22 | );
23 |
24 | const root = await findByTestId(testId);
25 | expect(root).toHaveClass('ChromaDotLoader-size0');
26 | });
27 |
28 | test('it renders a DotLoader with "size={1}"', async () => {
29 | const { findByTestId } = renderWithTheme(
30 |
31 | );
32 |
33 | const root = await findByTestId(testId);
34 | expect(root).toHaveClass('ChromaDotLoader-size1');
35 | });
36 |
--------------------------------------------------------------------------------
/src/components/DotLoader/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | DotLoader,
3 | DotLoaderProps,
4 | DotLoaderStylesKey,
5 | DotLoaderClasses,
6 | } from './DotLoader';
7 |
--------------------------------------------------------------------------------
/src/components/ExpansionPanel/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | ExpansionPanel,
3 | ExpansionPanelProps,
4 | ExpansionPanelClasses,
5 | ExpansionPanelStylesKey,
6 | } from './ExpansionPanel';
7 |
--------------------------------------------------------------------------------
/src/components/FormBox/FormBox.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StoryObj, StoryFn, Meta } from '@storybook/react';
3 |
4 | import { FormBox } from './FormBox';
5 | import { TextField } from '../TextField';
6 | import { TextArea } from '../TextArea';
7 |
8 | const meta: Meta = {
9 | title: 'Form Components/FormBox',
10 | component: FormBox,
11 | argTypes: {},
12 | };
13 | export default meta;
14 | type Story = StoryObj;
15 |
16 | const Template: StoryFn = (args) => (
17 |
18 |
19 |
20 |
21 | );
22 |
23 | export const Default: Story = {
24 | render: Template,
25 | };
26 |
--------------------------------------------------------------------------------
/src/components/FormBox/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | FormBox,
3 | FormBoxClasses,
4 | FormBoxProps,
5 | FormBoxStylesKey,
6 | } from './FormBox';
7 |
--------------------------------------------------------------------------------
/src/components/Header/Header.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StoryObj, Meta } from '@storybook/react';
3 |
4 | import { Header } from './Header';
5 | import { Logo } from '../../assets/Logo';
6 | import { Button } from '../Button';
7 | import { Avatar } from '../Avatar';
8 |
9 | const meta: Meta = {
10 | title: 'Components/Header',
11 | component: Header,
12 | };
13 | export default meta;
14 | type Story = StoryObj;
15 |
16 | export const Default: Story = {
17 | args: {
18 | left: ,
19 | },
20 | };
21 |
22 | export const Full: Story = {
23 | args: {
24 | centerLogo: true,
25 | logo: ,
26 | left: ,
27 | right: ,
28 | },
29 | };
30 |
--------------------------------------------------------------------------------
/src/components/Header/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | Header,
3 | HeaderClasses,
4 | HeaderProps,
5 | HeaderStylesKey,
6 | headerHeight,
7 | } from './Header';
8 |
--------------------------------------------------------------------------------
/src/components/IconButton/IconButton.stories.tsx:
--------------------------------------------------------------------------------
1 | import { StoryObj, Meta } from '@storybook/react';
2 |
3 | import { IconButton } from './IconButton';
4 | import { Edit, Settings } from '@lifeomic/chromicons';
5 |
6 | const meta: Meta = {
7 | component: IconButton,
8 | args: {
9 | 'aria-label': 'Edit',
10 | icon: Edit,
11 | },
12 | argTypes: {
13 | disabled: {
14 | description: '`boolean`',
15 | table: {
16 | defaultValue: { summary: false },
17 | },
18 | control: { type: 'boolean' },
19 | },
20 | onClick: { action: 'clicked' },
21 | },
22 | };
23 | export default meta;
24 | type Story = StoryObj;
25 |
26 | export const Default: Story = {};
27 |
28 | export const Icon: Story = {
29 | parameters: {
30 | docs: {
31 | description: {
32 | story:
33 | 'For a list of available icons, see our Chrōmicons catalog.',
34 | },
35 | },
36 | },
37 | args: {
38 | icon: Settings,
39 | },
40 | };
41 |
42 | export const Size: Story = {
43 | args: {
44 | size: 0,
45 | },
46 | };
47 |
48 | export const Color: Story = {
49 | args: {
50 | color: 'negative',
51 | },
52 | };
53 |
54 | export const Disabled: Story = {
55 | args: {
56 | disabled: true,
57 | },
58 | };
59 |
60 | export const InverseDark: Story = {
61 | parameters: {
62 | backgrounds: { default: 'dark' },
63 | },
64 | args: {
65 | color: 'inverse',
66 | },
67 | };
68 |
69 | export const InverseBlue: Story = {
70 | parameters: {
71 | backgrounds: { default: 'blue' },
72 | },
73 | args: {
74 | color: 'inverse',
75 | },
76 | };
77 |
--------------------------------------------------------------------------------
/src/components/IconButton/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | IconButton,
3 | IconButtonClasses,
4 | IconButtonProps,
5 | IconButtonStylesKey,
6 | } from './IconButton';
7 |
--------------------------------------------------------------------------------
/src/components/IconButtonFloat/IconButtonFloat.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StoryObj, Meta } from '@storybook/react';
3 |
4 | import { IconButtonFloat } from './IconButtonFloat';
5 | import { Edit, Settings } from '@lifeomic/chromicons';
6 |
7 | const meta: Meta = {
8 | component: IconButtonFloat,
9 | args: {
10 | 'aria-label': 'Edit',
11 | icon: Edit,
12 | },
13 | argTypes: {
14 | disabled: {
15 | description: '`boolean`',
16 | table: {
17 | defaultValue: { summary: false },
18 | },
19 | control: { type: 'boolean' },
20 | },
21 | onClick: { action: 'clicked' },
22 | },
23 | decorators: [
24 | (story) => (
25 |
26 |
38 | {story()}
39 |
40 |
41 | ),
42 | ],
43 | };
44 | export default meta;
45 | type Story = StoryObj;
46 |
47 | export const Default: Story = {};
48 |
49 | export const Icon: Story = {
50 | parameters: {
51 | docs: {
52 | description: {
53 | story:
54 | 'For a list of available icons, see our Chrōmicons catalog.',
55 | },
56 | },
57 | },
58 | args: {
59 | icon: Settings,
60 | },
61 | };
62 |
63 | export const Size: Story = {
64 | args: {
65 | size: 0,
66 | },
67 | };
68 |
69 | export const Justify: Story = {
70 | args: {
71 | justify: 'left',
72 | },
73 | };
74 |
75 | export const Disabled: Story = {
76 | args: {
77 | disabled: true,
78 | },
79 | };
80 |
--------------------------------------------------------------------------------
/src/components/IconButtonFloat/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | IconButtonFloat,
3 | IconButtonFloatClasses,
4 | IconButtonFloatProps,
5 | IconButtonFloatStylesKey,
6 | } from './IconButtonFloat';
7 |
--------------------------------------------------------------------------------
/src/components/IconButtonLink/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | IconButtonLink,
3 | IconButtonLinkClasses,
4 | IconButtonLinkProps,
5 | IconButtonLinkStylesKey,
6 | } from './IconButtonLink';
7 |
--------------------------------------------------------------------------------
/src/components/IconTile/IconTile.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StoryObj, StoryFn, Meta } from '@storybook/react';
3 |
4 | import { IconTile } from './IconTile';
5 | import { IconTileHero } from './IconTileHero';
6 | import { IconTileBadge } from './IconTileBadge';
7 | import { IconTileContent } from './IconTileContent';
8 | import { Edit2 } from '@lifeomic/chromicons';
9 | import sampleBackground from '../../../stories/assets/sampleBackground.svg';
10 |
11 | const meta: Meta = {
12 | component: IconTile,
13 | argTypes: {
14 | onClick: { action: 'clicked' },
15 | },
16 | };
17 | export default meta;
18 | type Story = StoryObj;
19 |
20 | const Template: StoryFn = (args) => (
21 |
22 |
23 |
24 |
25 |
26 | );
27 |
28 | export const Default: Story = {
29 | render: Template,
30 | };
31 |
--------------------------------------------------------------------------------
/src/components/IconTile/IconTile.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { renderWithTheme } from '../../testUtils/renderWithTheme';
3 | import { IconTile } from './index';
4 |
5 | const testId = 'IconTile';
6 |
7 | test('it renders an IconTile', async () => {
8 | const { findByTestId } = renderWithTheme();
9 | const root = await findByTestId(testId);
10 | expect(root).toBeInTheDocument();
11 | });
12 |
13 | test('it applies the provided className', async () => {
14 | const { findByTestId } = renderWithTheme(
15 |
16 | );
17 | const root = await findByTestId(testId);
18 | expect(root).toHaveClass('custom-class-name');
19 | });
20 |
21 | test('it spreads props', async () => {
22 | const role = 'link';
23 | const { findByTestId } = renderWithTheme(
24 |
25 | );
26 | const root = await findByTestId(testId);
27 | expect(root.getAttribute('role')).toBe(role);
28 | });
29 |
30 | test('it applies the proper attributes when onClick is provided', async () => {
31 | const { findByTestId } = renderWithTheme(
32 | ({})} data-testid={testId} />
33 | );
34 | const root = await findByTestId(testId);
35 | expect(root).toHaveClass('ChromaIconTile-cursorPointer');
36 | expect(root.getAttribute('role')).toEqual('button');
37 | expect(root.getAttribute('tabIndex')).toEqual('0');
38 | });
39 |
40 | test('it renders children', async () => {
41 | const testId = 'children';
42 | const { findByTestId } = renderWithTheme(
43 |
44 |
45 |
46 | );
47 |
48 | const children = await findByTestId(testId);
49 | expect(children).toBeInTheDocument();
50 | });
51 |
--------------------------------------------------------------------------------
/src/components/IconTile/IconTileBadge.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { IconComponent } from '../../testUtils/IconComponent';
3 | import { renderWithTheme } from '../../testUtils/renderWithTheme';
4 | import { IconTileBadge } from './index';
5 |
6 | const testId = 'IconTileBadge';
7 |
8 | test('it renders an IconTileBadge', async () => {
9 | const { findByTestId } = renderWithTheme(
10 |
11 | );
12 | const root = await findByTestId(testId);
13 | expect(root).toBeInTheDocument();
14 | });
15 |
16 | test('it applies the provided className', async () => {
17 | const { findByTestId } = renderWithTheme(
18 |
19 | );
20 | const root = await findByTestId(testId);
21 | expect(root).toHaveClass('custom-class-name');
22 | });
23 |
24 | test('it spreads props', async () => {
25 | const role = 'link';
26 |
27 | const { findByTestId } = renderWithTheme(
28 |
29 | );
30 | const root = await findByTestId(testId);
31 | expect(root.getAttribute('role')).toBe(role);
32 | });
33 |
34 | test('it renders the provided icon', async () => {
35 | const { getByRole } = renderWithTheme(
36 |
37 | );
38 | const root = getByRole('img', { hidden: true });
39 | expect(root).toBeInTheDocument();
40 | expect(root).toHaveClass('ChromaIconTileBadge-icon');
41 | });
42 |
43 | test('it renders children', async () => {
44 | const testId = 'children';
45 | const { findByTestId } = renderWithTheme(
46 |
47 |
48 |
49 | );
50 |
51 | const children = await findByTestId(testId);
52 | expect(children).toBeInTheDocument();
53 | });
54 |
--------------------------------------------------------------------------------
/src/components/IconTile/IconTileContent.tsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx';
2 | import * as React from 'react';
3 | import { makeStyles } from '../../styles/index';
4 | import { GetClasses } from '../../typeUtils';
5 | import { Text } from '../Text';
6 |
7 | export const IconTileContentStylesKey = 'ChromaIconTileContent';
8 |
9 | export const useStyles = makeStyles(
10 | (theme) => ({
11 | root: {
12 | width: '100%',
13 | height: '57%',
14 | display: 'flex',
15 | flexDirection: 'column',
16 | justifyContent: 'center',
17 | alignItems: 'center',
18 | },
19 | text: {
20 | marginTop: theme.spacing(1),
21 | fontSize: theme.pxToRem(18),
22 | fontWeight: theme.typography.fontWeightBold,
23 | letterSpacing: 0.25,
24 | },
25 | caption: {
26 | color: theme.palette.black[500],
27 | textTransform: 'uppercase',
28 | letterSpacing: 2,
29 | },
30 | }),
31 | { name: IconTileContentStylesKey }
32 | );
33 |
34 | export interface IconTileContentOwnProps
35 | extends React.DetailedHTMLProps<
36 | React.HTMLAttributes,
37 | HTMLDivElement
38 | > {
39 | caption?: string;
40 | children?: React.ReactNode;
41 | text?: string;
42 | }
43 |
44 | export type IconTileContentClasses = GetClasses;
45 |
46 | export interface IconTileContentProps extends IconTileContentOwnProps {}
47 |
48 | export const IconTileContent = React.forwardRef<
49 | HTMLDivElement,
50 | IconTileContentProps
51 | >(({ children, className, text, caption, ...rootProps }, ref) => {
52 | const classes = useStyles({});
53 |
54 | return (
55 |
56 | {!!text && {text}}
57 | {!!caption && (
58 |
59 | {caption}
60 |
61 | )}
62 | {children}
63 |
64 | );
65 | });
66 |
--------------------------------------------------------------------------------
/src/components/IconTile/IconTileHero.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { renderWithTheme } from '../../testUtils/renderWithTheme';
3 | import { IconTileHero } from './index';
4 |
5 | const testId = 'IconTileHero';
6 |
7 | test('it renders an IconTileHero', async () => {
8 | const { findByTestId } = renderWithTheme(
9 |
10 | );
11 | const root = await findByTestId(testId);
12 | expect(root).toBeInTheDocument();
13 | });
14 |
15 | test('it applies the provided className', async () => {
16 | const { findByTestId } = renderWithTheme(
17 |
18 | );
19 | const root = await findByTestId(testId);
20 | expect(root).toHaveClass('custom-class-name');
21 | });
22 |
23 | test('it spreads props', async () => {
24 | const role = 'link';
25 |
26 | const { findByTestId } = renderWithTheme(
27 |
28 | );
29 | const root = await findByTestId(testId);
30 | expect(root.getAttribute('role')).toBe(role);
31 | });
32 |
33 | test('it renders children', async () => {
34 | const testId = 'children';
35 | const { findByTestId } = renderWithTheme(
36 |
37 |
38 |
39 | );
40 |
41 | const children = await findByTestId(testId);
42 | expect(children).toBeInTheDocument();
43 | });
44 |
--------------------------------------------------------------------------------
/src/components/IconTile/IconTileHero.tsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx';
2 | import * as React from 'react';
3 | import { makeStyles } from '../../styles/index';
4 | import { GetClasses, StandardProps } from '../../typeUtils';
5 |
6 | export const IconTileHeroStylesKey = 'ChromaIconTileHero';
7 |
8 | export const useStyles = makeStyles(
9 | (theme) => ({
10 | root: {
11 | position: 'relative',
12 | display: 'flex',
13 | flexDirection: 'column',
14 | alignItems: 'center',
15 | width: '100%',
16 | height: '43%',
17 | },
18 | background: ({
19 | backgroundUrl,
20 | backgroundColor = theme.palette.graphite[100],
21 | }: IconTileHeroOwnProps) => ({
22 | background: `url(${backgroundUrl}), ${backgroundColor}`,
23 | width: '100%',
24 | height: '100%',
25 | backgroundRepeat: 'no-repeat',
26 | backgroundPosition: 'right top',
27 | backgroundSize: 'cover',
28 | }),
29 | }),
30 | { name: IconTileHeroStylesKey }
31 | );
32 |
33 | export type IconTileHeroClasses = GetClasses;
34 |
35 | export interface IconTileHeroOwnProps {
36 | backgroundColor?: string;
37 | backgroundUrl?: string;
38 | children?: React.ReactNode;
39 | }
40 |
41 | export interface IconTileHeroProps
42 | extends IconTileHeroOwnProps,
43 | StandardProps {}
44 |
45 | export const IconTileHero = React.forwardRef(
46 | (props, ref) => {
47 | const {
48 | children,
49 | className,
50 | // remove unsafeRootProps
51 | backgroundColor,
52 | backgroundUrl,
53 | classes: additionalClasses,
54 | ...rootProps
55 | } = props;
56 | const classes = useStyles(props);
57 |
58 | return (
59 |
60 | {!!backgroundUrl &&
}
61 | {children}
62 |
63 | );
64 | }
65 | );
66 |
--------------------------------------------------------------------------------
/src/components/IconTile/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | IconTile,
3 | IconTileClasses,
4 | IconTileProps,
5 | IconTileStylesKey,
6 | } from './IconTile';
7 |
8 | export {
9 | IconTileBadge,
10 | IconTileBadgeClasses,
11 | IconTileBadgeProps,
12 | IconTileBadgeStylesKey,
13 | } from './IconTileBadge';
14 |
15 | export {
16 | IconTileContent,
17 | IconTileContentClasses,
18 | IconTileContentProps,
19 | IconTileContentStylesKey,
20 | } from './IconTileContent';
21 |
22 | export {
23 | IconTileHero,
24 | IconTileHeroClasses,
25 | IconTileHeroProps,
26 | IconTileHeroStylesKey,
27 | } from './IconTileHero';
28 |
--------------------------------------------------------------------------------
/src/components/InfiniteScroll/InfiniteScroll.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StoryObj, StoryFn, Meta } from '@storybook/react';
3 |
4 | import { InfiniteScroll } from './InfiniteScroll';
5 |
6 | const meta: Meta = {
7 | component: InfiniteScroll,
8 | decorators: [
9 | (story) => (
10 |
18 |
24 | {story()}
25 |
26 |
27 | ),
28 | ],
29 | };
30 | export default meta;
31 | type Story = StoryObj;
32 |
33 | const Template: StoryFn = (args) => {
34 | let i = 0;
35 | const getPage = () => new Array(30).fill(null).map(() => i++);
36 | const [items, setItems] = React.useState(getPage());
37 | const [loading, setLoading] = React.useState(false);
38 |
39 | const loadMore = async () => {
40 | setLoading(true);
41 | setItems(items.concat(getPage()));
42 | setLoading(false);
43 | };
44 | return (
45 |
46 | {items.map((item) => (
47 |
48 | {item}
49 |
50 | ))}
51 |
52 | );
53 | };
54 |
55 | export const Default: Story = {
56 | render: Template,
57 | args: {
58 | scrollContainer: 'parent',
59 | hasNextPage: true,
60 | },
61 | };
62 |
--------------------------------------------------------------------------------
/src/components/InfiniteScroll/InfiniteScroll.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import useInfiniteScroll, {
4 | InfiniteScrollProps,
5 | } from '../../hooks/events/useInfiniteScroll';
6 |
7 | export interface Props extends InfiniteScrollProps {
8 | className?: string;
9 | children: any;
10 | }
11 |
12 | /**
13 | * The InfiniteScroll component provides endless scroll behavior. It is a thin
14 | * wrapper with hooks that detect if the element is in view, whether to fetch more
15 | * results based on position offset relative to the parent, etc. You will need to
16 | * toggle loading on and off to load more results
17 | *
18 | * ### Links
19 | *
20 | * - [Component Source](https://github.com/lifeomic/chroma-react/blob/master/src/components/InfiniteScroll/InfiniteScroll.tsx)
21 | * - [Story Source](https://github.com/lifeomic/chroma-react/blob/master/stories/components/InfiniteScroll/InfiniteScroll.stories.tsx)
22 | */
23 | export const InfiniteScroll = ({ children, className, ...props }: Props) => {
24 | const infiniteRef = useInfiniteScroll({
25 | ...props,
26 | });
27 |
28 | return (
29 |
30 | {children}
31 |
32 | );
33 | };
34 |
--------------------------------------------------------------------------------
/src/components/InfiniteScroll/index.ts:
--------------------------------------------------------------------------------
1 | export { InfiniteScroll } from './InfiniteScroll';
2 |
--------------------------------------------------------------------------------
/src/components/KeymapHelp/KeymapHelp.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StoryObj, Meta } from '@storybook/react';
3 |
4 | import { KeymapHelp } from './KeymapHelp';
5 |
6 | const meta: Meta = {
7 | component: KeymapHelp,
8 | decorators: [
9 | (story) => (
10 |
16 |
Open with shift + ?
17 | {story()}
18 |
19 | ),
20 | ],
21 | };
22 | export default meta;
23 | type Story = StoryObj;
24 |
25 | export const Default: Story = {};
26 |
27 | export const AdditionalKeys: Story = {
28 | args: {
29 | keyMapDocs: [
30 | {
31 | sequences: ['ctrl', 'alt', 'delete'],
32 | description: 'Bring up the help menu',
33 | },
34 | ],
35 | },
36 | };
37 |
--------------------------------------------------------------------------------
/src/components/KeymapHelp/KeymapHelp.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { KeymapHelp, KeymapHelpProps } from '.';
4 | import { renderWithTheme } from '../../testUtils/renderWithTheme';
5 |
6 | const LISTENER_TEST_ID = 'key-listener-test-id';
7 |
8 | const render = (props?: KeymapHelpProps) =>
9 | renderWithTheme(
10 |
11 | );
12 |
13 | it('launches the modal on Shift + ?', () => {
14 | const view = render();
15 |
16 | const listener = view.queryByTestId(LISTENER_TEST_ID);
17 |
18 | expect(listener).not.toBeNull();
19 | });
20 |
--------------------------------------------------------------------------------
/src/components/KeymapHelp/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | KeymapHelp,
3 | KeymapHelpProps,
4 | KeymapHelpStylesKey,
5 | KeymapHelpClasses,
6 | } from './KeymapHelp';
7 |
--------------------------------------------------------------------------------
/src/components/LayoutManager/LayoutManagerContext.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { canAccessLocalStorage } from './canAccessLocalStorage';
3 |
4 | export const LayoutManagerStoreKey = '@@lifeomic/store/layoutSidebarCollapsed';
5 |
6 | export interface LayoutManagerContextValue {
7 | isSidebarCollapseDisabled: boolean;
8 | isSidebarCollapsed: boolean;
9 | toggleSidebarCollapsed(): void;
10 | }
11 |
12 | export const LayoutManagerContext = React.createContext<
13 | LayoutManagerContextValue
14 | >({
15 | isSidebarCollapseDisabled: false,
16 | isSidebarCollapsed: !!(
17 | canAccessLocalStorage() &&
18 | localStorage.getItem(LayoutManagerStoreKey) === 'true'
19 | ),
20 | toggleSidebarCollapsed: () => undefined,
21 | });
22 |
23 | export const useLayoutManager = () => React.useContext(LayoutManagerContext);
24 |
--------------------------------------------------------------------------------
/src/components/LayoutManager/canAccessLocalStorage.ts:
--------------------------------------------------------------------------------
1 | export const canAccessLocalStorage = () => {
2 | try {
3 | localStorage.getItem('chroma-react-storage');
4 | return true;
5 | } catch {
6 | return false;
7 | }
8 | };
9 |
--------------------------------------------------------------------------------
/src/components/LayoutManager/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | LayoutManager,
3 | LayoutManagerClasses,
4 | LayoutManagerProps,
5 | LayoutManagerStylesKey,
6 | sideBarWidth,
7 | sideBarWidthCollapsed,
8 | } from './LayoutManager';
9 |
10 | export {
11 | LayoutManagerContext,
12 | LayoutManagerContextValue,
13 | LayoutManagerStoreKey,
14 | useLayoutManager,
15 | } from './LayoutManagerContext';
16 |
--------------------------------------------------------------------------------
/src/components/LinearProgress/LinearProgress.stories.tsx:
--------------------------------------------------------------------------------
1 | import { StoryObj, Meta } from '@storybook/react';
2 |
3 | import { LinearProgress } from './LinearProgress';
4 |
5 | const meta: Meta = {
6 | component: LinearProgress,
7 | };
8 | export default meta;
9 | type Story = StoryObj;
10 |
11 | export const Default: Story = {};
12 |
13 | export const Indeterminate: Story = {
14 | args: {
15 | variant: 'indeterminate',
16 | },
17 | };
18 |
19 | export const Value: Story = {
20 | args: {
21 | value: 50,
22 | },
23 | };
24 |
--------------------------------------------------------------------------------
/src/components/LinearProgress/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | LinearProgress,
3 | LinearProgressProps,
4 | LinearProgressClasses,
5 | LinearProgressStylesKey,
6 | } from './LinearProgress';
7 |
--------------------------------------------------------------------------------
/src/components/Link/Link.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StoryObj, Meta } from '@storybook/react';
3 |
4 | import { Link } from './Link';
5 | import { MemoryRouter } from 'react-router-dom';
6 |
7 | const meta: Meta = {
8 | component: Link,
9 | args: {
10 | to: '/',
11 | children: 'Link',
12 | },
13 | decorators: [(story) => {story()}],
14 | };
15 | export default meta;
16 | type Story = StoryObj;
17 |
18 | export const Default: Story = {};
19 |
20 | export const NewTab: Story = {
21 | args: {
22 | newTab: true,
23 | },
24 | };
25 |
26 | export const Color: Story = {
27 | args: {
28 | color: 'negative',
29 | },
30 | };
31 |
32 | export const InverseDark: Story = {
33 | parameters: {
34 | backgrounds: { default: 'dark' },
35 | },
36 | args: {
37 | color: 'inverse',
38 | },
39 | };
40 |
41 | export const InverseBlue: Story = {
42 | parameters: {
43 | backgrounds: { default: 'blue' },
44 | },
45 | args: {
46 | color: 'inverse',
47 | },
48 | };
49 |
--------------------------------------------------------------------------------
/src/components/Link/index.ts:
--------------------------------------------------------------------------------
1 | export { Link, LinkProps, LinkClasses, LinkStylesKey } from './Link';
2 |
--------------------------------------------------------------------------------
/src/components/List/ListGroupHeading.tsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx';
2 | import * as React from 'react';
3 | import { makeStyles } from '../../styles';
4 | import { GetClasses } from '../../typeUtils';
5 |
6 | export const ListGroupHeadingStylesKey = 'ChromaListGroupHeading';
7 |
8 | export const useStyles = makeStyles(
9 | (theme) => ({
10 | root: {
11 | color: theme.palette.text.hint,
12 | fontSize: theme.pxToRem(10),
13 | fontWeight: theme.typography.fontWeightBold,
14 | letterSpacing: theme.pxToRem(1),
15 | padding: theme.spacing(0.25, 0, 0.5),
16 | textTransform: 'uppercase',
17 | '&:not(:first-child)': {
18 | borderTop: `solid 1px ${theme.palette.divider}`,
19 | marginTop: theme.spacing(0.75),
20 | paddingTop: theme.spacing(1.25),
21 | },
22 | },
23 | }),
24 | { name: ListGroupHeadingStylesKey }
25 | );
26 |
27 | export type ListGroupHeadingClasses = GetClasses;
28 |
29 | export type ListGroupHeadingProps = {
30 | className?: string;
31 | children?: React.ReactNode;
32 | };
33 |
34 | export const ListGroupHeading: React.FC = ({
35 | className,
36 | children,
37 | }) => {
38 | const classes = useStyles({});
39 | return {children};
40 | };
41 |
--------------------------------------------------------------------------------
/src/components/List/index.ts:
--------------------------------------------------------------------------------
1 | export { List, ListClasses, ListProps, ListStylesKey } from './List';
2 |
3 | export {
4 | ListItem,
5 | ListItemClasses,
6 | ListItemProps,
7 | ListItemStylesKey,
8 | } from './ListItem';
9 |
10 | export {
11 | ListGroupHeading,
12 | ListGroupHeadingClasses,
13 | ListGroupHeadingProps,
14 | ListGroupHeadingStylesKey,
15 | } from './ListGroupHeading';
16 |
--------------------------------------------------------------------------------
/src/components/Menu/Menu.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StoryObj, Meta } from '@storybook/react';
3 |
4 | import { Menu } from './Menu';
5 | import { IconButton } from '../IconButton';
6 | import { MoreHorizontal } from '@lifeomic/chromicons';
7 | import { MenuItem } from './MenuItem';
8 | import { MenuGroupHeading } from './MenuGroupHeading';
9 |
10 | const meta: Meta = {
11 | component: Menu,
12 | args: {
13 | 'aria-label': 'Chroma menu options',
14 | anchorElement: (
15 |
16 | ),
17 | items: [
18 | ,
19 | ,
20 | ,
21 | ,
22 | ],
23 | },
24 | decorators: [(story) => {story()}
],
25 | };
26 | export default meta;
27 | type Story = StoryObj;
28 |
29 | export const Default: Story = {};
30 |
31 | export const GroupHeadings: Story = {
32 | args: {
33 | items: [
34 | First Section,
35 | ,
36 | ,
37 | Second Section,
38 | ,
39 | ,
40 | ],
41 | },
42 | };
43 |
44 | export const Title: Story = {
45 | args: {
46 | title: 'Title',
47 | },
48 | };
49 |
50 | export const Placement: Story = {
51 | args: {
52 | placement: 'top-end',
53 | },
54 | };
55 |
56 | export const Gutter: Story = {
57 | args: {
58 | gutter: 50,
59 | },
60 | };
61 |
62 | export const UsePortal: Story = {
63 | args: {
64 | usePortal: true,
65 | },
66 | };
67 |
--------------------------------------------------------------------------------
/src/components/Menu/MenuButton.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import {
3 | IconComponent,
4 | testId as iconComponentTestId,
5 | } from '../../testUtils/IconComponent';
6 | import { renderWithTheme } from '../../testUtils/renderWithTheme';
7 | import { MenuButton, MenuButtonProps } from './index';
8 |
9 | const testId = 'MenuButton';
10 |
11 | const getBaseProps = (): MenuButtonProps => ({});
12 |
13 | test('it renders a Button', async () => {
14 | const props = getBaseProps();
15 | const { findByTestId } = renderWithTheme(
16 |
17 | MenuButton
18 |
19 | );
20 | const root = await findByTestId(testId);
21 | expect(root).toBeInTheDocument();
22 | });
23 |
24 | test('it renders a trailing icon', async () => {
25 | const props = getBaseProps();
26 | const { findByTestId } = renderWithTheme(
27 |
28 | MenuButton
29 |
30 | );
31 | const trailingIcon = await findByTestId(iconComponentTestId);
32 | expect(trailingIcon).toBeInTheDocument();
33 | });
34 |
--------------------------------------------------------------------------------
/src/components/Menu/MenuButton.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { makeStyles } from '../../styles';
3 | import { GetClasses } from '../../typeUtils';
4 | import { Button, ButtonProps } from '../Button';
5 |
6 | export const MenuButtonStylesKey = 'ChromaMenuButton';
7 |
8 | export const useStyles = makeStyles(
9 | (theme) => ({
10 | trailingIcon: {
11 | width: theme.spacing(2),
12 | height: theme.spacing(2),
13 | marginLeft: theme.spacing(1),
14 | verticalAlign: 'middle',
15 | position: 'relative',
16 | top: theme.pxToRem(-1),
17 | },
18 | }),
19 | { name: MenuButtonStylesKey }
20 | );
21 |
22 | export type MenuButtonClasses = GetClasses;
23 |
24 | export interface MenuButtonProps extends ButtonProps {
25 | children?: React.ReactNode;
26 | trailingIcon?: React.MemoExoticComponent<
27 | (props: React.SVGProps) => JSX.Element
28 | >;
29 | }
30 |
31 | /** @deprecated Please use `trailingIcon` from Button instead. This will be removed in a future release */
32 | export const MenuButton = React.forwardRef(
33 | ({ children, trailingIcon: TrailingIcon, ...rootProps }, ref) => {
34 | const classes = useStyles({});
35 | return (
36 |
46 | );
47 | }
48 | );
49 |
--------------------------------------------------------------------------------
/src/components/Menu/MenuGroupHeading.tsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx';
2 | import * as React from 'react';
3 | import { makeStyles } from '../../styles';
4 | import { GetClasses } from '../../typeUtils';
5 |
6 | export const MenuGroupHeadingStylesKey = 'ChromaMenuGroupHeading';
7 |
8 | export const useStyles = makeStyles(
9 | (theme) => ({
10 | root: {
11 | fontSize: theme.pxToRem(10),
12 | fontWeight: theme.typography.fontWeightBold,
13 | letterSpacing: theme.pxToRem(1),
14 | padding: theme.spacing(0.25, 2, 0.5),
15 | color: theme.palette.text.hint,
16 | textTransform: 'uppercase',
17 | '&:not(:first-child)': {
18 | marginTop: theme.spacing(0.75),
19 | paddingTop: theme.spacing(1.25),
20 | borderTop: `solid 1px ${theme.palette.divider}`,
21 | },
22 | },
23 | }),
24 | { name: MenuGroupHeadingStylesKey }
25 | );
26 |
27 | export type MenuGroupHeadingClasses = GetClasses;
28 |
29 | export type MenuGroupHeadingProps = {
30 | className?: string;
31 | children?: React.ReactNode;
32 | };
33 |
34 | export const MenuGroupHeading: React.FC = ({
35 | className,
36 | children,
37 | }) => {
38 | const classes = useStyles({});
39 | return {children}
;
40 | };
41 |
--------------------------------------------------------------------------------
/src/components/Menu/MenuItem.stories.tsx:
--------------------------------------------------------------------------------
1 | import { StoryObj, Meta } from '@storybook/react';
2 |
3 | import { MenuItem } from './MenuItem';
4 | import { HelpCircle } from '@lifeomic/chromicons';
5 |
6 | const meta: Meta = {
7 | component: MenuItem,
8 | args: {
9 | text: 'Menu Item',
10 | },
11 | argTypes: {
12 | onClick: { action: 'clicked' },
13 | },
14 | };
15 | export default meta;
16 | type Story = StoryObj;
17 |
18 | export const Default: Story = {};
19 |
20 | export const SecondaryText: Story = {
21 | args: {
22 | secondaryText: 'This is secondary text',
23 | },
24 | };
25 |
26 | export const Icon: Story = {
27 | args: {
28 | icon: HelpCircle,
29 | },
30 | };
31 |
--------------------------------------------------------------------------------
/src/components/Menu/index.ts:
--------------------------------------------------------------------------------
1 | export { Menu, MenuClasses, MenuProps, MenuStylesKey } from './Menu';
2 |
3 | export {
4 | MenuButton,
5 | MenuButtonClasses,
6 | MenuButtonProps,
7 | MenuButtonStylesKey,
8 | } from './MenuButton';
9 |
10 | export {
11 | MenuItem,
12 | MenuItemClasses,
13 | MenuItemProps,
14 | MenuItemStylesKey,
15 | } from './MenuItem';
16 |
17 | export {
18 | MenuGroupHeading,
19 | MenuGroupHeadingClasses,
20 | MenuGroupHeadingProps,
21 | MenuGroupHeadingStylesKey,
22 | } from './MenuGroupHeading';
23 |
--------------------------------------------------------------------------------
/src/components/Modal/ModalActions.tsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx';
2 | import * as React from 'react';
3 | import { makeStyles } from '../../styles';
4 | import { GetClasses } from '../../typeUtils';
5 | import { Box } from '../Box';
6 |
7 | export const ModalActionsStylesKey = 'ChromaModalActions';
8 |
9 | export const useStyles = makeStyles(
10 | (theme) => ({
11 | root: {
12 | borderTop: `${theme.pxToRem(1)} solid ${theme.palette.divider}`,
13 | },
14 | }),
15 | { name: ModalActionsStylesKey }
16 | );
17 |
18 | export type ModalActionsClasses = GetClasses;
19 |
20 | export interface ModalActionsProps
21 | extends React.ComponentPropsWithoutRef<'div'> {
22 | children?: React.ReactNode;
23 | justify?: 'space-between' | 'flex-end';
24 | }
25 |
26 | export const ModalActions: React.FC = ({
27 | className,
28 | children,
29 | justify = 'flex-end',
30 | ...rootProps
31 | }) => {
32 | const classes = useStyles({});
33 | return (
34 |
42 | {children}
43 |
44 | );
45 | };
46 |
--------------------------------------------------------------------------------
/src/components/Modal/helpers.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | /**
4 | * @deprecated Use `composeEventHandlers(...)` instead.
5 | */
6 | /* istanbul ignore next */
7 | export function wrapEvent(theirHandler: any, ourHandler: any) {
8 | return (event: any) => {
9 | theirHandler && theirHandler(event);
10 |
11 | if (!event.defaultPrevented) {
12 | return ourHandler(event);
13 | }
14 | };
15 | }
16 |
17 | export function useForkedRef(...refs: Array) {
18 | return React.useMemo(() => {
19 | if (refs.every((ref) => ref == null)) {
20 | return null;
21 | }
22 |
23 | return (node: any) => {
24 | refs.forEach((ref) => {
25 | assignRef(ref, node);
26 | });
27 | };
28 | // eslint-disable-next-line react-hooks/exhaustive-deps
29 | }, refs);
30 | }
31 |
32 | export function assignRef(ref: any, value: any) {
33 | if (ref == null) {
34 | return;
35 | }
36 |
37 | if (typeof ref === 'function') {
38 | ref(value);
39 | } else {
40 | try {
41 | ref.current = value;
42 | } catch (error) {
43 | throw new Error(`Cannot assign value "${value}" to ref "${ref}"`);
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/components/Modal/index.ts:
--------------------------------------------------------------------------------
1 | export { Modal, ModalClasses, ModalProps, ModalStylesKey } from './Modal';
2 | export {
3 | ModalActionsClasses,
4 | ModalActionsProps,
5 | ModalActionsStylesKey,
6 | } from './ModalActions';
7 |
--------------------------------------------------------------------------------
/src/components/NumberFormat/PercentFormatField.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StoryObj, Meta } from '@storybook/react';
3 |
4 | import { PercentFormatField } from './UnitNumberFormatField';
5 | import { Apple, HelpCircle, TrendingUp } from '@lifeomic/chromicons';
6 |
7 | const meta: Meta = {
8 | title: 'Form Components/NumberFormat/PercentFormatField',
9 | component: PercentFormatField,
10 | args: {
11 | label: 'Percent Format Field',
12 | value: 50,
13 | },
14 | argTypes: {
15 | color: {
16 | control: 'radio',
17 | options: ['default', 'inverse'],
18 | },
19 | },
20 | };
21 | export default meta;
22 | type Story = StoryObj;
23 |
24 | export const Default: Story = {};
25 |
26 | export const Min: Story = {
27 | args: {
28 | min: 20,
29 | },
30 | };
31 |
32 | export const Max: Story = {
33 | args: {
34 | max: 70,
35 | },
36 | };
37 |
38 | export const Adornments: Story = {
39 | args: {
40 | startAdornment: ,
41 | endAdornment: ,
42 | },
43 | };
44 |
45 | export const TooltipMessage: Story = {
46 | parameters: {
47 | docs: {
48 | description: {
49 | story:
50 | 'Icon and Tooltip Message must be used at the same time for either of them to render.',
51 | },
52 | },
53 | },
54 | args: {
55 | icon: HelpCircle,
56 | tooltipMessage: 'Tooltip Message',
57 | },
58 | };
59 |
60 | export const RequiredAndError: Story = {
61 | args: {
62 | hasError: true,
63 | showRequiredLabel: true,
64 | errorMessage: 'This is required',
65 | },
66 | };
67 |
68 | export const InverseDark: Story = {
69 | parameters: {
70 | backgrounds: { default: 'dark' },
71 | },
72 | args: {
73 | color: 'inverse',
74 | },
75 | };
76 |
77 | export const InverseBlue: Story = {
78 | parameters: {
79 | backgrounds: { default: 'blue' },
80 | },
81 | args: {
82 | color: 'inverse',
83 | },
84 | };
85 |
--------------------------------------------------------------------------------
/src/components/NumberFormat/PhoneNumberFormatField.stories.tsx:
--------------------------------------------------------------------------------
1 | import { StoryObj, Meta } from '@storybook/react';
2 |
3 | import { PhoneNumberFormatField } from './PhoneNumberFormatField';
4 |
5 | const meta: Meta = {
6 | title: 'Form Components/NumberFormat/PhoneNumberFormatField',
7 | component: PhoneNumberFormatField,
8 | argTypes: {
9 | color: {
10 | control: 'radio',
11 | options: ['default', 'inverse'],
12 | },
13 | },
14 | };
15 | export default meta;
16 | type Story = StoryObj;
17 |
18 | export const Default: Story = {};
19 |
20 | export const Placeholder: Story = {
21 | args: {
22 | placeholder: 'placeholder',
23 | },
24 | };
25 |
26 | export const RequiredAndError: Story = {
27 | args: {
28 | hasError: true,
29 | showRequiredLabel: true,
30 | errorMessage: 'This is required',
31 | },
32 | };
33 |
34 | export const InverseDark: Story = {
35 | parameters: {
36 | backgrounds: { default: 'dark' },
37 | },
38 | args: {
39 | color: 'inverse',
40 | },
41 | };
42 |
43 | export const InverseBlue: Story = {
44 | parameters: {
45 | backgrounds: { default: 'blue' },
46 | },
47 | args: {
48 | color: 'inverse',
49 | },
50 | };
51 |
--------------------------------------------------------------------------------
/src/components/NumberFormat/PhoneNumberFormatField.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { renderWithTheme } from '../../testUtils/renderWithTheme';
3 | import { PhoneNumberFormatField } from './';
4 |
5 | test('rendering PhoneNumberFormatField renders a PhoneInput', async () => {
6 | const onChange = jest.fn();
7 | const value = '1234567890';
8 | const view = renderWithTheme(
9 |
10 | );
11 | const field = await view.getByRole('textbox');
12 | expect(field).toBeInTheDocument();
13 | expect(field).toHaveAttribute('value', value);
14 | });
15 |
--------------------------------------------------------------------------------
/src/components/NumberFormat/PriceFormatField.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StoryObj, Meta } from '@storybook/react';
3 |
4 | import { PriceFormatField } from './UnitNumberFormatField';
5 | import { Apple, HelpCircle, TrendingUp } from '@lifeomic/chromicons';
6 |
7 | const meta: Meta = {
8 | title: 'Form Components/NumberFormat/PriceFormatField',
9 | component: PriceFormatField,
10 | args: {
11 | label: 'Price Format Field',
12 | value: 50,
13 | },
14 | argTypes: {
15 | color: {
16 | control: 'radio',
17 | options: ['default', 'inverse'],
18 | },
19 | },
20 | };
21 | export default meta;
22 | type Story = StoryObj;
23 |
24 | export const Default: Story = {};
25 |
26 | export const Min: Story = {
27 | args: {
28 | min: 20,
29 | },
30 | };
31 |
32 | export const Max: Story = {
33 | args: {
34 | max: 70,
35 | },
36 | };
37 |
38 | export const Adornments: Story = {
39 | args: {
40 | startAdornment: ,
41 | endAdornment: ,
42 | },
43 | };
44 |
45 | export const TooltipMessage: Story = {
46 | parameters: {
47 | docs: {
48 | description: {
49 | story:
50 | 'Icon and Tooltip Message must be used at the same time for either of them to render.',
51 | },
52 | },
53 | },
54 | args: {
55 | icon: HelpCircle,
56 | tooltipMessage: 'Tooltip Message',
57 | },
58 | };
59 |
60 | export const RequiredAndError: Story = {
61 | args: {
62 | hasError: true,
63 | showRequiredLabel: true,
64 | errorMessage: 'This is required',
65 | },
66 | };
67 |
68 | export const InverseDark: Story = {
69 | parameters: {
70 | backgrounds: { default: 'dark' },
71 | },
72 | args: {
73 | color: 'inverse',
74 | },
75 | };
76 |
77 | export const InverseBlue: Story = {
78 | parameters: {
79 | backgrounds: { default: 'blue' },
80 | },
81 | args: {
82 | color: 'inverse',
83 | },
84 | };
85 |
--------------------------------------------------------------------------------
/src/components/NumberFormat/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | UnitNumberFormatField,
3 | UnitNumberFormatFieldProps,
4 | UnitNumberFormatFieldStylesKey,
5 | UnitNumberFormatFieldClasses,
6 | PriceFormatField,
7 | PriceFormatFieldProps,
8 | PercentFormatField,
9 | PercentFormatFieldProps,
10 | } from './UnitNumberFormatField';
11 |
12 | export {
13 | PhoneNumberFormatField,
14 | PhoneNumberFormatFieldProps,
15 | PhoneNumberFormatFieldStylesKey,
16 | PhoneNumberFormatFieldClasses,
17 | } from './PhoneNumberFormatField';
18 |
--------------------------------------------------------------------------------
/src/components/PageHeader/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | PageHeader,
3 | PageHeaderClasses,
4 | PageHeaderProps,
5 | PageHeaderStylesKey,
6 | } from './PageHeader';
7 |
--------------------------------------------------------------------------------
/src/components/PageLayout/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | PageLayout,
3 | PageLayoutClasses,
4 | PageLayoutProps,
5 | PageLayoutStylesKey,
6 | } from './PageLayout';
7 |
--------------------------------------------------------------------------------
/src/components/Paper/Paper.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StoryObj, Meta } from '@storybook/react';
3 |
4 | import { Paper } from './Paper';
5 | import { Box } from '../Box';
6 | import { Text } from '../Text';
7 |
8 | const meta: Meta = {
9 | component: Paper,
10 | };
11 | export default meta;
12 | type Story = StoryObj;
13 |
14 | export const Default: Story = {
15 | args: {
16 | children: (
17 |
18 | Content
19 |
20 | This is example content inside a Paper component
21 |
22 | Truly astounding content
23 |
24 | ),
25 | },
26 | };
27 |
--------------------------------------------------------------------------------
/src/components/Paper/Paper.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { renderWithTheme } from '../../testUtils/renderWithTheme';
3 | import { Paper } from './';
4 |
5 | const testId = 'Paper';
6 |
7 | test('it renders a Paper', async () => {
8 | const { findByTestId } = renderWithTheme();
9 | const root = await findByTestId(testId);
10 | expect(root).toHaveClass('ChromaPaper-root');
11 | });
12 |
13 | test('it renders a Paper with "padding=0"', async () => {
14 | const { findByTestId } = renderWithTheme(
15 |
16 | );
17 | const root = await findByTestId(testId);
18 | expect(root).toHaveClass('ChromaPaper-padding0');
19 | });
20 |
21 | test('it renders a Paper with "padding=1"', async () => {
22 | const { findByTestId } = renderWithTheme(
23 |
24 | );
25 | const root = await findByTestId(testId);
26 | expect(root).toHaveClass('ChromaPaper-padding1');
27 | });
28 |
29 | test('it renders a Paper with "padding=2"', async () => {
30 | const { findByTestId } = renderWithTheme(
31 |
32 | );
33 | const root = await findByTestId(testId);
34 | expect(root).toHaveClass('ChromaPaper-padding2');
35 | });
36 |
37 | test('it applies the provided className', async () => {
38 | const { findByTestId } = renderWithTheme(
39 |
40 | );
41 | const root = await findByTestId(testId);
42 | expect(root).toHaveClass('custom-class-name');
43 | });
44 |
--------------------------------------------------------------------------------
/src/components/Paper/Paper.tsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx';
2 | import * as React from 'react';
3 | import { makeStyles } from '../../styles';
4 | import { GetClasses } from '../../typeUtils';
5 | import { Box, BoxProps } from '../Box';
6 |
7 | export const PaperStylesKey = 'ChromaPaper';
8 |
9 | export const useStyles = makeStyles(
10 | (theme) => ({
11 | root: {
12 | background: theme.palette.common.white,
13 | boxShadow: theme.boxShadows.table,
14 | borderRadius: theme.pxToRem(3),
15 | },
16 | padding0: {
17 | padding: 0,
18 | },
19 | padding1: {
20 | padding: theme.spacing(1),
21 | },
22 | padding2: {
23 | padding: theme.spacing(2),
24 | },
25 | }),
26 | { name: PaperStylesKey }
27 | );
28 |
29 | export interface PaperOwnProps extends BoxProps {
30 | padding?: 0 | 1 | 2;
31 | }
32 |
33 | export type PaperClasses = GetClasses;
34 |
35 | export interface PaperProps extends PaperOwnProps {}
36 |
37 | /**
38 | * The Paper component is used as a general content wrapper.
39 | *
40 | * ### Links
41 | *
42 | * - [Component Source](https://github.com/lifeomic/chroma-react/blob/master/src/components/Paper/Paper.tsx)
43 | * - [Story Source](https://github.com/lifeomic/chroma-react/blob/master/stories/components/Paper/Paper.stories.tsx)
44 | */
45 | export const Paper = React.forwardRef(
46 | ({ children, className, padding = 2, ...rootProps }, ref) => {
47 | const classes = useStyles({});
48 |
49 | return (
50 |
64 | {children}
65 |
66 | );
67 | }
68 | );
69 |
--------------------------------------------------------------------------------
/src/components/Paper/index.ts:
--------------------------------------------------------------------------------
1 | export { Paper, PaperClasses, PaperProps, PaperStylesKey } from './Paper';
2 |
--------------------------------------------------------------------------------
/src/components/Pill/Pill.stories.tsx:
--------------------------------------------------------------------------------
1 | import { StoryObj, Meta } from '@storybook/react';
2 |
3 | import { Pill } from './Pill';
4 |
5 | const meta: Meta = {
6 | component: Pill,
7 | args: {
8 | label: 'Pill',
9 | },
10 | };
11 | export default meta;
12 | type Story = StoryObj;
13 |
14 | export const Default: Story = {};
15 |
16 | export const Color: Story = {
17 | args: {
18 | color: 'negative',
19 | },
20 | };
21 |
22 | export const Variant: Story = {
23 | args: {
24 | variant: 'highlight',
25 | },
26 | };
27 |
--------------------------------------------------------------------------------
/src/components/Pill/index.ts:
--------------------------------------------------------------------------------
1 | export { Pill, PillClasses, PillProps, PillStylesKey } from './Pill';
2 |
--------------------------------------------------------------------------------
/src/components/Popover/PopoverActions.tsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx';
2 | import * as React from 'react';
3 | import { makeStyles } from '../../styles';
4 | import { GetClasses } from '../../typeUtils';
5 |
6 | export const PopoverActionsStylesKey = 'ChromaPopoverActions';
7 |
8 | export const useStyles = makeStyles(
9 | (theme) => ({
10 | root: {
11 | borderTop: `1px solid ${theme.palette.divider}`,
12 | display: 'flex',
13 | padding: theme.spacing(1.75, 2),
14 | '& > *:not(:last-child)': {
15 | marginRight: theme.spacing(2),
16 | },
17 | },
18 | justifyStart: {
19 | justifyContent: 'flex-start',
20 | },
21 | justifyEnd: {
22 | justifyContent: 'flex-end',
23 | },
24 | justifyCenter: {
25 | justifyContent: 'center',
26 | },
27 | }),
28 | { name: PopoverActionsStylesKey }
29 | );
30 |
31 | export interface PopoverActionsOwnProps
32 | extends React.DetailedHTMLProps<
33 | React.HTMLAttributes,
34 | HTMLDivElement
35 | > {
36 | children?: React.ReactNode;
37 | className?: string;
38 | justify?: 'flex-start' | 'center' | 'flex-end';
39 | }
40 |
41 | export type PopoverActionsClasses = GetClasses;
42 |
43 | export interface PopoverActionsProps extends PopoverActionsOwnProps {}
44 |
45 | export const PopoverActions = React.forwardRef<
46 | HTMLDivElement,
47 | PopoverActionsProps
48 | >(({ children, className, justify = 'flex-end', ...rootProps }, ref) => {
49 | const classes = useStyles({});
50 |
51 | return (
52 |
65 | {children}
66 |
67 | );
68 | });
69 |
--------------------------------------------------------------------------------
/src/components/Popover/PopoverContent.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { renderWithTheme } from '../../testUtils/renderWithTheme';
3 | import { PopoverContent } from '../Popover';
4 |
5 | const testId = 'PopoverContent';
6 |
7 | test('it renders PopoverContent', async () => {
8 | const { findByTestId } = renderWithTheme(
9 |
10 | );
11 | const root = await findByTestId(testId);
12 | expect(root).toBeInTheDocument();
13 | });
14 |
15 | test('it renders children', async () => {
16 | const { findByTestId } = renderWithTheme(
17 |
18 |
19 |
20 | );
21 |
22 | const children = await findByTestId('children');
23 | expect(children).toBeInTheDocument();
24 | });
25 |
--------------------------------------------------------------------------------
/src/components/Popover/PopoverContent.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { makeStyles } from '../../styles';
3 | import { GetClasses } from '../../typeUtils';
4 |
5 | export const PopoverContentStylesKey = 'ChromaPopoverContent';
6 |
7 | export const useStyles = makeStyles(
8 | (theme) => ({
9 | root: {
10 | padding: theme.spacing(1.25, 2.5),
11 | },
12 | }),
13 | { name: PopoverContentStylesKey }
14 | );
15 |
16 | export type PopoverContentClasses = GetClasses;
17 |
18 | export interface PopoverContentProps
19 | extends React.DetailedHTMLProps<
20 | React.HTMLAttributes,
21 | HTMLDivElement
22 | > {
23 | children?: React.ReactNode;
24 | }
25 |
26 | export const PopoverContent = React.forwardRef<
27 | HTMLDivElement,
28 | PopoverContentProps
29 | >(({ children, ...rootProps }, ref) => {
30 | const classes = useStyles({});
31 |
32 | return (
33 |
34 | {children}
35 |
36 | );
37 | });
38 |
--------------------------------------------------------------------------------
/src/components/Popover/PopoverList.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { renderWithTheme } from '../../testUtils/renderWithTheme';
3 | import { PopoverList } from '../Popover';
4 |
5 | const testId = 'PopoverList';
6 |
7 | test('it renders a PopoverList', async () => {
8 | const { findByTestId } = renderWithTheme(
9 |
10 | );
11 | const root = await findByTestId(testId);
12 | expect(root).toBeInTheDocument();
13 | expect(root.getAttribute('tabIndex')).toEqual('0');
14 | expect(root.nodeName).toEqual('UL');
15 | });
16 |
17 | test('it renders children', async () => {
18 | const { findByTestId } = renderWithTheme(
19 |
20 |
21 |
22 | );
23 |
24 | const children = await findByTestId('children');
25 | expect(children).toBeInTheDocument();
26 | });
27 |
--------------------------------------------------------------------------------
/src/components/Popover/PopoverList.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { makeStyles } from '../../styles';
3 | import { GetClasses } from '../../typeUtils';
4 |
5 | export const PopoverListStylesKey = 'ChromaPopoverList';
6 |
7 | export const useStyles = makeStyles(
8 | (theme) => ({
9 | root: {
10 | listStyle: 'none',
11 | padding: 0,
12 | margin: 0,
13 | paddingTop: theme.spacing(2),
14 | paddingBottom: theme.spacing(2),
15 | maxHeight: theme.pxToRem(432),
16 | overflowY: 'auto',
17 | '&:focus': {
18 | outline: 'none',
19 | },
20 | },
21 | }),
22 | { name: PopoverListStylesKey }
23 | );
24 |
25 | export type PopoverListClasses = GetClasses;
26 |
27 | export interface PopoverListProps
28 | extends React.DetailedHTMLProps<
29 | React.HTMLAttributes,
30 | HTMLUListElement
31 | > {
32 | children?: React.ReactNode;
33 | }
34 |
35 | export const PopoverList = React.forwardRef(
36 | ({ children, ...rootProps }, ref) => {
37 | const classes = useStyles({});
38 |
39 | return (
40 |
43 | );
44 | }
45 | );
46 |
--------------------------------------------------------------------------------
/src/components/Popover/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | Popover,
3 | PopoverClasses,
4 | PopoverProps,
5 | PopoverStylesKey,
6 | PopoverRenderProps,
7 | } from './Popover';
8 |
9 | export {
10 | PopoverActions,
11 | PopoverActionsClasses,
12 | PopoverActionsProps,
13 | PopoverActionsStylesKey,
14 | } from './PopoverActions';
15 |
16 | export {
17 | PopoverContent,
18 | PopoverContentClasses,
19 | PopoverContentProps,
20 | PopoverContentStylesKey,
21 | } from './PopoverContent';
22 |
23 | export {
24 | PopoverItem,
25 | PopoverItemClasses,
26 | PopoverItemProps,
27 | PopoverItemStylesKey,
28 | } from './PopoverItem';
29 |
30 | export {
31 | PopoverList,
32 | PopoverListClasses,
33 | PopoverListProps,
34 | PopoverListStylesKey,
35 | } from './PopoverList';
36 |
--------------------------------------------------------------------------------
/src/components/PrimaryNavigation/PrimaryNavigationItem.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { MemoryRouter } from 'react-router-dom';
3 | import { IconComponent } from '../../testUtils/IconComponent';
4 | import { renderWithTheme } from '../../testUtils/renderWithTheme';
5 | import { PrimaryNavigationItem, PrimaryNavigationItemProps } from './';
6 |
7 | const testId = 'PrimaryNavigationExpansionItem';
8 |
9 | const getBaseProps = (): PrimaryNavigationItemProps => ({
10 | to: 'to',
11 | label: 'label',
12 | icon: ,
13 | });
14 |
15 | test('it renders a NavigationItem', async () => {
16 | const props = getBaseProps();
17 | const { findByTestId } = renderWithTheme(
18 |
19 |
20 |
21 | );
22 | const root = await findByTestId(testId);
23 | expect(root).toBeInTheDocument();
24 | expect(root.nodeName).toEqual('LI');
25 | });
26 |
27 | test('it renders the label', async () => {
28 | const props = getBaseProps();
29 | const { findByText } = renderWithTheme(
30 |
31 |
32 |
33 | );
34 | const label = await findByText(/label/);
35 | expect(label).toBeInTheDocument();
36 | });
37 |
38 | test('it renders the beta badge', async () => {
39 | const props = getBaseProps();
40 | const { findByText } = renderWithTheme(
41 |
42 |
48 |
49 | );
50 | const beta = await findByText(/beta/);
51 | expect(beta).toBeInTheDocument();
52 | });
53 |
--------------------------------------------------------------------------------
/src/components/PrimaryNavigation/_private/NavOrExternalLink.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { NavLink } from 'react-router-dom';
3 |
4 | export const NavOrExternalLink: React.FC = ({
5 | to,
6 | children,
7 | className,
8 | ...props
9 | }) => {
10 | if (to.match(/^https?:/)) {
11 | return (
12 |
18 | {children}
19 |
20 | );
21 | }
22 | return (
23 |
24 | {children}
25 |
26 | );
27 | };
28 |
--------------------------------------------------------------------------------
/src/components/PrimaryNavigation/_private/common.ts:
--------------------------------------------------------------------------------
1 | import { TargetAndTransition } from 'framer-motion';
2 |
3 | export const inDuration = 0.25;
4 | export const outDuration = 0.15;
5 |
6 | export const inDurationSecondary = 0.35;
7 | export const outDurationSecondary = 0.35;
8 | export const delaySecondary = 0.225;
9 |
10 | export const BETA_INITIAL_TRANSITION = {
11 | opacity: 0,
12 | };
13 |
14 | export const BETA_ANIMATE_TRANSITION: TargetAndTransition = {
15 | opacity: 1,
16 | transition: {
17 | duration: inDurationSecondary,
18 | delay: delaySecondary,
19 | },
20 | };
21 |
22 | export const BETA_EXIT_TRANSITION: TargetAndTransition = {
23 | opacity: 1,
24 | transition: {
25 | duration: outDurationSecondary,
26 | delay: delaySecondary,
27 | },
28 | };
29 |
30 | export const PLUS_INITIAL_TRANSITION = {
31 | opacity: 0,
32 | };
33 |
34 | export const PLUS_ANIMATE_TRANSITION: TargetAndTransition = {
35 | opacity: 1,
36 | transition: {
37 | duration: inDurationSecondary,
38 | delay: delaySecondary,
39 | },
40 | };
41 |
42 | export const PLUS_EXIT_TRANSITION: TargetAndTransition = {
43 | opacity: 0,
44 | transition: {
45 | duration: outDurationSecondary,
46 | delay: delaySecondary,
47 | },
48 | };
49 |
--------------------------------------------------------------------------------
/src/components/PrimaryNavigation/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | testIds as PrimaryNavigationTestIds,
3 | PrimaryNavigation,
4 | PrimaryNavigationClasses,
5 | PrimaryNavigationProps,
6 | PrimaryNavigationStylesKey,
7 | } from './PrimaryNavigation';
8 |
9 | export {
10 | PrimaryNavigationItem,
11 | PrimaryNavigationItemClasses,
12 | PrimaryNavigationItemProps,
13 | PrimaryNavigationItemStylesKey,
14 | } from './PrimaryNavigationItem';
15 |
16 | export {
17 | PrimaryNavigationExpansionItem,
18 | PrimaryNavigationExpansionItemClasses,
19 | PrimaryNavigationExpansionItemProps,
20 | PrimaryNavigationExpansionItemStylesKey,
21 | } from './PrimaryNavigationExpansionItem';
22 |
23 | export {
24 | PrimaryNavigationSubItem,
25 | PrimaryNavigationSubItemClasses,
26 | PrimaryNavigationSubItemProps,
27 | PrimaryNavigationSubItemStylesKey,
28 | } from './PrimaryNavigationSubItem';
29 |
--------------------------------------------------------------------------------
/src/components/Radio/Radio.stories.tsx:
--------------------------------------------------------------------------------
1 | import { StoryObj, Meta } from '@storybook/react';
2 |
3 | import { Radio } from './Radio';
4 | import { Apple } from '@lifeomic/chromicons';
5 |
6 | const meta: Meta = {
7 | title: 'Form Components/Radio/Radio',
8 | component: Radio,
9 | args: {
10 | label: 'Radio',
11 | },
12 | };
13 | export default meta;
14 | type Story = StoryObj;
15 |
16 | export const Default: Story = {};
17 |
18 | export const Icon: Story = {
19 | args: {
20 | label: 'Apples',
21 | icon: Apple,
22 | },
23 | parameters: {
24 | docs: {
25 | description: {
26 | story:
27 | 'See a list of available icons in our [Chromicons](https://lifeomic.github.io/chromicons.com/) icon set.',
28 | },
29 | },
30 | },
31 | };
32 |
33 | export const InverseDark: Story = {
34 | parameters: {
35 | backgrounds: { default: 'dark' },
36 | },
37 | args: {
38 | color: 'inverse',
39 | },
40 | };
41 |
42 | export const InverseBlue: Story = {
43 | parameters: {
44 | backgrounds: { default: 'blue' },
45 | },
46 | args: {
47 | color: 'inverse',
48 | },
49 | };
50 |
--------------------------------------------------------------------------------
/src/components/Radio/RadioGroup.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StoryFn, Meta, StoryObj } from '@storybook/react';
3 |
4 | import { RadioGroup } from './RadioGroup';
5 | import { Radio } from './Radio';
6 |
7 | const meta: Meta = {
8 | title: 'Form Components/Radio/RadioGroup',
9 | component: RadioGroup,
10 | args: {
11 | title: 'Radio Group',
12 | },
13 | argTypes: {
14 | onChange: { action: 'clicked' },
15 | },
16 | };
17 | export default meta;
18 | type Story = StoryObj;
19 |
20 | const Template: StoryFn = (args) => (
21 |
22 |
23 |
24 |
25 | );
26 |
27 | export const Default: Story = {
28 | render: Template,
29 | };
30 |
31 | export const InverseDark: Story = {
32 | render: Template,
33 | parameters: {
34 | backgrounds: { default: 'dark' },
35 | },
36 | args: {
37 | color: 'inverse',
38 | },
39 | };
40 |
41 | export const InverseBlue: Story = {
42 | render: Template,
43 | parameters: {
44 | backgrounds: { default: 'blue' },
45 | },
46 | args: {
47 | color: 'inverse',
48 | },
49 | };
50 |
--------------------------------------------------------------------------------
/src/components/Radio/RadioGroupMinimal.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StoryFn, Meta, StoryObj } from '@storybook/react';
3 |
4 | import { RadioGroupMinimal } from './RadioGroupMinimal';
5 | import { Radio } from './Radio';
6 |
7 | const meta: Meta = {
8 | title: 'Form Components/Radio/RadioGroupMinimal',
9 | component: RadioGroupMinimal,
10 | argTypes: {
11 | onChange: { action: 'clicked' },
12 | },
13 | args: {
14 | title: 'Radio Group Minimal',
15 | },
16 | };
17 | export default meta;
18 | type Story = StoryObj;
19 |
20 | const Template: StoryFn = (args) => (
21 |
22 |
23 |
24 |
25 | );
26 |
27 | export const Default: Story = {
28 | render: Template,
29 | };
30 |
31 | export const InverseDark: Story = {
32 | render: Template,
33 | parameters: {
34 | backgrounds: { default: 'dark' },
35 | },
36 | args: {
37 | color: 'inverse',
38 | },
39 | };
40 |
41 | export const InverseBlue: Story = {
42 | render: Template,
43 | parameters: {
44 | backgrounds: { default: 'blue' },
45 | },
46 | args: {
47 | color: 'inverse',
48 | },
49 | };
50 |
--------------------------------------------------------------------------------
/src/components/Radio/index.ts:
--------------------------------------------------------------------------------
1 | export { Radio, RadioClasses, RadioProps, RadioStylesKey } from './Radio';
2 | export {
3 | RadioGroup,
4 | RadioGroupClasses,
5 | RadioGroupProps,
6 | RadioGroupStylesKey,
7 | } from './RadioGroup';
8 |
9 | export {
10 | RadioGroupMinimal,
11 | RadioGroupMinimalClasses,
12 | RadioGroupMinimalProps,
13 | RadioGroupMinimalStylesKey,
14 | } from './RadioGroupMinimal';
15 |
--------------------------------------------------------------------------------
/src/components/Radio/useRadioGroup.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { RadioProps } from './Radio';
3 |
4 | interface RadioGroupState
5 | extends Pick {}
6 |
7 | export const RadioGroupContext = React.createContext({
8 | color: 'default',
9 | name: undefined,
10 | onChange: (_e: React.ChangeEvent) => undefined,
11 | value: undefined,
12 | });
13 |
14 | export const useRadioGroup = () => React.useContext(RadioGroupContext);
15 |
--------------------------------------------------------------------------------
/src/components/SearchField/SearchField.stories.tsx:
--------------------------------------------------------------------------------
1 | import { StoryObj, Meta } from '@storybook/react';
2 |
3 | import { SearchField } from './SearchField';
4 |
5 | const meta: Meta = {
6 | title: 'Form Components/SearchField',
7 | component: SearchField,
8 | args: {
9 | 'aria-label': 'Search',
10 | },
11 | };
12 | export default meta;
13 | type Story = StoryObj;
14 |
15 | export const Default: Story = {};
16 |
17 | export const InverseDark = {
18 | parameters: {
19 | backgrounds: { default: 'dark' },
20 | },
21 | args: {
22 | color: 'inverse',
23 | },
24 | };
25 |
26 | export const InverseBlue = {
27 | parameters: {
28 | backgrounds: { default: 'blue' },
29 | },
30 | args: {
31 | color: 'inverse',
32 | },
33 | };
34 |
--------------------------------------------------------------------------------
/src/components/SearchField/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | SearchField,
3 | SearchFieldClasses,
4 | SearchFieldProps,
5 | SearchFieldStylesKey,
6 | } from './SearchField';
7 |
--------------------------------------------------------------------------------
/src/components/SecondaryNavigation/SecondaryNavigation.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StoryObj, StoryFn, Meta } from '@storybook/react';
3 |
4 | import { SecondaryNavigation } from './SecondaryNavigation';
5 | import { MemoryRouter } from 'react-router-dom';
6 | import { SecondaryNavigationItem } from './SecondaryNavigationItem';
7 |
8 | const meta: Meta = {
9 | component: SecondaryNavigation,
10 | decorators: [(story) => {story()}],
11 | };
12 | export default meta;
13 | type Story = StoryObj;
14 |
15 | const Template: StoryFn = (args) => (
16 |
17 |
18 |
19 |
20 | );
21 |
22 | export const Default: Story = {
23 | render: Template,
24 | };
25 |
--------------------------------------------------------------------------------
/src/components/SecondaryNavigation/SecondaryNavigation.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { renderWithTheme } from '../../testUtils/renderWithTheme';
3 | import { SecondaryNavigation } from '../SecondaryNavigation';
4 |
5 | const testId = 'SecondaryNavigation';
6 |
7 | test('it renders a SecondaryNavigation', async () => {
8 | const { findByTestId } = renderWithTheme(
9 |
10 | );
11 | const root = await findByTestId(testId);
12 | expect(root).toBeInTheDocument();
13 | expect(root.getAttribute('role')).toEqual('navigation');
14 | expect(root.getAttribute('aria-label')).toEqual('Secondary');
15 | });
16 |
17 | test('it applies the provided className', async () => {
18 | const { findByTestId } = renderWithTheme(
19 |
20 | );
21 | const root = await findByTestId(testId);
22 | expect(root).toHaveClass('custom-class-name');
23 | });
24 |
25 | test('it applies the the "horizontal" variant', async () => {
26 | const { findByTestId } = renderWithTheme(
27 |
28 | );
29 | const root = await findByTestId(testId);
30 | expect(root).toHaveClass('ChromaSecondaryNavigation-horizontalNav');
31 | });
32 |
33 | test('it renders children', async () => {
34 | const { findByTestId } = renderWithTheme(
35 |
36 |
37 |
38 | );
39 | const child = await findByTestId(testId);
40 | expect(child).toBeInTheDocument();
41 | });
42 |
--------------------------------------------------------------------------------
/src/components/SecondaryNavigation/SecondaryNavigationItem.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StoryObj, Meta } from '@storybook/react';
3 |
4 | import { MemoryRouter } from 'react-router-dom';
5 | import { SecondaryNavigationItem } from './SecondaryNavigationItem';
6 |
7 | const meta: Meta = {
8 | component: SecondaryNavigationItem,
9 | args: {
10 | to: '/',
11 | label: 'Default',
12 | },
13 | decorators: [(story) => {story()}],
14 | };
15 | export default meta;
16 | type Story = StoryObj;
17 |
18 | export const Default: Story = {};
19 |
--------------------------------------------------------------------------------
/src/components/SecondaryNavigation/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | SecondaryNavigation,
3 | SecondaryNavigationClasses,
4 | SecondaryNavigationProps,
5 | SecondaryNavigationStylesKey,
6 | } from './SecondaryNavigation';
7 |
8 | export {
9 | SecondaryNavigationItem,
10 | SecondaryNavigationItemClasses,
11 | SecondaryNavigationItemProps,
12 | SecondaryNavigationItemStylesKey,
13 | } from './SecondaryNavigationItem';
14 |
--------------------------------------------------------------------------------
/src/components/Select/GroupHeading.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { renderWithTheme } from '../../testUtils/renderWithTheme';
3 | import { GroupHeading, GroupHeadingProps } from './index';
4 |
5 | const testId = 'GroupHeading';
6 |
7 | const getBaseProps = (): GroupHeadingProps => ({
8 | ['data-select-role']: 'heading',
9 | });
10 |
11 | test('it renders a GroupHeading', async () => {
12 | const props = getBaseProps();
13 | const text = 'a heading';
14 | const { findByTestId } = renderWithTheme(
15 |
16 | {text}
17 |
18 | );
19 |
20 | const heading = await findByTestId(testId);
21 | expect(heading).toBeInTheDocument();
22 | expect(heading).toHaveTextContent(text);
23 | });
24 |
25 | test('it applies the provided className', async () => {
26 | const props = getBaseProps();
27 | const { findByTestId } = renderWithTheme(
28 |
29 | Heading
30 |
31 | );
32 |
33 | const heading = await findByTestId(testId);
34 | expect(heading).toHaveClass('custom-class-name');
35 | });
36 |
--------------------------------------------------------------------------------
/src/components/Select/GroupHeading.tsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx';
2 | import * as React from 'react';
3 | import { makeStyles } from '../../styles';
4 | import { GetClasses } from '../../typeUtils';
5 |
6 | export const GroupHeadingStylesKey = 'ChromaSelectGroupHeading';
7 |
8 | export const useStyles = makeStyles(
9 | (theme) => ({
10 | root: {
11 | fontSize: theme.pxToRem(10),
12 | fontWeight: theme.typography.fontWeightBold,
13 | letterSpacing: theme.pxToRem(1),
14 | padding: theme.spacing(0.25, 2, 0.5),
15 | color: theme.palette.text.hint,
16 | textTransform: 'uppercase',
17 | '&:not(:first-child)': {
18 | marginTop: theme.spacing(0.75),
19 | paddingTop: theme.spacing(1.25),
20 | borderTop: `solid 1px ${theme.palette.divider}`,
21 | },
22 | },
23 | }),
24 | { name: GroupHeadingStylesKey }
25 | );
26 |
27 | export type GroupHeadingClasses = GetClasses;
28 |
29 | export interface GroupHeadingProps {
30 | className?: string;
31 | children?: React.ReactNode;
32 | ['data-select-role']: 'heading';
33 | }
34 |
35 | export const GroupHeading: React.FC = ({
36 | children,
37 | className,
38 | ['data-select-role']: dataSelectRole,
39 | ...rootProps
40 | }) => {
41 | const classes = useStyles({});
42 | return (
43 |
44 | {children}
45 |
46 | );
47 | };
48 |
--------------------------------------------------------------------------------
/src/components/Select/index.ts:
--------------------------------------------------------------------------------
1 | export { ComboBox, ComboBoxProps } from './ComboBox';
2 |
3 | export {
4 | testIds as SelectTestIds,
5 | Select,
6 | SelectClasses,
7 | SelectProps,
8 | SelectStylesKey,
9 | useStyles,
10 | } from './Select';
11 |
12 | export {
13 | checkSize,
14 | SelectOption,
15 | SelectOptionClasses,
16 | SelectOptionProps,
17 | SelectOptionStylesKey,
18 | } from './SelectOption';
19 |
20 | export {
21 | RoverOption,
22 | RoverOptionClasses,
23 | RoverOptionProps,
24 | RoverOptionStylesKey,
25 | } from './RoverOption';
26 |
27 | export {
28 | GroupHeading,
29 | GroupHeadingClasses,
30 | GroupHeadingProps,
31 | GroupHeadingStylesKey,
32 | } from './GroupHeading';
33 |
--------------------------------------------------------------------------------
/src/components/Select/useWindowSize.ts:
--------------------------------------------------------------------------------
1 | /* istanbul ignore file */
2 | // https://usehooks.com/useWindowSize/
3 | import * as React from 'react';
4 |
5 | interface Size {
6 | height?: number;
7 | width?: number;
8 | }
9 |
10 | /**
11 | * @deprecated Use `useWindowSize` from 'hooks/events/useWindowSize` instead. This function
12 | * will be removed in the next major version of Chroma.
13 | */
14 | /* istanbul ignore next */
15 | export function useWindowSize() {
16 | function getSize() {
17 | return {
18 | width: window.innerWidth,
19 | height: window.innerHeight,
20 | };
21 | }
22 |
23 | const [windowSize, setWindowSize] = React.useState(getSize);
24 |
25 | React.useEffect(() => {
26 | function handleResize() {
27 | setWindowSize(getSize());
28 | }
29 |
30 | window.addEventListener('resize', handleResize);
31 | return () => window.removeEventListener('resize', handleResize);
32 | }, []); // Empty array ensures that effect is only run on mount and unmount
33 |
34 | return windowSize;
35 | }
36 |
--------------------------------------------------------------------------------
/src/components/Skeleton/Skeleton.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StoryObj, Meta } from '@storybook/react';
3 |
4 | import { Skeleton } from './Skeleton';
5 |
6 | const meta: Meta = {
7 | component: Skeleton,
8 | args: {
9 | height: 30,
10 | width: 300,
11 | },
12 | decorators: [
13 | (story) => (
14 |
22 | {story()}
23 |
24 | ),
25 | ],
26 | };
27 | export default meta;
28 | type Story = StoryObj;
29 |
30 | export const Default: Story = {};
31 |
--------------------------------------------------------------------------------
/src/components/Skeleton/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | Skeleton,
3 | SkeletonClasses,
4 | SkeletonProps,
5 | SkeletonStylesKey,
6 | } from './Skeleton';
7 |
--------------------------------------------------------------------------------
/src/components/SlideOver/Actions.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { renderWithTheme } from '../../testUtils/renderWithTheme';
3 | import { Actions } from './index';
4 |
5 | const testId = 'Actions';
6 |
7 | test('it renders a Actions', async () => {
8 | const { findByTestId } = renderWithTheme();
9 |
10 | const root = await findByTestId(testId);
11 | expect(root).toBeInTheDocument();
12 | });
13 |
14 | test('it applies the provided className', async () => {
15 | const { findByTestId } = renderWithTheme(
16 |
17 | );
18 |
19 | const root = await findByTestId(testId);
20 | expect(root).toHaveClass('custom-class');
21 | });
22 |
--------------------------------------------------------------------------------
/src/components/SlideOver/Actions.tsx:
--------------------------------------------------------------------------------
1 | import { GetClasses } from '../../typeUtils';
2 | import { makeStyles } from '../../styles';
3 | import { Box, BoxProps } from '../Box';
4 | import * as React from 'react';
5 | import clsx from 'clsx';
6 |
7 | export const ActionsStylesKey = 'ChromaSlideOverActions';
8 |
9 | export const useStyles = makeStyles(
10 | (theme) => ({
11 | root: {
12 | borderTop: `1px solid ${theme.palette.divider}`,
13 | },
14 | }),
15 | { name: ActionsStylesKey }
16 | );
17 |
18 | export type ActionsClasses = GetClasses;
19 |
20 | export interface ActionsProps extends BoxProps {}
21 |
22 | export const Actions: React.FC = ({
23 | className,
24 | children,
25 | ...rootProps
26 | }) => {
27 | const classes = useStyles({});
28 | return (
29 |
35 | {children}
36 |
37 | );
38 | };
39 |
--------------------------------------------------------------------------------
/src/components/SlideOver/Body.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { renderWithTheme } from '../../testUtils/renderWithTheme';
3 | import { Body } from './index';
4 |
5 | const testId = 'Body';
6 |
7 | test('it renders a Body', async () => {
8 | const { findByTestId } = renderWithTheme();
9 |
10 | const root = await findByTestId(testId);
11 | expect(root).toBeInTheDocument();
12 | });
13 |
14 | test('it applies the provided className', async () => {
15 | const { findByTestId } = renderWithTheme(
16 |
17 | );
18 |
19 | const root = await findByTestId(testId);
20 | expect(root).toHaveClass('custom-class');
21 | });
22 |
23 | test('it renders the component using the provided "as"', async () => {
24 | const { findByTestId } = renderWithTheme(
25 |
26 | );
27 |
28 | const root = await findByTestId(testId);
29 | expect(root.nodeName).toEqual('SECTION');
30 | });
31 |
--------------------------------------------------------------------------------
/src/components/SlideOver/Body.tsx:
--------------------------------------------------------------------------------
1 | import { GetClasses } from '../../typeUtils';
2 | import { makeStyles } from '../../styles';
3 | import * as React from 'react';
4 | import clsx from 'clsx';
5 |
6 | export const BodyStylesKey = 'ChromaSlideOverBody';
7 |
8 | export const useStyles = makeStyles(
9 | (theme) => ({
10 | root: {
11 | display: 'flex',
12 | padding: theme.spacing(2),
13 | width: '100%',
14 | height: '100%',
15 | },
16 | }),
17 | { name: BodyStylesKey }
18 | );
19 |
20 | export type BodyClasses = GetClasses;
21 |
22 | export interface BodyProps {
23 | as?: React.ElementType;
24 | className?: string;
25 | children?: React.ReactNode;
26 | [key: string]: any;
27 | }
28 | export const Body: React.FC = ({
29 | as,
30 | className,
31 | children,
32 | ...rootProps
33 | }) => {
34 | const classes = useStyles({});
35 |
36 | const AsComponent = as || 'div';
37 |
38 | return (
39 |
40 | {children}
41 |
42 | );
43 | };
44 |
--------------------------------------------------------------------------------
/src/components/SlideOver/SlideOver.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { renderWithTheme } from '../../testUtils/renderWithTheme';
3 | import { SlideOver, SlideOverProps } from './index';
4 |
5 | const testId = 'SlideOver';
6 |
7 | const getBaseProps = (): SlideOverProps => ({
8 | isOpen: true,
9 | });
10 |
11 | test('it does not render if "isOpen" is false', () => {
12 | const { queryByTestId } = renderWithTheme(
13 |
14 | );
15 |
16 | const root = queryByTestId(testId);
17 | expect(root).not.toBeInTheDocument();
18 | });
19 |
20 | test('it renders a SlideOver', async () => {
21 | const { findByTestId } = renderWithTheme(
22 |
23 | );
24 |
25 | const root = await findByTestId(testId);
26 | expect(root).toBeInTheDocument();
27 | });
28 |
--------------------------------------------------------------------------------
/src/components/SlideOver/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | Actions,
3 | ActionsClasses,
4 | ActionsProps,
5 | ActionsStylesKey,
6 | } from './Actions';
7 | export { Body, BodyClasses, BodyProps, BodyStylesKey } from './Body';
8 | export { Header, HeaderClasses, HeaderProps, HeaderStylesKey } from './Header';
9 | export {
10 | SlideOver,
11 | SlideOverClasses,
12 | SlideOverProps,
13 | SlideOverStylesKey,
14 | } from './SlideOver';
15 |
--------------------------------------------------------------------------------
/src/components/Slider/Slider.stories.tsx:
--------------------------------------------------------------------------------
1 | import { StoryObj, Meta } from '@storybook/react';
2 |
3 | import { Slider } from './Slider';
4 |
5 | const meta: Meta = {
6 | component: Slider,
7 | args: {
8 | label: 'Slider',
9 | },
10 | };
11 | export default meta;
12 | type Story = StoryObj;
13 |
14 | export const Default: Story = {};
15 |
16 | export const LabelPlacement: Story = {
17 | args: {
18 | labelPlacement: 'bottom',
19 | },
20 | };
21 |
22 | export const HelpMessage: Story = {
23 | args: {
24 | helpMessage: 'Help Message',
25 | },
26 | };
27 |
28 | export const Value: Story = {
29 | args: {
30 | value: 40,
31 | },
32 | };
33 |
34 | export const ShowValue: Story = {
35 | args: {
36 | value: 40,
37 | showValue: true,
38 | },
39 | };
40 |
41 | export const FormatValue: Story = {
42 | args: {
43 | value: 40,
44 | formatValue: (value: number | undefined) => `${value} cm`,
45 | },
46 | };
47 |
48 | export const Disabled: Story = {
49 | args: {
50 | disabled: true,
51 | },
52 | };
53 |
54 | export const Error: Story = {
55 | args: {
56 | hasError: true,
57 | errorMessage: 'Error Message',
58 | },
59 | };
60 |
61 | export const InverseDark: Story = {
62 | parameters: {
63 | backgrounds: { default: 'dark' },
64 | },
65 | args: {
66 | value: 40,
67 | color: 'inverse',
68 | helpMessage: 'Help Message',
69 | showValue: true,
70 | },
71 | };
72 |
73 | export const InverseBlue: Story = {
74 | parameters: {
75 | backgrounds: { default: 'blue' },
76 | },
77 | args: {
78 | value: 40,
79 | color: 'inverse',
80 | helpMessage: 'Help Message',
81 | showValue: true,
82 | },
83 | };
84 |
--------------------------------------------------------------------------------
/src/components/Slider/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | Slider,
3 | SliderClasses,
4 | SliderProps,
5 | SliderStylesKey,
6 | testIds,
7 | } from './Slider';
8 |
--------------------------------------------------------------------------------
/src/components/SmallTile/SmallTile.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StoryObj, Meta } from '@storybook/react';
3 |
4 | import { SmallTile } from './SmallTile';
5 | import { SmallTileContent } from './SmallTileContent';
6 | import { SmallTileFooter } from './SmallTileFooter';
7 |
8 | const meta: Meta = {
9 | component: SmallTile,
10 | args: {
11 | children: (
12 | <>
13 |
14 |
15 | >
16 | ),
17 | },
18 | subcomponents: {
19 | SmallTileContent,
20 | SmallTileFooter,
21 | },
22 | };
23 | export default meta;
24 | type Story = StoryObj;
25 |
26 | export const Default: Story = {};
27 |
--------------------------------------------------------------------------------
/src/components/SmallTile/SmallTile.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { renderWithTheme } from '../../testUtils/renderWithTheme';
3 | import { SmallTile } from './index';
4 |
5 | const testId = 'SmallTile';
6 |
7 | test('it renders an SmallTile', async () => {
8 | const { findByTestId } = renderWithTheme();
9 | const root = await findByTestId(testId);
10 | expect(root).toBeInTheDocument();
11 | });
12 |
13 | test('it applies the provided className', async () => {
14 | const { findByTestId } = renderWithTheme(
15 |
16 | );
17 | const root = await findByTestId(testId);
18 | expect(root).toHaveClass('custom-class-name');
19 | });
20 |
21 | test('it spreads props', async () => {
22 | const role = 'link';
23 | const { findByTestId } = renderWithTheme(
24 |
25 | );
26 | const root = await findByTestId(testId);
27 | expect(root.getAttribute('role')).toBe(role);
28 | });
29 |
30 | test('it applies the proper attributes when onClick is provided', async () => {
31 | const { findByTestId } = renderWithTheme(
32 | ({})} data-testid={testId} />
33 | );
34 | const root = await findByTestId(testId);
35 | expect(root).toHaveClass('ChromaSmallTile-cursorPointer');
36 | expect(root.getAttribute('role')).toEqual('button');
37 | expect(root.getAttribute('tabIndex')).toEqual('0');
38 | });
39 |
40 | test('it renders children', async () => {
41 | const testId = 'children';
42 | const { findByTestId } = renderWithTheme(
43 |
44 |
45 |
46 | );
47 |
48 | const children = await findByTestId(testId);
49 | expect(children).toBeInTheDocument();
50 | });
51 |
--------------------------------------------------------------------------------
/src/components/SmallTile/SmallTileContent.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { renderWithTheme } from '../../testUtils/renderWithTheme';
3 | import { SmallTileContent } from './index';
4 |
5 | const testId = 'SmallTileContent';
6 |
7 | test('it renders a SmallTileContent', async () => {
8 | const { findByTestId } = renderWithTheme(
9 |
10 | );
11 | const root = await findByTestId(testId);
12 | expect(root).toBeInTheDocument();
13 | });
14 |
15 | test('it applies the provided className', async () => {
16 | const { findByTestId } = renderWithTheme(
17 |
18 | );
19 | const root = await findByTestId(testId);
20 | expect(root).toHaveClass('custom-class-name');
21 | });
22 |
23 | test('it spreads props', async () => {
24 | const role = 'link';
25 |
26 | const { findByTestId } = renderWithTheme(
27 |
28 | );
29 | const root = await findByTestId(testId);
30 | expect(root.getAttribute('role')).toBe(role);
31 | });
32 |
33 | test('it renders the provided "text"', async () => {
34 | const { findByText } = renderWithTheme(
35 |
36 | );
37 | const root = await findByText(/text/);
38 | expect(root).toBeInTheDocument();
39 | });
40 |
41 | test('it renders children', async () => {
42 | const testId = 'children';
43 | const { findByTestId } = renderWithTheme(
44 |
45 |
46 |
47 | );
48 |
49 | const children = await findByTestId(testId);
50 | expect(children).toBeInTheDocument();
51 | });
52 |
--------------------------------------------------------------------------------
/src/components/SmallTile/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | SmallTile,
3 | SmallTileClasses,
4 | SmallTileProps,
5 | SmallTileStylesKey,
6 | } from './SmallTile';
7 |
8 | export {
9 | SmallTileContent,
10 | SmallTileContentClasses,
11 | SmallTileContentProps,
12 | SmallTileContentStylesKey,
13 | } from './SmallTileContent';
14 |
15 | export {
16 | SmallTileFooter,
17 | SmallTileFooterClasses,
18 | SmallTileFooterProps,
19 | SmallTileFooterStylesKey,
20 | } from './SmallTileFooter';
21 |
--------------------------------------------------------------------------------
/src/components/Snackbar/Snackbar.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StoryObj, Meta } from '@storybook/react';
3 |
4 | import { Snackbar } from './Snackbar';
5 | import { Button } from '../Button';
6 | import { AlertTriangle } from '@lifeomic/chromicons';
7 |
8 | const meta: Meta = {
9 | component: Snackbar,
10 | args: {
11 | isOpen: true,
12 | children: (
13 | <>
14 | This is child text with a call to action
15 |
16 | >
17 | ),
18 | },
19 | decorators: [
20 | (story: Function) => (
21 |
26 | {story()}
27 |
28 | ),
29 | ],
30 | };
31 | export default meta;
32 | type Story = StoryObj;
33 |
34 | export const Default: Story = {};
35 | export const Icon: Story = {
36 | args: {
37 | icon: AlertTriangle,
38 | statusType: 'error',
39 | role: 'alert',
40 | allowDismiss: true,
41 | children: (
42 | <>
43 | Warning
44 |
47 | >
48 | ),
49 | },
50 | };
51 |
--------------------------------------------------------------------------------
/src/components/Snackbar/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | Snackbar,
3 | SnackbarClasses,
4 | SnackbarProps,
5 | SnackbarStylesKey,
6 | } from './Snackbar';
7 |
--------------------------------------------------------------------------------
/src/components/SpinButton/SpinButton.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StoryObj, StoryFn, Meta } from '@storybook/react';
3 |
4 | import { SpinButton } from './SpinButton';
5 | import { Box } from '../Box';
6 | import { TextField } from '../TextField';
7 |
8 | const meta: Meta = {
9 | component: SpinButton,
10 | argTypes: {
11 | onClick: { action: 'clicked' },
12 | },
13 | };
14 | export default meta;
15 | type Story = StoryObj;
16 |
17 | export const Default: Story = {};
18 |
19 | export const KeyboardSupport: StoryFn = () => {
20 | const [valueNow, setValueNow] = React.useState(0);
21 | return (
22 |
23 |
29 | setValueNow(valueNow - 1)}
31 | onIncrement={() => setValueNow(valueNow + 1)}
32 | />
33 |
34 | );
35 | };
36 |
--------------------------------------------------------------------------------
/src/components/SpinButton/SpinButton.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import userEvent from '@testing-library/user-event';
3 |
4 | import { renderWithTheme } from '../../testUtils/renderWithTheme';
5 | import { SpinButton } from './index';
6 |
7 | const testId = 'Button';
8 |
9 | test('it renders two buttons', async () => {
10 | const noop = () => {};
11 |
12 | const { findByTestId } = renderWithTheme(
13 |
14 | );
15 |
16 | const spinButton = await findByTestId(testId);
17 |
18 | const arrowButtons = spinButton.querySelectorAll('[type="button"]');
19 | expect(arrowButtons.length).toEqual(2);
20 | });
21 |
22 | test('support keyboard ArrowUp and ArrowDown', async () => {
23 | const onIncrement = jest.fn();
24 | const onDecrement = jest.fn();
25 |
26 | const { findByTestId } = renderWithTheme(
27 |
32 | );
33 |
34 | const spinButton = await findByTestId(testId);
35 |
36 | spinButton.focus();
37 | await userEvent.keyboard('{ArrowUp}');
38 | expect(onIncrement).toHaveBeenCalledTimes(1);
39 |
40 | spinButton.focus();
41 | await userEvent.keyboard('{ArrowDown}');
42 | expect(onDecrement).toHaveBeenCalledTimes(1);
43 |
44 | spinButton.focus();
45 | await userEvent.keyboard('{ArrowLeft}');
46 | // nothing should change
47 | expect(onIncrement).toHaveBeenCalledTimes(1);
48 | expect(onDecrement).toHaveBeenCalledTimes(1);
49 | });
50 |
--------------------------------------------------------------------------------
/src/components/SpinButton/index.tsx:
--------------------------------------------------------------------------------
1 | export { SpinButton, SpinButtonStylesKey, SpinButtonProps } from './SpinButton';
2 |
--------------------------------------------------------------------------------
/src/components/Stepper/StepConnector.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { renderWithTheme } from '../../testUtils/renderWithTheme';
3 | import { StepConnector } from './index';
4 |
5 | const testId = 'StepConnector';
6 |
7 | test('it renders a StepConnector', async () => {
8 | const { findByTestId } = renderWithTheme(
9 |
10 | );
11 | const root = await findByTestId(testId);
12 | expect(root).toBeInTheDocument();
13 | expect(root.getAttribute('role')).toEqual('presentation');
14 | expect(root).toHaveClass('ChromaStepConnector-connectorRoot');
15 | });
16 |
17 | test('it applies the class with hasSubTitle', async () => {
18 | const { findByTestId } = renderWithTheme(
19 |
20 | );
21 | const root = await findByTestId(testId);
22 | expect(root).toHaveClass('ChromaStepConnector-subTitle');
23 | });
24 |
25 | test('it applies the class with hasSubTitlePill', async () => {
26 | const { findByTestId } = renderWithTheme(
27 |
28 | );
29 | const root = await findByTestId(testId);
30 | expect(root).toHaveClass('ChromaStepConnector-subTitlePill');
31 | });
32 |
33 | test('it applies the activeLine class when "active"', async () => {
34 | const { findByTestId } = renderWithTheme(
35 |
36 | );
37 | const root = await findByTestId(testId);
38 | expect(root?.firstElementChild).toHaveClass('ChromaStepConnector-activeLine');
39 | });
40 |
41 | test('it applies the activeLine class when "completed"', async () => {
42 | const { findByTestId } = renderWithTheme(
43 |
44 | );
45 | const root = await findByTestId(testId);
46 | expect(root?.firstElementChild).toHaveClass('ChromaStepConnector-activeLine');
47 | });
48 |
--------------------------------------------------------------------------------
/src/components/Stepper/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | Stepper,
3 | StepperProps,
4 | StepperClasses,
5 | StepperStylesKey,
6 | } from './Stepper';
7 |
8 | export { Step, StepProps, StepClasses, StepStylesKey } from './Step';
9 |
10 | export {
11 | StepConnector,
12 | StepConnectorProps,
13 | StepConnectorClasses,
14 | StepConnectorStylesKey,
15 | } from './StepConnector';
16 |
--------------------------------------------------------------------------------
/src/components/TableModule/TableActionDivider.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { renderWithTheme } from '../../testUtils/renderWithTheme';
3 | import { TableActionDivider } from './TableActionDivider';
4 |
5 | test('it renders', async () => {
6 | const { findByTestId } = renderWithTheme(
7 |
8 | );
9 |
10 | const root = await findByTestId('table-action-divider');
11 | expect(root).toBeInTheDocument();
12 | });
13 |
--------------------------------------------------------------------------------
/src/components/TableModule/TableActionDivider.tsx:
--------------------------------------------------------------------------------
1 | import { Divider } from '../Divider';
2 | import { GetClasses } from '../../typeUtils';
3 | import { makeStyles } from '../../styles';
4 | import * as React from 'react';
5 | import clsx from 'clsx';
6 |
7 | export const TableActionDividerStylesKey = 'ChromaTableActionDivider';
8 |
9 | export const useStyles = makeStyles(
10 | (theme) => ({
11 | root: {
12 | height: theme.pxToRem(19),
13 | margin: `0 ${theme.pxToRem(16)} 0 0`,
14 | top: theme.pxToRem(4),
15 | position: 'relative',
16 | },
17 | }),
18 | { name: TableActionDividerStylesKey }
19 | );
20 |
21 | export type TableActionDividerClasses = GetClasses;
22 |
23 | export interface TableActionDividerProps {
24 | className?: string;
25 | }
26 |
27 | export const TableActionDivider: React.FC = ({
28 | className,
29 | ...rootProps
30 | }) => {
31 | const classes = useStyles({});
32 | return (
33 |
39 | );
40 | };
41 |
--------------------------------------------------------------------------------
/src/components/TableModule/TableModuleActions.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { renderWithTheme } from '../../testUtils/renderWithTheme';
3 | import { TableModuleActions } from './index';
4 |
5 | const testId = 'TableModuleActions';
6 |
7 | test('it renders a TableModuleActions', async () => {
8 | const { findByTestId } = renderWithTheme(
9 |
10 | content
11 |
12 | );
13 | const root = await findByTestId(testId);
14 | expect(root).toBeInTheDocument();
15 | expect(root.getAttribute('aria-label')).toEqual('Table Actions');
16 | });
17 |
18 | test('it renders a combination of anchor and button actions', async () => {
19 | const { findByTestId, findAllByTestId } = renderWithTheme(
20 |
21 |
22 | Second Element
23 | Last Element
24 | ,
25 | {}
26 | );
27 |
28 | const button = await findByTestId('button');
29 | expect(button).toBeInTheDocument;
30 |
31 | const anchors = await findAllByTestId('anchor');
32 | expect(anchors?.length).toEqual(2);
33 | expect(anchors[0]).toBeInTheDocument();
34 | expect(anchors[1]).toBeInTheDocument();
35 | });
36 |
--------------------------------------------------------------------------------
/src/components/TableModule/TableModuleActions.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { makeStyles } from '../../styles/index';
3 | import { GetClasses } from '../../typeUtils';
4 | import clsx from 'clsx';
5 |
6 | export const TableModuleActionsStylesKey = 'ChromaTableModuleActions';
7 |
8 | export const useStyles = makeStyles(
9 | (theme) => ({
10 | root: {
11 | display: 'flex',
12 | justifyContent: 'flex-end',
13 | // Handle the case where an IconButton or IconbuttonLink is in a cell
14 | // We don't want it to be too large to push out the height of the row!
15 | '& > button, & > a': {
16 | display: 'flex',
17 | height: theme.pxToRem(26),
18 | marginRight: theme.spacing(2.5),
19 | padding: 0,
20 | '& > svg': {
21 | height: theme.pxToRem(15),
22 | width: theme.pxToRem(15),
23 | },
24 | '&:last-child': {
25 | marginRight: 0,
26 | },
27 | },
28 | },
29 | }),
30 | { name: TableModuleActionsStylesKey }
31 | );
32 |
33 | export type TableModuleActionsClasses = GetClasses;
34 |
35 | export interface TableModuleActionsProps
36 | extends React.DetailedHTMLProps<
37 | React.HTMLAttributes,
38 | HTMLDivElement
39 | > {
40 | children?: React.ReactNode;
41 | }
42 |
43 | export const TableModuleActions: React.FC = ({
44 | children,
45 | className,
46 | ...rootProps
47 | }) => {
48 | const classes = useStyles({});
49 | return (
50 |
55 | {children}
56 |
57 | );
58 | };
59 |
--------------------------------------------------------------------------------
/src/components/TableModule/configSort.ts:
--------------------------------------------------------------------------------
1 | import { TableSortClickProps, TableConfiguration } from './types';
2 |
3 | const buildSortConfig = (handleSortClick: any) => {
4 | return [
5 | {
6 | header: {
7 | label: 'Description',
8 | onSort: (header: TableSortClickProps) => {
9 | handleSortClick({
10 | key: 'description',
11 | direction: header.sortDirection,
12 | });
13 | },
14 | },
15 | cell: {
16 | valuePath: 'description',
17 | },
18 | },
19 | {
20 | header: {
21 | label: 'Calories',
22 | onSort: (header: TableSortClickProps) => {
23 | handleSortClick({
24 | key: 'calories',
25 | direction: header.sortDirection,
26 | });
27 | },
28 | },
29 | cell: {
30 | valuePath: 'calories',
31 | },
32 | },
33 | {
34 | header: {
35 | label: 'Fat',
36 | onSort: (header: TableSortClickProps) => {
37 | handleSortClick({ key: 'fat', direction: header.sortDirection });
38 | },
39 | },
40 | cell: {
41 | valuePath: 'fat',
42 | },
43 | },
44 | {
45 | header: {
46 | label: 'Carbs',
47 | onSort: (header: TableSortClickProps) => {
48 | handleSortClick({ key: 'carbs', direction: header.sortDirection });
49 | },
50 | },
51 | cell: {
52 | valuePath: 'carbs',
53 | },
54 | },
55 | ] as Array;
56 | };
57 |
58 | export { buildSortConfig };
59 |
--------------------------------------------------------------------------------
/src/components/TableModule/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | TableActionDivider,
3 | TableActionDividerClasses,
4 | TableActionDividerProps,
5 | TableActionDividerStylesKey,
6 | } from './TableActionDivider';
7 |
8 | export {
9 | TableModule,
10 | TableModuleClasses,
11 | TableModuleProps,
12 | TableModuleStylesKey,
13 | testIds,
14 | } from './TableModule';
15 |
16 | export {
17 | TableConfiguration,
18 | TableHeader,
19 | TableCell,
20 | TableSortClickProps,
21 | TableSortState,
22 | RowSelectionRow,
23 | RowSelectionState,
24 | } from './types';
25 |
26 | export {
27 | TableModuleActions,
28 | TableModuleActionsClasses,
29 | TableModuleActionsProps,
30 | TableModuleActionsStylesKey,
31 | } from './TableModuleActions';
32 |
--------------------------------------------------------------------------------
/src/components/TableModule/storyData.ts:
--------------------------------------------------------------------------------
1 | export const data = [
2 | {
3 | description: 'Frozen yoghurt',
4 | calories: '159',
5 | fat: '6.0',
6 | carbs: '24',
7 | category: 'yogurt',
8 | misc: 'opulem',
9 | },
10 | {
11 | description: 'Ice cream sandwich',
12 | calories: '237',
13 | fat: '9.0',
14 | carbs: '37',
15 | category: 'ice cream',
16 | misc: 'retata',
17 | },
18 | {
19 | description: 'Eclair',
20 | calories: '262',
21 | fat: '16.0',
22 | carbs: '24',
23 | category: 'dessert',
24 | misc: 'doleam',
25 | },
26 | {
27 | description: 'Cupcake',
28 | calories: '305',
29 | fat: '3.7',
30 | carbs: '67',
31 | category: 'cake',
32 | misc: 'consecutir',
33 | },
34 | ];
35 |
--------------------------------------------------------------------------------
/src/components/TableModule/types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description A default interface for capturing the different
3 | * "sortDirection" options.
4 | */
5 | export interface TableSortDirection {
6 | sortDirection: 'asc' | 'desc' | null;
7 | }
8 |
9 | /**
10 | * @description The internal state of the table to determine how sorting
11 | * should work.
12 | */
13 | export interface TableSortState extends TableSortDirection {
14 | sortKey?: number | null;
15 | }
16 |
17 | interface TableAlignOptions {
18 | align?: 'left' | 'right';
19 | }
20 |
21 | /**
22 | * @description A public interface providing typings for a Table Header's "onSort" method.
23 | */
24 | export interface TableSortClickProps extends TableSortDirection {
25 | index: number;
26 | header: TableHeader;
27 | }
28 |
29 | export interface TableHeader extends TableAlignOptions {
30 | label?: string;
31 | content?(header: TableHeader): any;
32 | onSort?(sort: TableSortClickProps): any;
33 | className?: string;
34 | }
35 |
36 | export interface TableCell- extends TableAlignOptions {
37 | valuePath?: string;
38 | content?(row: Item, index?: number): any;
39 | className?: string;
40 | }
41 |
42 | export interface TableConfiguration
- {
43 | header: TableHeader;
44 | cell: TableCell
- ;
45 | isSticky?: boolean;
46 | }
47 |
48 | // TanStack table core APIs
49 | export type RowSelectionState = Record;
50 |
51 | export interface TableState {
52 | rowSelection: RowSelectionState;
53 | }
54 |
55 | export interface RowSelectionRow {
56 | getIsSelected: () => boolean;
57 | getCanSelect: () => boolean;
58 | toggleSelected: (value?: boolean) => void;
59 | getToggleSelectedHandler: () => (event: unknown) => void;
60 | }
61 |
--------------------------------------------------------------------------------
/src/components/Tabs/TabList.tsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx';
2 | import * as React from 'react';
3 | import { TabList as BaseTabList } from 'reakit/Tab';
4 | import { makeStyles } from '../../styles';
5 | import { GetClasses } from '../../typeUtils';
6 | import { TabsContext } from './TabsContext';
7 |
8 | export const TabListStylesKey = 'ChromaTabList';
9 |
10 | export const useStyles = makeStyles(
11 | (theme) => ({
12 | root: {
13 | display: 'flex',
14 | flexDirection: 'row',
15 | borderBottom: `1px solid ${theme.palette.divider}`,
16 | width: '100%',
17 | overflow: 'hidden',
18 | },
19 | pill: {
20 | background: theme.hexToRgba(theme.palette.graphite[900], 0.15),
21 | borderRadius: theme.pxToRem(20),
22 | border: 'solid 1px transparent',
23 | display: 'inline-flex',
24 | flexDirection: 'row',
25 | margin: 0,
26 | maxHeight: theme.pxToRem(35),
27 | overflow: 'hidden',
28 | padding: theme.spacing(0.25),
29 | width: 'auto',
30 | },
31 | fullWidth: {
32 | width: '100%',
33 | },
34 | }),
35 | { name: TabListStylesKey }
36 | );
37 |
38 | export type TabListClasses = GetClasses;
39 |
40 | export interface TabListProps {
41 | ['aria-label']: string;
42 | className?: string;
43 | }
44 |
45 | export const TabList: React.FC = ({
46 | 'aria-label': ariaLabel,
47 | className,
48 | ...rootProps
49 | }) => {
50 | const classes = useStyles({});
51 | const { variant, fullWidth, tabState } = React.useContext(TabsContext);
52 | return (
53 |
62 | );
63 | };
64 |
--------------------------------------------------------------------------------
/src/components/Tabs/TabPanel.tsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx';
2 | import * as React from 'react';
3 | import { TabPanel as BaseTabPanel } from 'reakit/Tab';
4 | import { makeStyles } from '../../styles';
5 | import { GetClasses } from '../../typeUtils';
6 | import { TabsContext } from './TabsContext';
7 | import { TabStop } from './types';
8 |
9 | export const TabPanelStylesKey = 'ChromaTabPanel';
10 |
11 | export const useStyles = makeStyles(
12 | (_theme) => ({
13 | root: {
14 | outline: 'none',
15 | },
16 | }),
17 | { name: TabPanelStylesKey }
18 | );
19 |
20 | export type TabPanelClasses = GetClasses;
21 |
22 | export interface TabPanelProps extends TabStop {
23 | className?: string;
24 | }
25 |
26 | export const TabPanel: React.FC = ({
27 | className,
28 | // Reakit's TabPanel no longer requires a "stopId"
29 | // (in fact, it breaks things), so we destructure it out
30 | // for now
31 | stopId,
32 | ...rootProps
33 | }) => {
34 | const classes = useStyles({});
35 | const { tabState } = React.useContext(TabsContext);
36 | return (
37 |
42 | );
43 | };
44 |
--------------------------------------------------------------------------------
/src/components/Tabs/TabsContext.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { TabStateReturn } from 'reakit/ts';
3 |
4 | export interface TabsState {
5 | tabState: TabStateReturn;
6 | variant: 'default' | 'pill';
7 | fullWidth?: boolean;
8 | }
9 |
10 | export const TabsContext = React.createContext({
11 | // only will happen when tabs components are used outside of the context
12 | tabState: {} as any,
13 | variant: 'default',
14 | });
15 |
--------------------------------------------------------------------------------
/src/components/Tabs/index.ts:
--------------------------------------------------------------------------------
1 | export { Tabs, TabsProps } from './Tabs';
2 |
3 | export { Tab, TabProps, TabClasses, TabStylesKey } from './Tab';
4 |
5 | export {
6 | TabList,
7 | TabListProps,
8 | TabListClasses,
9 | TabListStylesKey,
10 | } from './TabList';
11 |
12 | export {
13 | TabPanel,
14 | TabPanelProps,
15 | TabPanelClasses,
16 | TabPanelStylesKey,
17 | } from './TabPanel';
18 |
--------------------------------------------------------------------------------
/src/components/Tabs/types.ts:
--------------------------------------------------------------------------------
1 | export interface TabStop {
2 | stopId?: string;
3 | }
4 |
--------------------------------------------------------------------------------
/src/components/Text/Text.stories.tsx:
--------------------------------------------------------------------------------
1 | import { StoryObj, Meta } from '@storybook/react';
2 |
3 | import { Text } from './Text';
4 |
5 | const meta: Meta = {
6 | component: Text,
7 | args: {
8 | children: 'Text',
9 | },
10 | };
11 | export default meta;
12 | type Story = StoryObj;
13 |
14 | export const Default: Story = {};
15 |
16 | export const InverseDark: Story = {
17 | parameters: {
18 | backgrounds: { default: 'dark' },
19 | },
20 | args: {
21 | color: 'inverse',
22 | },
23 | };
24 |
25 | export const InverseBlue: Story = {
26 | parameters: {
27 | backgrounds: { default: 'blue' },
28 | },
29 | args: {
30 | color: 'inverse',
31 | },
32 | };
33 |
--------------------------------------------------------------------------------
/src/components/Text/index.ts:
--------------------------------------------------------------------------------
1 | export { Text, TextProps, TextStylesKey, TextClasses } from './Text';
2 |
--------------------------------------------------------------------------------
/src/components/TextArea/TextArea.stories.tsx:
--------------------------------------------------------------------------------
1 | import { StoryObj, Meta } from '@storybook/react';
2 |
3 | import { TextArea } from './TextArea';
4 | import { HelpCircle } from '@lifeomic/chromicons';
5 |
6 | const meta: Meta = {
7 | title: 'Form Components/TextArea',
8 | component: TextArea,
9 | args: {
10 | label: 'TextArea',
11 | },
12 | };
13 | export default meta;
14 | type Story = StoryObj;
15 |
16 | export const Default: Story = {};
17 |
18 | export const NoLabel: Story = {
19 | args: {
20 | label: '',
21 | 'aria-label': 'TextArea',
22 | },
23 | };
24 |
25 | export const SecondaryLabel: Story = {
26 | args: {
27 | secondaryLabel: 'Secondary Label',
28 | },
29 | };
30 |
31 | export const HelpMessage: Story = {
32 | args: {
33 | helpMessage: 'Help Message',
34 | },
35 | };
36 |
37 | export const Tooltip: Story = {
38 | args: {
39 | icon: HelpCircle,
40 | tooltipMessage: 'Here is descriptive text',
41 | },
42 | };
43 |
44 | export const Required: Story = {
45 | args: {
46 | showRequiredLabel: true,
47 | },
48 | };
49 |
50 | export const Error: Story = {
51 | args: {
52 | hasError: true,
53 | errorMessage: 'This is required',
54 | },
55 | };
56 |
57 | export const ReadOnly: Story = {
58 | args: {
59 | readOnly: true,
60 | },
61 | };
62 |
63 | export const InverseDark: Story = {
64 | parameters: {
65 | backgrounds: { default: 'dark' },
66 | },
67 | args: {
68 | color: 'inverse',
69 | },
70 | };
71 |
72 | export const InverseBlue: Story = {
73 | parameters: {
74 | backgrounds: { default: 'blue' },
75 | },
76 | args: {
77 | color: 'inverse',
78 | },
79 | };
80 |
--------------------------------------------------------------------------------
/src/components/TextArea/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | TextArea,
3 | TextAreaClasses,
4 | TextAreaProps,
5 | TextAreaStylesKey,
6 | } from './TextArea';
7 |
--------------------------------------------------------------------------------
/src/components/TextField/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | TextField,
3 | TextFieldClasses,
4 | TextFieldProps,
5 | TextFieldStylesKey,
6 | } from './TextField';
7 |
--------------------------------------------------------------------------------
/src/components/Toggle/Toggle.stories.tsx:
--------------------------------------------------------------------------------
1 | import { StoryObj, Meta } from '@storybook/react';
2 |
3 | import { Toggle } from './Toggle';
4 |
5 | const meta: Meta = {
6 | title: 'Form Components/Toggle',
7 | component: Toggle,
8 | args: {
9 | label: 'Toggle',
10 | },
11 | };
12 | export default meta;
13 | type Story = StoryObj;
14 |
15 | export const Default: Story = {};
16 |
17 | export const Placement: Story = {
18 | args: {
19 | placement: 'right',
20 | },
21 | };
22 |
23 | export const ShowRequiredLabel: Story = {
24 | args: {
25 | showRequiredLabel: true,
26 | },
27 | };
28 |
29 | export const Error: Story = {
30 | args: {
31 | hasError: true,
32 | errorMessage: 'Error Message',
33 | },
34 | };
35 |
36 | export const HelpMessage: Story = {
37 | args: {
38 | helpMessage: 'Error Message',
39 | },
40 | };
41 |
42 | export const InverseDark: Story = {
43 | parameters: {
44 | backgrounds: { default: 'dark' },
45 | },
46 | args: {
47 | color: 'inverse',
48 | },
49 | };
50 |
51 | export const InverseBlue: Story = {
52 | parameters: {
53 | backgrounds: { default: 'blue' },
54 | },
55 | args: {
56 | color: 'inverse',
57 | },
58 | };
59 |
--------------------------------------------------------------------------------
/src/components/Toggle/index.ts:
--------------------------------------------------------------------------------
1 | export { Toggle, ToggleClasses, ToggleProps, ToggleStylesKey } from './Toggle';
2 |
--------------------------------------------------------------------------------
/src/components/Tooltip/Tooltip.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StoryObj, Meta } from '@storybook/react';
3 |
4 | import { Tooltip } from './Tooltip';
5 | import { Button } from '../Button';
6 | import { IconButton } from '../IconButton';
7 | import { Info } from '@lifeomic/chromicons';
8 |
9 | const meta: Meta = {
10 | component: Tooltip,
11 | args: {
12 | title: 'Tooltip',
13 | children: ,
14 | },
15 | };
16 | export default meta;
17 | type Story = StoryObj;
18 |
19 | export const Default: Story = {};
20 |
21 | export const Children: Story = {
22 | args: {
23 | children: (
24 |
Other elements can be children too
25 | ),
26 | title: 'Like this paragraph',
27 | },
28 | };
29 |
30 | export const IconChild: Story = {
31 | args: {
32 | children: ,
33 | title: 'Icons are a common tooltip paradigm',
34 | },
35 | };
36 |
--------------------------------------------------------------------------------
/src/components/Tooltip/Tooltip.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { renderWithTheme } from '../../testUtils/renderWithTheme';
3 | import { Tooltip } from './index';
4 |
5 | const testId = 'Tooltip';
6 |
7 | test('it renders a hidden Tooltip', async () => {
8 | const { findByTestId } = renderWithTheme(
9 |
10 |
11 |
12 | );
13 |
14 | const root = await findByTestId(testId);
15 | expect(root.hidden).toBeTruthy();
16 | });
17 |
18 | // NOTE: We have to test it this way with "defaultVisible" because we don't
19 | // have a great way to trigger the mouse events. The react testing library
20 | // mouse*() functions don't trigger the visibility as we'd expect.
21 | test('it renders a visible Tooltip', async () => {
22 | const { findByTestId, findByText } = renderWithTheme(
23 |
24 |
25 |
26 | );
27 |
28 | const root = await findByTestId(testId);
29 | expect(root.hidden).toBeFalsy();
30 |
31 | const title = await findByText('foo bar');
32 | expect(title).toBeTruthy();
33 | expect(title.textContent).toEqual('foo bar');
34 | });
35 |
36 | test('it applies the provided className', async () => {
37 | const className = 'custom-class';
38 |
39 | const { findByTestId } = renderWithTheme(
40 |
46 |
47 |
48 | );
49 |
50 | const root = await findByTestId(testId);
51 | expect(root.classList.contains(className)).toBeTruthy();
52 | });
53 |
--------------------------------------------------------------------------------
/src/components/Tooltip/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | Tooltip,
3 | TooltipProps,
4 | TooltipClasses,
5 | TooltipStylesKey,
6 | } from './Tooltip';
7 |
--------------------------------------------------------------------------------
/src/components/_private/ConditionalWrapper.tsx:
--------------------------------------------------------------------------------
1 | export interface ConditionalWrapperProps {
2 | condition: boolean;
3 | wrapper: any;
4 | children: React.ReactNode;
5 | }
6 |
7 | export const ConditionalWrapper: React.FC = ({
8 | condition,
9 | wrapper,
10 | children,
11 | }) => (condition ? wrapper(children) : children);
12 |
--------------------------------------------------------------------------------
/src/components/_private/LinkOrExternalLink.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Link } from 'react-router-dom';
3 |
4 | /**
5 | * A private component to Chroma, used to generate an `` tag, either the
6 | * semantic, out of the box version, or a react-router-dom ``.
7 | */
8 | export const LinkOrExternalLink: React.FC = ({
9 | to,
10 | children,
11 | className,
12 | disabled,
13 | innerRef,
14 | rel,
15 | target,
16 | ...rootProps
17 | }) => {
18 | const disabledProps: React.AnchorHTMLAttributes = disabled
19 | ? { 'aria-disabled': true, role: 'button' }
20 | : {};
21 |
22 | if (to.toString().match(/^https?:/) || disabled) {
23 | const externalTarget = target || '_blank';
24 | return (
25 |
34 | {children}
35 |
36 | );
37 | }
38 | return (
39 |
47 | {children}
48 |
49 | );
50 | };
51 |
--------------------------------------------------------------------------------
/src/components/_private/UniqueId.ts:
--------------------------------------------------------------------------------
1 | function gen4Part() {
2 | return Math.random().toString(16).slice(-4);
3 | }
4 |
5 | // TODO: Replace with `useId` when React18 is adopted
6 | export function generateUniqueId(prefix: string) {
7 | return (prefix || '').concat(
8 | [
9 | gen4Part(),
10 | gen4Part(),
11 | gen4Part(),
12 | gen4Part(),
13 | gen4Part(),
14 | gen4Part(),
15 | gen4Part(),
16 | gen4Part(),
17 | ].join('')
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/_private/forms/FormErrorMessage.tsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx';
2 | import * as React from 'react';
3 | import { makeStyles } from '../../../styles';
4 | import { GetClasses } from '../../../typeUtils';
5 | import { Text } from '../../Text';
6 | import { BaseFormMessage } from './FormElementUtils';
7 |
8 | export const FormErrorMessageStylesKey = 'ChromaFormErrorMessage';
9 |
10 | const useStyles = makeStyles(
11 | (theme) => ({
12 | root: {
13 | color: theme.palette.error.main,
14 | lineHeight: 1.25,
15 | marginTop: theme.spacing(0.375),
16 | },
17 | inverse: {
18 | color: theme.palette.error[50],
19 | },
20 | }),
21 | { name: FormErrorMessageStylesKey }
22 | );
23 |
24 | export type FormErrorMessageClasses = GetClasses;
25 |
26 | export interface FormErrorMessageProps extends BaseFormMessage {}
27 |
28 | export const FormErrorMessage: React.FC = ({
29 | className,
30 | describedById,
31 | color,
32 | children,
33 | rootElementId,
34 | ...rootProps
35 | }) => {
36 | const classes = useStyles({});
37 | return (
38 |
52 | {children}
53 |
54 | );
55 | };
56 |
--------------------------------------------------------------------------------
/src/components/_private/forms/FormHelpMessage.tsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx';
2 | import * as React from 'react';
3 | import { makeStyles } from '../../../styles';
4 | import { GetClasses } from '../../../typeUtils';
5 | import { Text } from '../../Text';
6 | import { BaseFormMessage } from './FormElementUtils';
7 |
8 | export const FormHelpMessageStylesKey = 'ChromaFormHelpMessage';
9 |
10 | const useStyles = makeStyles(
11 | (theme) => ({
12 | root: {
13 | color: theme.palette.text.hint,
14 | lineHeight: 1.25,
15 | marginTop: theme.spacing(0.375),
16 | },
17 | inverse: {
18 | color: theme.palette.common.white,
19 | opacity: 0.9,
20 | },
21 | }),
22 | { name: FormHelpMessageStylesKey }
23 | );
24 |
25 | export type FormHelpMessageClasses = GetClasses;
26 |
27 | export interface FormHelpMessageProps extends BaseFormMessage {}
28 |
29 | export const FormHelpMessage: React.FC = ({
30 | className,
31 | color,
32 | children,
33 | rootElementId,
34 | describedById,
35 | ...rootProps
36 | }) => {
37 | const classes = useStyles({});
38 | return (
39 |
50 | {children}
51 |
52 | );
53 | };
54 |
--------------------------------------------------------------------------------
/src/components/_private/forms/index.ts:
--------------------------------------------------------------------------------
1 | export { FormHelpMessage } from './FormHelpMessage';
2 | export { FormErrorMessage } from './FormErrorMessage';
3 | export {
4 | buildDescribedBy,
5 | errorFor,
6 | helpFor,
7 | BaseFormElement,
8 | BaseFormElementWithNodeLabel,
9 | } from './FormElementUtils';
10 |
--------------------------------------------------------------------------------
/src/components/_private/notificationTypes.ts:
--------------------------------------------------------------------------------
1 | export type NotificationStatusType = 'info' | 'warning' | 'error' | 'success';
2 |
--------------------------------------------------------------------------------
/src/hooks/events/useWindowSize.test.tsx:
--------------------------------------------------------------------------------
1 | import { act, renderHook } from '@testing-library/react-hooks';
2 | import useWindowSize from './useWindowSize';
3 |
4 | // https://spectrum.chat/testing-library/help-react/how-to-set-window-innerwidth-to-test-mobile~70aa9572-b7cc-4397-92f5-a09d75ed24b8
5 | window.resizeTo = function resizeTo(width, height) {
6 | Object.assign(this, {
7 | innerWidth: width,
8 | innerHeight: height,
9 | outerWidth: width,
10 | outerHeight: height,
11 | }).dispatchEvent(new this.Event('resize'));
12 | };
13 |
14 | describe('useWindowSize', () => {
15 | it('should be defined', () => {
16 | expect(useWindowSize).toBeDefined();
17 | });
18 |
19 | it('should update width', () => {
20 | const hook = renderHook(() => useWindowSize());
21 | act(() => {
22 | window.resizeTo(320, 768);
23 | hook.rerender();
24 | });
25 | expect(hook.result.current.width).toBe(320);
26 | act(() => {
27 | window.resizeTo(640, 768);
28 | hook.rerender();
29 | });
30 | expect(hook.result.current.width).toBe(640);
31 | });
32 |
33 | it('should update height', () => {
34 | const hook = renderHook(() => useWindowSize());
35 | act(() => {
36 | window.resizeTo(320, 500);
37 | hook.rerender();
38 | });
39 | expect(hook.result.current.height).toBe(500);
40 | act(() => {
41 | window.resizeTo(320, 1000);
42 | hook.rerender();
43 | });
44 | expect(hook.result.current.height).toBe(1000);
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/src/hooks/events/useWindowSize.tsx:
--------------------------------------------------------------------------------
1 | import { useCallback, useEffect, useState } from 'react';
2 |
3 | function useWindowSize() {
4 | const getSize = useCallback(() => {
5 | const size = {
6 | width: window.innerWidth,
7 | height: window.innerHeight,
8 | };
9 |
10 | return size;
11 | }, []);
12 |
13 | const [size, setSize] = useState(getSize());
14 |
15 | useEffect(() => {
16 | function handleResize() {
17 | setSize(getSize());
18 | }
19 |
20 | window.addEventListener('resize', handleResize);
21 |
22 | return () => {
23 | window.removeEventListener('resize', handleResize);
24 | };
25 | }, [getSize]);
26 |
27 | return size;
28 | }
29 |
30 | export default useWindowSize;
31 |
--------------------------------------------------------------------------------
/src/hooks/utils/useInterval.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from 'react';
2 |
3 | function useInterval(callback: Function, delay: number | null) {
4 | const savedCallback = useRef();
5 |
6 | useEffect(() => {
7 | savedCallback.current = callback;
8 | }, [callback]);
9 |
10 | useEffect(() => {
11 | function tick() {
12 | savedCallback.current && savedCallback.current();
13 | }
14 |
15 | if (delay) {
16 | const id = setInterval(() => {
17 | tick();
18 | }, delay);
19 | return () => clearInterval(id);
20 | }
21 | }, [delay]);
22 | }
23 |
24 | export default useInterval;
25 |
--------------------------------------------------------------------------------
/src/jest.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lifeomic/chroma-react/7f253614eb17adef57db4c4f197017c91f40efb5/src/jest.js
--------------------------------------------------------------------------------
/src/styles/createBoxShadows.ts:
--------------------------------------------------------------------------------
1 | export interface BoxShadows {
2 | table: string;
3 | header: string;
4 | elevatedContent: string;
5 | popover: string;
6 | tooltip: string;
7 | focusVisible: string;
8 | focusVisibleInverse: string;
9 | }
10 |
11 | export type BoxShadowsOptions = Partial;
12 |
13 | export const baseBoxShadows: BoxShadows = {
14 | table: '0px 2px 8px rgba(0, 0, 0, 0.13)',
15 | header: '0px 0px 24px rgba(0, 0, 0, 0.15)',
16 | elevatedContent: '0px 10px 64px rgba(0, 0, 0, 0.15)',
17 | popover: '0px 10px 64px rgba(0, 0, 0, 0.2)',
18 | tooltip: '0px 0px 24px rgba(0, 0, 0, 0.15)',
19 | focusVisible: '0 0 0 2px rgba(0, 150, 225, .3)',
20 | focusVisibleInverse: '0 0 0 2px rgba(255, 255, 255, .3)',
21 | };
22 |
23 | export const createBoxShadows = (
24 | shadows: BoxShadowsOptions = {}
25 | ): BoxShadows => ({
26 | ...baseBoxShadows,
27 | ...shadows,
28 | });
29 |
--------------------------------------------------------------------------------
/src/styles/createPalette.ts:
--------------------------------------------------------------------------------
1 | import { Palette as MUIPalette } from '@mui/material/styles/createPalette';
2 | import deepmerge from 'deepmerge';
3 | import black from '../colors/black';
4 | import blue from '../colors/blue';
5 | import charcoal from '../colors/charcoal';
6 | import darkGraphite from '../colors/darkGraphite';
7 | import graphite from '../colors/graphite';
8 | import green from '../colors/green';
9 | import grey from '../colors/grey';
10 | import navy from '../colors/navy';
11 | import orange from '../colors/orange';
12 | import purple from '../colors/purple';
13 | import red from '../colors/red';
14 | import text from '../colors/text';
15 | import yellow from '../colors/yellow';
16 | import { DeepPartial } from '../typeUtils';
17 |
18 | const baseMuiPalette = {
19 | primary: blue,
20 | secondary: green,
21 | error: red,
22 | mode: 'light',
23 | common: {
24 | black: black.main,
25 | white: '#ffffff',
26 | },
27 | grey,
28 | text,
29 | divider: grey[400],
30 | background: {
31 | paper: grey[50],
32 | default: '#f5f8fa',
33 | },
34 | negative: {
35 | main: red.main,
36 | dark: red.dark,
37 | },
38 | positive: {
39 | main: green.main,
40 | dark: green.dark,
41 | },
42 | };
43 |
44 | const basePalette = {
45 | black,
46 | blue,
47 | charcoal,
48 | darkGraphite,
49 | graphite,
50 | green,
51 | navy,
52 | orange,
53 | purple,
54 | red,
55 | yellow,
56 | ...baseMuiPalette,
57 | };
58 |
59 | export type Palette = typeof basePalette & MUIPalette;
60 |
61 | export type PaletteOptions = DeepPartial;
62 |
63 | export const createPalette = (palette: PaletteOptions = {}): Palette => {
64 | return deepmerge(basePalette, palette, { clone: false }) as any;
65 | };
66 |
--------------------------------------------------------------------------------
/src/styles/createStyles.ts:
--------------------------------------------------------------------------------
1 | import muiCreateStyles from '@mui/styles/createStyles';
2 | import { Styles } from '@mui/styles';
3 |
4 | const createStyles = muiCreateStyles;
5 |
6 | export { Styles, createStyles };
7 |
--------------------------------------------------------------------------------
/src/styles/createTheme.test.ts:
--------------------------------------------------------------------------------
1 | import { createTheme, Theme } from './createTheme';
2 |
3 | test('default theme', () => {
4 | expect(createTheme()).toMatchSnapshot();
5 | });
6 |
7 | test('overridden theme', () => {
8 | const components = (theme: Theme) => ({
9 | MuiButton: {
10 | styleOverrides: {
11 | root: {
12 | marginLeft: theme.spacing(1),
13 | },
14 | },
15 | },
16 | ChromaAlert: {
17 | styleOverrides: {
18 | root: {
19 | marginLeft: theme.spacing(1),
20 | },
21 | },
22 | },
23 | });
24 |
25 | // Allows Chroma's default values to be overridden
26 | const breakpoints = { xs: 100, sm: 800, md: 1000, lg: 1500, xl: 2000 };
27 |
28 | expect(createTheme({ breakpoints, components })).toMatchSnapshot();
29 | });
30 |
--------------------------------------------------------------------------------
/src/styles/createZIndex.ts:
--------------------------------------------------------------------------------
1 | import { ZIndex as MUIZIndex } from '@mui/material/styles';
2 |
3 | export interface ZIndex extends MUIZIndex {
4 | byValueUpTo20: { [key: number]: number };
5 | dayPicker: number;
6 | header: number;
7 | menu: number;
8 | popover: number;
9 | select: number;
10 | slideOver: number;
11 | }
12 |
13 | export type ZIndexOptions = Partial;
14 |
15 | export const baseZIndex: ZIndex = {
16 | mobileStepper: 1000,
17 | fab: 1050,
18 | speedDial: 1050,
19 | appBar: 1100,
20 | drawer: 1200,
21 | modal: 1300,
22 | snackbar: 1400,
23 | tooltip: 1500,
24 | byValueUpTo20: {} as { [key: number]: number },
25 | dayPicker: 99999,
26 | header: 1100,
27 | menu: 40,
28 | popover: 40,
29 | select: 1350, // must be > MUI modal zIndex (1300), will be < MUI snackbar (1400) and tooltip (1500)
30 | slideOver: 100,
31 | };
32 |
33 | // populate byValueUpToX
34 | const populateByValueUpToX = (object: { [key: number]: number }, x: number) => {
35 | for (let i = 0; i <= x; i++) {
36 | object[i] = i;
37 | }
38 | };
39 |
40 | populateByValueUpToX(baseZIndex.byValueUpTo20, 20);
41 |
42 | export const createZIndex = (zIndex: ZIndexOptions = {}): ZIndex => ({
43 | ...baseZIndex,
44 | ...zIndex,
45 | });
46 |
--------------------------------------------------------------------------------
/src/styles/index.ts:
--------------------------------------------------------------------------------
1 | export { StyledEngineProvider } from '@mui/material';
2 | export { ThemeProvider } from '@mui/material/styles';
3 | export { createTheme, Theme, useTheme } from './createTheme';
4 | export { createPalette, Palette, PaletteOptions } from './createPalette';
5 | export {
6 | createTypography,
7 | FontSize,
8 | FontWeight,
9 | fontSizes,
10 | fontWeights,
11 | } from './createTypography';
12 | export { makeStyles } from './makeStyles';
13 | export { createStyles, Styles } from './createStyles';
14 | export { withStyles, WithStyles } from './withStyles';
15 | export { withTheme, WithTheme } from './withTheme';
16 | export { useMediaQuery } from './useMediaQuery';
17 |
--------------------------------------------------------------------------------
/src/styles/makeStyles.ts:
--------------------------------------------------------------------------------
1 | import {
2 | makeStyles as muiMakeStyles,
3 | Styles,
4 | WithStylesOptions,
5 | } from '@mui/styles';
6 | import { Theme } from './createTheme';
7 |
8 | export const makeStyles = <
9 | Props extends {} = {},
10 | ClassKey extends string = string
11 | >(
12 | styles: Styles,
13 | options?: WithStylesOptions
14 | ) => {
15 | return muiMakeStyles(styles, options);
16 | };
17 |
--------------------------------------------------------------------------------
/src/styles/overrides/MuiTab.ts:
--------------------------------------------------------------------------------
1 | import { ComponentsOverrides as Overrides } from '@mui/material/styles';
2 | import { Theme } from '../createTheme';
3 |
4 | export const createMuiTabOverrides = (theme: Theme): Overrides['MuiTab'] => ({
5 | root: {
6 | minHeight: theme.pxToRem(48),
7 | fontWeight: theme.typography.fontWeightRegular,
8 | fontSize: theme.typography.subtitle1.fontSize,
9 | textTransform: 'initial',
10 | transition: 'color 0.5s ease',
11 | letterSpacing: theme.pxToRem(0.1),
12 | '&:hover': {
13 | color: theme.palette.black[500],
14 | },
15 | '&:disabled': {
16 | color: theme.palette.black[400],
17 | },
18 | '&:focus': {
19 | outline: 'none',
20 | },
21 | '&$selected': {
22 | color: theme.palette.black[900],
23 | },
24 | [theme.breakpoints.up('sm')]: {
25 | fontSize: theme.typography.subtitle1.fontSize,
26 | },
27 | },
28 | textColorInherit: {
29 | color: theme.palette.black[900],
30 | opacity: 'initial',
31 | },
32 | iconWrapper: {
33 | paddingLeft: theme.spacing(2),
34 | paddingRight: theme.spacing(2),
35 | },
36 | });
37 |
--------------------------------------------------------------------------------
/src/styles/overrides/MuiTabs.ts:
--------------------------------------------------------------------------------
1 | import { ComponentsOverrides as Overrides } from '@mui/material/styles/overrides';
2 | import { Theme } from '../createTheme';
3 |
4 | export const createMuiTabsOverrides = (theme: Theme): Overrides['MuiTabs'] => ({
5 | root: {
6 | minHeight: 'initial',
7 | height: 'initial',
8 | borderBottom: `1px solid ${theme.palette.divider}`,
9 | width: '100%',
10 | },
11 | indicator: {
12 | height: theme.pxToRem(3),
13 | backgroundColor: theme.palette.primary.main,
14 | },
15 | });
16 |
--------------------------------------------------------------------------------
/src/styles/overrides/MuiTooltip.ts:
--------------------------------------------------------------------------------
1 | import { ComponentsOverrides as Overrides } from '@mui/material/styles/overrides';
2 | import { Theme } from '../createTheme';
3 | import { fontSizes } from '../createTypography';
4 |
5 | export const createMuiTooltipOverrides = (
6 | theme: Theme
7 | ): Overrides['MuiTooltip'] => ({
8 | tooltip: {
9 | backgroundColor: theme.palette.common.white,
10 | color: theme.palette.text.hint,
11 | fontSize: fontSizes.tooltip,
12 | paddingLeft: theme.spacing(1),
13 | paddingRight: theme.spacing(1),
14 | maxWidth: 500,
15 | letterSpacing: '0.021875em',
16 | boxShadow: theme.boxShadows.tooltip,
17 | },
18 | popper: {
19 | opacity: 1,
20 | },
21 | });
22 |
--------------------------------------------------------------------------------
/src/styles/overrides/index.ts:
--------------------------------------------------------------------------------
1 | import deepmerge from 'deepmerge';
2 | import { Theme } from '../createTheme';
3 | import { Components } from './ChromaComponents';
4 | import { createMuiButtonOverrides } from './MuiButton';
5 | import { createMuiTabOverrides } from './MuiTab';
6 | import { createMuiTabsOverrides } from './MuiTabs';
7 | import { createMuiTooltipOverrides } from './MuiTooltip';
8 |
9 | export interface OverridesCreator {
10 | (theme: Theme): Components;
11 | }
12 |
13 | export const createOverrides = (
14 | theme: Theme,
15 | overridesCreator?: OverridesCreator
16 | ) => {
17 | const baseOverrides: Components = {
18 | MuiButton: {
19 | defaultProps: { color: 'secondary' },
20 | styleOverrides: createMuiButtonOverrides(theme),
21 | },
22 | MuiCheckbox: { defaultProps: { color: 'secondary' } },
23 | MuiFormControl: { defaultProps: { variant: 'standard' } },
24 | MuiSelect: { defaultProps: { variant: 'standard' } },
25 | MuiTooltip: { styleOverrides: createMuiTooltipOverrides(theme) },
26 | MuiTabs: { styleOverrides: createMuiTabsOverrides(theme) },
27 | MuiTab: { styleOverrides: createMuiTabOverrides(theme) },
28 | MuiTextField: { defaultProps: { variant: 'standard' } },
29 | };
30 |
31 | if (!overridesCreator) {
32 | return baseOverrides;
33 | }
34 |
35 | return deepmerge(baseOverrides, overridesCreator(theme), { clone: false });
36 | };
37 |
--------------------------------------------------------------------------------
/src/styles/screenreaderOnly.ts:
--------------------------------------------------------------------------------
1 | import { CreateCSSProperties, PropsFunc } from '@mui/styles';
2 | import { CSSProperties } from 'react';
3 |
4 | export const screenreaderOnlyStyles:
5 | | CSSProperties
6 | | CreateCSSProperties<{}>
7 | | PropsFunc<{}, CreateCSSProperties<{}>> = {
8 | position: 'absolute',
9 | width: '1px',
10 | height: '1px',
11 | padding: 0,
12 | margin: '-1px',
13 | overflow: 'hidden',
14 | clip: 'rect(0, 0, 0, 0)',
15 | whiteSpace: 'nowrap',
16 | borderWidth: 0,
17 | };
18 |
--------------------------------------------------------------------------------
/src/styles/useMediaQuery.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | export const useMediaQuery = (query: string) => {
4 | const mediaQueryListeners = window.matchMedia(query);
5 | const [matches, setMatches] = React.useState(mediaQueryListeners.matches);
6 |
7 | React.useEffect(() => {
8 | mediaQueryListeners.addListener(() => {
9 | setMatches(mediaQueryListeners.matches);
10 | });
11 | }, [mediaQueryListeners, query]);
12 |
13 | return matches;
14 | };
15 |
--------------------------------------------------------------------------------
/src/styles/utils/colorManipulator.ts:
--------------------------------------------------------------------------------
1 | export function hexToRgba(hex: string, opacity?: number): string {
2 | let rgba = '';
3 | if (isHex(hex)) {
4 | const hexArr = sliceHex(hex);
5 | const r = hexToDec(hexArr[0]);
6 | const g = hexToDec(hexArr[1]);
7 | const b = hexToDec(hexArr[2]);
8 | if (opacity) {
9 | const percent = opacity > 1 ? '%' : '';
10 | rgba = `rgba(${r},${g},${b},${opacity}${percent})`;
11 | } else {
12 | rgba = `rgba(${r},${g},${b},1)`;
13 | }
14 | } else {
15 | throw new Error('Invalid hex code');
16 | }
17 | return rgba;
18 | }
19 |
20 | export function hexToDec(hex: string): number {
21 | if (hex.length === 2) {
22 | return parseInt(hex[0], 16) * 16 + parseInt(hex[1], 16);
23 | } else {
24 | return parseInt(hex[0], 16) * 16 + parseInt(hex[0], 16);
25 | }
26 | }
27 |
28 | export function isHex(hex: string): boolean {
29 | return /^#([A-Fa-f0-9]{3}){1,2}$/.test(hex);
30 | }
31 |
32 | export function sliceHex(hex: string): Array {
33 | const arr = [];
34 | hex = hex.slice(1); // remove #
35 | const l = hex.length;
36 | arr.push(hex.slice(0, l / 3));
37 | arr.push(hex.slice(l / 3, (l / 3) * 2));
38 | arr.push(hex.slice((l / 3) * 2, l));
39 | return arr;
40 | }
41 |
--------------------------------------------------------------------------------
/src/styles/withStyles.ts:
--------------------------------------------------------------------------------
1 | export { withStyles, WithStyles } from '@mui/styles';
2 |
--------------------------------------------------------------------------------
/src/styles/withTheme.ts:
--------------------------------------------------------------------------------
1 | import muiWithTheme, { WithTheme as MuiWithTheme } from '@mui/styles/withTheme';
2 | import { ConsistentWith } from '@mui/types';
3 | import { Theme } from './createTheme';
4 |
5 | export type WithTheme = MuiWithTheme;
6 |
7 | export const withTheme = <
8 | C extends React.ComponentType<
9 | ConsistentWith, WithTheme>
10 | >
11 | >(
12 | component: C
13 | ) => muiWithTheme(component);
14 |
--------------------------------------------------------------------------------
/src/testUtils/IconComponent.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | export const testId = 'iconComponent';
4 |
5 | const IconSvg = (props: React.SVGProps) => (
6 |
15 | );
16 |
17 | export const IconComponent: React.MemoExoticComponent<(
18 | props: React.SVGProps
19 | ) => JSX.Element> = React.memo(IconSvg);
20 |
--------------------------------------------------------------------------------
/src/testUtils/getTestProps.ts:
--------------------------------------------------------------------------------
1 | export const testIdProp = 'data-testid';
2 |
3 | export const getTestProps = (id: string) => ({
4 | [testIdProp]: id,
5 | });
6 |
--------------------------------------------------------------------------------
/src/testUtils/renderWithTheme.tsx:
--------------------------------------------------------------------------------
1 | import { Queries, render, RenderOptions } from '@testing-library/react';
2 | import * as React from 'react';
3 | import { createTheme, StyledEngineProvider, ThemeProvider } from '../styles';
4 |
5 | export const theme = createTheme();
6 |
7 | export function renderWithTheme(
8 | ui: React.ReactElement,
9 | options?: RenderOptions | Omit
10 | ) {
11 | const { rerender, ...result } = render(
12 |
13 | {ui}
14 | ,
15 | options
16 | );
17 |
18 | const wrappedRerender = (ui: React.ReactElement) =>
19 | rerender(
20 |
21 | {ui}
22 |
23 | );
24 |
25 | return {
26 | ...result,
27 | rerender: wrappedRerender,
28 | };
29 | }
30 |
--------------------------------------------------------------------------------
/src/typeUtils.ts:
--------------------------------------------------------------------------------
1 | export type DeepPartial = {
2 | [P in keyof T]?: T[P] extends Array
3 | ? Array>
4 | : T[P] extends ReadonlyArray
5 | ? ReadonlyArray>
6 | : DeepPartial;
7 | };
8 |
9 | export type GetClasses any> = keyof ReturnType;
10 |
11 | export type ClassOverrides = {
12 | classes?: { [K in ClassKey]?: string };
13 | };
14 |
15 | export type StandardProps<
16 | C,
17 | ClassKey extends string,
18 | AcceptsRef = true
19 | > = React.DetailedHTMLProps, C> &
20 | ClassOverrides & {
21 | ref?: AcceptsRef extends true
22 | ? C extends { ref?: infer RefType }
23 | ? RefType
24 | : React.Ref
25 | : never;
26 | };
27 |
--------------------------------------------------------------------------------
/src/typings/assets.d.ts:
--------------------------------------------------------------------------------
1 | declare module '@lifeomic/react-brand-icons/*';
2 |
3 | declare module '*.svg' {
4 | const content: string;
5 | export default content;
6 | }
7 |
--------------------------------------------------------------------------------
/src/utils/event-handlers.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Composes a series of `handlers` into a single handler. Subsequent handlers
3 | * will not be invoked after a handler calls `e.preventDefault()`.
4 | *
5 | * Useful for allowing external overriding of internal event handlers in
6 | * a component.
7 | *
8 | * @example
9 | * { ... }
14 | * ])
15 | * }
16 | * />
17 | */
18 | export const composeEventHandlers =
(
19 | handlers: (((e: Event) => void) | undefined)[]
20 | ) => (e: Event) => {
21 | for (const handler of handlers) {
22 | if (handler && !e.defaultPrevented) {
23 | handler(e);
24 | }
25 | }
26 | };
27 |
--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export { warning } from './warning';
2 | export * from './event-handlers';
3 |
--------------------------------------------------------------------------------
/src/utils/warning.ts:
--------------------------------------------------------------------------------
1 | // Yoinked from https://github.com/reakit/reakit/blob/master/packages/reakit-utils/src/warning.ts
2 |
3 | /**
4 | * Logs `messages` to the console using `console.warn` based on a `condition`.
5 | *
6 | * @example
7 | * import { warning } from "@lifeomic/chroma-react/utils/warning";
8 | *
9 | * warning(true, "a", "b"); // console.warn("a\nb")
10 | * warning(false, "a", "b"); // does nothing
11 | */
12 | export function warning(condition: boolean, ...messages: string[]) {
13 | if (process.env.NODE_ENV !== 'production') {
14 | if (!condition) return;
15 |
16 | const text = messages.join('\n');
17 |
18 | // eslint-disable-next-line no-console
19 | console.warn(text);
20 |
21 | // Throwing an error and catching it immediately to improve debugging
22 | // A consumer can use 'pause on caught exceptions'
23 | // https://github.com/facebook/react/issues/4216
24 | try {
25 | throw Error(text);
26 | } catch (x) {
27 | // do nothing
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/stories/_recipes/formik.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta } from '@storybook/addon-docs';
2 |
3 |
4 |
5 | # formik recipe
6 |
7 | The best way to integrate formik with our form components is to look at
8 | [formik-material-ui](https://github.com/stackworx/formik-material-ui/blob/master/packages/formik-material-ui/src/TextField.tsx).
9 |
10 | **NOTE**: If you're a LifeOmic employee, you can access these components
11 | already setup for you [here](https://github.com/lifeomic/phc-ui/tree/master/packages/phc-web-toolkit/src/components/ChromaFormik).
12 |
--------------------------------------------------------------------------------
/stories/assets/legoman.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lifeomic/chroma-react/7f253614eb17adef57db4c4f197017c91f40efb5/stories/assets/legoman.jpg
--------------------------------------------------------------------------------
/stories/styles/color/ColorValue.tsx:
--------------------------------------------------------------------------------
1 | import { getContrastRatio } from '@mui/material/styles';
2 | import * as React from 'react';
3 | import { makeStyles } from '../../../src/styles';
4 |
5 | const useStyles = makeStyles((theme) => ({
6 | root: {
7 | display: 'flex',
8 | padding: theme.spacing(2),
9 | justifyContent: 'space-between',
10 | alignItems: 'flex-end',
11 | },
12 | }));
13 |
14 | interface PaletteProps {
15 | name: string;
16 | value: string;
17 | isMain?: boolean;
18 | }
19 |
20 | export const ColorValue: React.FunctionComponent = ({
21 | name,
22 | value,
23 | isMain,
24 | }) => {
25 | const classes = useStyles({});
26 |
27 | const whiteRatio = getContrastRatio('#fff', value);
28 | const blackRatio = getContrastRatio(value, '#000');
29 | const textColor = whiteRatio > blackRatio ? '#fff' : '#000';
30 |
31 | return (
32 |
40 |
{name}
41 |
{value}
42 |
43 | );
44 | };
45 |
--------------------------------------------------------------------------------
/stories/styles/color/Palette.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Text } from '../../../src/components/Text';
3 | import { makeStyles } from '../../../src/styles';
4 | import { ColorValue } from './ColorValue';
5 |
6 | export interface PaletteItem {
7 | name: string;
8 | values: {
9 | [key: string]: string;
10 | };
11 | }
12 |
13 | const useStyles = makeStyles((theme) => ({
14 | palette: {
15 | borderRadius: theme.spacing(2),
16 | overflow: 'hidden',
17 | },
18 | }));
19 |
20 | interface PaletteProps {
21 | palette: PaletteItem;
22 | }
23 |
24 | export const Palette: React.FunctionComponent = ({ palette }) => {
25 | const classes = useStyles({});
26 | const {
27 | main,
28 | primary,
29 | light,
30 | dark,
31 | contrastText,
32 | ...colorMap
33 | } = palette.values;
34 |
35 | return (
36 |
37 |
40 |
41 | {Boolean(main) && }
42 | {Boolean(primary) && (
43 |
44 | )}
45 | {Boolean(light) && }
46 | {Boolean(dark) && }
47 | {Object.keys(colorMap).map((key) => {
48 | return ;
49 | })}
50 |
51 |
52 | );
53 | };
54 |
--------------------------------------------------------------------------------
/stories/styles/color/PaletteGrid.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Text } from '../../../src/components/Text';
3 | import { makeStyles } from '../../../src/styles';
4 | import { Palette, PaletteItem } from './Palette';
5 |
6 | const useStyles = makeStyles((theme) => ({
7 | root: {
8 | marginBottom: theme.spacing(4),
9 | },
10 | grid: {
11 | display: 'grid',
12 | gridGap: theme.spacing(2),
13 | gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))',
14 | },
15 | }));
16 |
17 | interface PaletteGridProps {
18 | palettes: PaletteItem[];
19 | title: string;
20 | }
21 |
22 | export const PaletteGrid: React.FunctionComponent = ({
23 | title,
24 | palettes,
25 | }) => {
26 | const classes = useStyles({});
27 |
28 | return (
29 |
30 | {title}
31 |
32 | {palettes.map((palette) => (
33 |
34 | ))}
35 |
36 |
37 | );
38 | };
39 |
--------------------------------------------------------------------------------
/stories/typings/assets.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.md' {
2 | const content: string;
3 | export default content;
4 | }
5 |
6 | declare module '*.jpg' {
7 | const url: string;
8 | export default url;
9 | }
10 |
11 | declare module '*.svg' {
12 | const content: string;
13 | export default content;
14 | }
15 |
16 | declare module '*.png' {
17 | const content: string;
18 | export default content;
19 | }
20 |
--------------------------------------------------------------------------------
/test/documentRangeMock.js:
--------------------------------------------------------------------------------
1 | // polyfill for document.createRange
2 | if (global.document) {
3 | document.createRange = () => ({
4 | setStart: () => {},
5 | setEnd: () => {},
6 | commonAncestorContainer: {
7 | nodeName: "BODY",
8 | ownerDocument: document
9 | }
10 | });
11 | }
12 |
--------------------------------------------------------------------------------
/test/emptyMock.js:
--------------------------------------------------------------------------------
1 | module.exports = {};
2 |
--------------------------------------------------------------------------------
/test/imageMock.js:
--------------------------------------------------------------------------------
1 | module.exports = 'mock-image-url';
2 |
--------------------------------------------------------------------------------
/test/mockMuiStyles.js:
--------------------------------------------------------------------------------
1 | const removeHash = (classes = {}) => {
2 | return Object.entries(classes).reduce(
3 | (prev, [key, value]) => {
4 | const split = value.split('-');
5 | const lastIndex = split.length - 1;
6 | const newValue = parseInt(split[lastIndex])
7 | ? split.slice(0, lastIndex).join('-')
8 | : value;
9 | prev[key] = newValue;
10 | return prev;
11 | },
12 | { ...classes }
13 | );
14 | };
15 |
16 | jest.mock('@mui/styles/makeStyles', () => {
17 | const makeStylesActual = jest.requireActual('@mui/styles/makeStyles').default;
18 |
19 | const makeStyles = (styles, options) => {
20 | const useStyles = makeStylesActual(styles, options);
21 | return (props) => {
22 | const classes = useStyles(props);
23 | return removeHash(classes);
24 | };
25 | };
26 |
27 | return makeStyles;
28 | });
29 |
30 | jest.mock('@mui/styles/withStyles', () => {
31 | const React = require('react');
32 | const withStylesActual = jest.requireActual('@mui/styles/withStyles').default;
33 |
34 | return (styles, options) => {
35 | const wrap = withStylesActual(styles, options);
36 |
37 | return (Component) => {
38 | return wrap(({ classes, ...props }) => {
39 | const newClasses = removeHash(classes);
40 | return React.createElement(Component, {
41 | classes: newClasses,
42 | ...props,
43 | });
44 | });
45 | };
46 | };
47 | });
48 |
--------------------------------------------------------------------------------
/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "rootDirs": ["./src"]
5 | },
6 | "include": ["./src/**/*"],
7 | "exclude": [
8 | "**/*.test.tsx",
9 | "**/*.test.ts",
10 | "**/*.stories.tsx",
11 | "./stories/**/*",
12 | "./src/testUtils/**/*"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/tsconfig.docs.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "exclude": [
4 | "**/*.test.tsx",
5 | "**/*.test.ts"
6 | ]
7 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowSyntheticDefaultImports": true,
4 | "declaration": true,
5 | "esModuleInterop": true,
6 | "forceConsistentCasingInFileNames": true,
7 | "jsx": "react",
8 | "lib": ["dom", "es2017", "esnext.asynciterable"],
9 | "module": "es2015",
10 | "outDir": "./dist",
11 | "noUnusedParameters": true,
12 | "rootDirs": ["./src", "./stories"],
13 | "skipLibCheck": true,
14 | "target": "es5",
15 | "noUnusedLocals": true,
16 | "noImplicitAny": true,
17 | "noImplicitThis": true,
18 | "alwaysStrict": true,
19 | "strictNullChecks": true,
20 | "strictFunctionTypes": true,
21 | "allowJs": true,
22 | "moduleResolution": "node",
23 | "pretty": true,
24 | "inlineSourceMap": true,
25 | "inlineSources": true
26 | },
27 | "include": [
28 | "./src/**/*",
29 | "./stories/**/*",
30 | "./node_modules/focus-visible/dist/"
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------