diff --git a/.eslintrc.json b/.eslintrc.json index ec93ef03..44d33d7e 100755 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,5 +1,10 @@ { - "extends": ["airbnb", "plugin:@typescript-eslint/recommended", "prettier"], + "extends": [ + "airbnb", + "plugin:@typescript-eslint/recommended", + "plugin:storybook/recommended", + "prettier" + ], "parser": "@typescript-eslint/parser", "plugins": ["@typescript-eslint", "react-hooks"], "env": { @@ -52,6 +57,7 @@ "no-restricted-syntax": "off", "no-shadow": "off", "no-use-before-define": "off", + "storybook/prefer-pascal-case": "off", "react/function-component-definition": [ "error", { diff --git a/.storybook/main.js b/.storybook/main.js index 66f7485d..9d54d2e4 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -1,12 +1,17 @@ module.exports = { core: { builder: 'webpack5', + disableTelemetry: true, }, stories: ['../stories/**/*.stories.tsx'], addons: [ '@storybook/addon-knobs', - '@storybook/addon-actions/register', + '@storybook/addon-actions', './purposeFunction/register.js', ], + framework: '@storybook/react', staticDirs: ['../assets'], + docsPage: { + docs: 'automatic', + }, }; diff --git a/.storybook/preview.js b/.storybook/preview.js index 3ea8d760..62507c75 100644 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -1,28 +1,25 @@ import React from 'react'; // Still needed for deployed storybook to work -import { addDecorator, addParameters } from '@storybook/react'; -import { withInfo } from '@storybook/addon-info'; import StoryRouter from 'storybook-react-router'; import franklinTheme from './franklin-theme'; import '../src/styles/index.scss'; -addDecorator(withInfo); - -addDecorator(StoryRouter()); - -addDecorator((storyFn) => ( -
- {storyFn()} -
-)); - -addParameters({ +export const parameters = { docs: { theme: franklinTheme, }, -}); +}; + +export const decorators = [ + StoryRouter(), + (storyFn) => ( +
+ {storyFn()} +
+ ), +]; diff --git a/.storybook/webpack.config.js b/.storybook/webpack.config.js index 0ca1fee1..918b540a 100644 --- a/.storybook/webpack.config.js +++ b/.storybook/webpack.config.js @@ -47,15 +47,29 @@ module.exports = async ({ config }) => { }); // use react-svg-loader for svg files in react components - config.module.rules.push({ - test: /\.svg$/i, - issuer: /\.tsx?$/, - use: [ - { - loader: '@svgr/webpack', - }, - ], - }); + config.module.rules.push( + { + test: /\.svg$/i, + issuer: /\.(j|t)sx?$/, + use: [ + { + loader: '@svgr/webpack', + options: { + prettier: false, + svgo: false, + svgoConfig: { + plugins: [{ removeViewBox: false }], + }, + }, + }, + ], + }, + { + test: /\.svg$/i, + issuer: /\.(css|scss)?$/, + loader: 'svg-url-loader', + } + ); // Otherwise use svg-url-loader config.module.rules.push({ diff --git a/package.json b/package.json index ba7182ee..574b0476 100755 --- a/package.json +++ b/package.json @@ -3,12 +3,16 @@ "description": "React and Zurb Foundation based design system for life sciences web applications", "version": "0.0.212", "main": "dist/franklin-components.js", + "exports": { + ".": "./dist/franklin-components.js", + "./styles.css": "./dist/styles.css" + }, "files": [ "src", "dist", "assets" ], - "types": "dist/types/components/index.d.ts", + "types": "dist/types/index.d.ts", "sideEffects": false, "repository": "https://github.com/ebi-uniprot/franklin.git", "author": "Xavier Watkins ", @@ -33,9 +37,13 @@ "prepare": "husky install" }, "browserslist": [ - "last 2 version", - "> 1%", - "IE 11" + "defaults", + "Firefox >= 35", + "Chrome >= 40", + "Edge >= 12", + "Safari >= 9", + "cover 95% in CN", + "not dead" ], "jest": { "coverageReporters": [ @@ -46,10 +54,10 @@ ], "coverageThreshold": { "global": { - "lines": 80.14, - "statements": 79.64, - "functions": 78.67, - "branches": 70.16 + "lines": 79.77, + "statements": 79.34, + "functions": 78.6, + "branches": 70.08 } }, "setupFilesAfterEnv": [ @@ -91,84 +99,82 @@ "foundation-sites": "6.7.5", "history": "4.10.1", "lodash-es": "4.17.21", - "query-string": "7.1.1", "timing-functions": "2.0.1", "tippy.js": "6.3.7", - "type-fest": "3.1.0", + "type-fest": "3.6.1", "uuid": "9.0.0" }, "devDependencies": { - "@babel/core": "7.19.6", + "@babel/core": "7.21.3", "@babel/plugin-proposal-class-properties": "7.18.6", "@babel/plugin-proposal-private-methods": "7.18.6", - "@babel/plugin-proposal-private-property-in-object": "7.18.6", - "@babel/plugin-transform-runtime": "7.19.6", + "@babel/plugin-proposal-private-property-in-object": "7.21.0", + "@babel/plugin-transform-runtime": "7.21.0", "@babel/polyfill": "7.12.1", - "@babel/preset-env": "7.19.4", + "@babel/preset-env": "7.20.2", "@babel/preset-react": "7.18.6", - "@babel/preset-typescript": "7.18.6", - "@storybook/addon-actions": "6.3.8", - "@storybook/addon-info": "5.3.21", - "@storybook/addon-knobs": "6.3.1", - "@storybook/addon-links": "6.3.8", - "@storybook/addons": "6.3.8", - "@storybook/api": "6.3.8", - "@storybook/builder-webpack5": "6.3.8", - "@storybook/components": "6.3.8", - "@storybook/manager-webpack5": "6.3.8", - "@storybook/react": "6.3.8", - "@svgr/webpack": "5.5.0", + "@babel/preset-typescript": "7.21.0", + "@storybook/addon-actions": "6.5.16", + "@storybook/addon-knobs": "6.4.0", + "@storybook/addon-links": "6.5.16", + "@storybook/addons": "6.5.16", + "@storybook/api": "6.5.16", + "@storybook/builder-webpack5": "6.5.16", + "@storybook/components": "6.5.16", + "@storybook/manager-webpack5": "6.5.16", + "@storybook/react": "6.5.16", + "@svgr/webpack": "6.5.1", "@testing-library/jest-dom": "5.16.5", - "@testing-library/react": "12.1.2", + "@testing-library/react": "14.0.0", "@types/classnames": "2.3.1", "@types/d3": "5.16.3", - "@types/jest": "29.2.1", - "@types/lodash-es": "4.17.6", - "@types/react-dom": "17.0.11", + "@types/jest": "29.5.0", + "@types/lodash-es": "4.17.7", + "@types/react-dom": "18.0.11", "@types/react-router-dom": "5.3.3", - "@types/rheostat": "3.0.2", - "@types/uuid": "8.3.4", - "@typescript-eslint/eslint-plugin": "5.42.0", - "@typescript-eslint/parser": "5.42.0", - "babel-jest": "29.2.2", - "babel-loader": "9.0.1", + "@types/uuid": "9.0.1", + "@typescript-eslint/eslint-plugin": "5.55.0", + "@typescript-eslint/parser": "5.55.0", + "babel-jest": "29.5.0", + "babel-loader": "9.1.2", "browser-sync-webpack-plugin": "2.3.0", - "core-js": "3.26.0", - "css-loader": "6.7.1", - "eslint": "8.26.0", + "core-js": "3.29.1", + "css-loader": "6.7.3", + "eslint": "8.36.0", "eslint-config-airbnb": "19.0.4", - "eslint-config-prettier": "8.5.0", - "eslint-plugin-import": "2.26.0", - "eslint-plugin-jest": "27.1.3", - "eslint-plugin-jsx-a11y": "6.6.1", + "eslint-config-prettier": "8.7.0", + "eslint-plugin-import": "2.27.5", + "eslint-plugin-jest": "27.2.1", + "eslint-plugin-jsx-a11y": "6.7.1", "eslint-plugin-prettier": "4.2.1", - "eslint-plugin-react": "7.31.10", + "eslint-plugin-react": "7.32.2", "eslint-plugin-react-hooks": "4.6.0", - "file-loader": "6.2.0", - "husky": "8.0.1", - "jest": "29.2.2", + "eslint-plugin-storybook": "^0.6.11", + "husky": "8.0.3", + "jest": "29.5.0", "jest-coverage-ratchet": "0.2.3", "jest-css-modules-transform": "4.4.2", - "jest-environment-jsdom": "29.2.2", + "jest-environment-jsdom": "29.5.0", "jest-silent-reporter": "0.5.0", - "lint-staged": "13.0.3", + "lint-staged": "13.2.0", "lorem-ipsum": "2.0.8", + "mini-css-extract-plugin": "2.7.5", "node-sass-json-importer": "4.3.0", "npm-run-all": "4.1.5", - "prettier": "2.7.1", - "react": "17.0.2", - "react-dom": "17.0.2", - "react-markdown": "8.0.3", + "prettier": "2.8.5", + "react": "18.2.0", + "react-dom": "18.2.0", + "react-markdown": "8.0.5", "react-router-dom": "5.3.0", "rehype-raw": "6.1.1", - "sass": "1.55.0", - "sass-loader": "13.1.0", + "sass": "1.59.3", + "sass-loader": "13.2.1", "storybook-react-router": "1.0.8", - "style-loader": "3.3.1", + "style-loader": "3.3.2", "svg-url-loader": "8.0.0", - "typescript": "4.8.4", - "webpack": "5.74.0", - "webpack-bundle-analyzer": "4.7.0", - "webpack-cli": "4.10.0" + "typescript": "5.0.2", + "webpack": "5.76.2", + "webpack-bundle-analyzer": "4.8.0", + "webpack-cli": "5.0.1" } } diff --git a/src/components/__mocks__/displayMenu.tsx b/src/components/__mocks__/displayMenu.tsx index 1a0c545e..53f2245c 100644 --- a/src/components/__mocks__/displayMenu.tsx +++ b/src/components/__mocks__/displayMenu.tsx @@ -1,4 +1,4 @@ -import { SwissProtIcon } from '..'; +import { SwissProtIcon } from '../..'; export const displayMenuDummyContent1 = 'Page 1 main content'; export const displayMenuDummyContent2 = 'Page 2 main content'; diff --git a/src/components/__tests__/__snapshots__/facets.spec.tsx.snap b/src/components/__tests__/__snapshots__/facets.spec.tsx.snap index 3ca9b293..fe6feb3a 100644 --- a/src/components/__tests__/__snapshots__/facets.spec.tsx.snap +++ b/src/components/__tests__/__snapshots__/facets.spec.tsx.snap @@ -60,66 +60,6 @@ exports[` should render facet with a lot of data 1`] = ` `; -exports[` should render facet with a lot of data 2`] = ` - -
-
- Proteins with -
- -
-
-`; - exports[` should render facet with few data 1`] = `
@@ -150,36 +90,6 @@ exports[` should render facet with few data 1`] = ` `; -exports[` should render facet with few data 2`] = ` - - - -`; - exports[` should render multiple facets 1`] = `
should render multiple facets 1`] = `
  • A. thaliana (1,239) @@ -428,35 +338,35 @@ exports[` should render multiple facets 1`] = ` >
  • 1 - 200 (101,062)
  • 201 - 400 (553,257)
  • 401 - 600 (423,158)
  • 601 - 800 (120,501)
  • >= 801 (85,205) diff --git a/src/components/__tests__/__snapshots__/tile.spec.tsx.snap b/src/components/__tests__/__snapshots__/tile.spec.tsx.snap index 095ea673..f8a642a9 100644 --- a/src/components/__tests__/__snapshots__/tile.spec.tsx.snap +++ b/src/components/__tests__/__snapshots__/tile.spec.tsx.snap @@ -10,7 +10,7 @@ exports[`Tile component should render 1`] = ` aria-hidden="true" class="tile__background-image" > - test-file-stub +
  • { it('should render', () => { diff --git a/src/components/__tests__/data-table.spec.tsx b/src/components/__tests__/data-table.spec.tsx index 6fed67f1..ae17abea 100644 --- a/src/components/__tests__/data-table.spec.tsx +++ b/src/components/__tests__/data-table.spec.tsx @@ -5,6 +5,7 @@ import { getAllByRole, ByRoleOptions, waitFor, + queryAllByRole, } from '@testing-library/react'; import { @@ -28,7 +29,7 @@ describe('DataTable', () => { content3: 'baz', })); - type DataType = typeof data[0]; + type DataType = (typeof data)[0]; const columns: Array | NonSortableColumn> = [ { @@ -179,7 +180,11 @@ describe('DataTable', () => { fireEvent.click(selectAll); // uncheck everything, get out of mixed state // No checked checkboxes to find, helper function should throw await waitFor(() => - expect(() => getBodyCheckboxes({ checked: true })).toThrow() + expect(() => + queryAllByRole(screen.getByRole('table'), 'checkbox', { + checked: true, + }) + ) ); expect(selectAll.indeterminate).toBe(false); // not in a mixed state }); diff --git a/src/components/__tests__/facets.spec.tsx b/src/components/__tests__/facets.spec.tsx index 27779a51..767e9f35 100644 --- a/src/components/__tests__/facets.spec.tsx +++ b/src/components/__tests__/facets.spec.tsx @@ -31,24 +31,6 @@ describe('', () => { }); }); -describe('', () => { - it('should render facet with few data', () => { - const { asFragment } = renderWithRouter(); - expect( - screen.queryByRole('button', { name: /More items/i }) - ).not.toBeInTheDocument(); - expect(asFragment()).toMatchSnapshot(); - }); - - it('should render facet with a lot of data', () => { - const { asFragment } = renderWithRouter(); - expect( - screen.getByRole('button', { name: /More items/i }) - ).toBeInTheDocument(); - expect(asFragment()).toMatchSnapshot(); - }); -}); - describe('parse facets and stringify', () => { it('should handle just facets', () => { const oneFacet = 'facets=identity%3A1.0'; @@ -58,13 +40,13 @@ describe('parse facets and stringify', () => { }); it('should handle facets and something else', () => { - const query = 'facets=identity%3A1.0&query=glucose'; + const query = 'query=glucose&facets=identity%3A1.0'; expect(stringify(parse(query))).toBe(query); }); it('should handle facets with another name', () => { const key = 'filter'; - const query = 'filter=identity%3A1.0&query=glucose'; + const query = 'query=glucose&filter=identity%3A1.0'; expect(stringify(parse(query, key), key)).toBe(query); }); }); diff --git a/src/components/__tests__/header.spec.tsx b/src/components/__tests__/header.spec.tsx index b9454d5a..895e52b9 100644 --- a/src/components/__tests__/header.spec.tsx +++ b/src/components/__tests__/header.spec.tsx @@ -7,7 +7,7 @@ import { HelpIcon, MainSearch, Dropdown, -} from '..'; +} from '../..'; import renderWithRouter from '../../testHelpers/renderWithRouter'; diff --git a/src/components/__tests__/histogram-filter.spec.tsx b/src/components/__tests__/histogram-filter.spec.tsx index d54586dd..b74f2174 100644 --- a/src/components/__tests__/histogram-filter.spec.tsx +++ b/src/components/__tests__/histogram-filter.spec.tsx @@ -1,6 +1,6 @@ import { render, fireEvent, screen } from '@testing-library/react'; -import { HistogramFilter } from '..'; +import { HistogramFilter } from '../..'; import useSize from '../../hooks/useSize'; diff --git a/src/components/__tests__/histogram.spec.tsx b/src/components/__tests__/histogram.spec.tsx index 4813e0cc..b5a40e2d 100644 --- a/src/components/__tests__/histogram.spec.tsx +++ b/src/components/__tests__/histogram.spec.tsx @@ -1,6 +1,6 @@ import { render, screen } from '@testing-library/react'; -import { Histogram } from '..'; +import { Histogram } from '../..'; import useSize from '../../hooks/useSize'; diff --git a/src/components/__tests__/long-number.spec.tsx b/src/components/__tests__/long-number.spec.tsx index fb5c1a50..95efed52 100644 --- a/src/components/__tests__/long-number.spec.tsx +++ b/src/components/__tests__/long-number.spec.tsx @@ -1,6 +1,6 @@ import { render } from '@testing-library/react'; -import { LongNumber } from '..'; +import { LongNumber } from '../..'; describe('Long number component', () => { it('should render', () => { diff --git a/src/components/__tests__/substring-highlight.spec.tsx b/src/components/__tests__/substring-highlight.spec.tsx index 05b80728..f2e3f3c0 100644 --- a/src/components/__tests__/substring-highlight.spec.tsx +++ b/src/components/__tests__/substring-highlight.spec.tsx @@ -1,5 +1,6 @@ import { render, screen } from '@testing-library/react'; -import { SubstringHighlight } from '..'; + +import { SubstringHighlight } from '../..'; // TODO: Can't get mark by role yet but it should be possible in the future: // https://github.com/testing-library/dom-testing-library/issues/1150 diff --git a/src/components/__tests__/tile.spec.tsx b/src/components/__tests__/tile.spec.tsx index 91b776ef..98c07778 100644 --- a/src/components/__tests__/tile.spec.tsx +++ b/src/components/__tests__/tile.spec.tsx @@ -1,7 +1,6 @@ import renderWithRouter from '../../testHelpers/renderWithRouter'; -import Tile from '../tile'; -import { ProtVistaIcon } from '..'; +import { ProtVistaIcon, Tile } from '../..'; import colors from '../../styles/colours.json'; @@ -11,7 +10,7 @@ describe('Tile component', () => { } backgroundColor={colors.seaBlue} to="/" gradient diff --git a/src/components/accordion-search.tsx b/src/components/accordion-search.tsx index 0060e6f9..046c735e 100644 --- a/src/components/accordion-search.tsx +++ b/src/components/accordion-search.tsx @@ -1,7 +1,13 @@ import { useState, useMemo, useEffect } from 'react'; import { debounce } from 'lodash-es'; -import { Accordion, Loader, Message, SearchInput, SubstringHighlight } from '.'; +import { + Accordion, + Loader, + Message, + SearchInput, + SubstringHighlight, +} from '..'; import '../styles/components/accordion-search.scss'; diff --git a/src/components/accordion.tsx b/src/components/accordion.tsx index 8b34b7b4..0aff50ee 100644 --- a/src/components/accordion.tsx +++ b/src/components/accordion.tsx @@ -1,4 +1,4 @@ -import { useState, FC, ReactNode, HTMLAttributes } from 'react'; +import { useState, ReactNode, HTMLAttributes } from 'react'; import cn from 'classnames'; import Bubble from './bubble'; @@ -24,10 +24,17 @@ type Props = { * Disable toggling and always open accordion */ alwaysOpen?: boolean; + /** + * Initial state of the component + */ initialOpen?: boolean; + /** + * React children + */ + children?: ReactNode; }; -const Accordion: FC> = ({ +const Accordion = ({ accordionTitle, count = 0, children, @@ -35,7 +42,7 @@ const Accordion: FC> = ({ initialOpen = false, className, ...props -}) => { +}: Props & HTMLAttributes) => { const [open, setOpen] = useState(initialOpen); const toggleOpen = () => { diff --git a/src/components/autocomplete-item.tsx b/src/components/autocomplete-item.tsx index 5163dfed..a9157581 100644 --- a/src/components/autocomplete-item.tsx +++ b/src/components/autocomplete-item.tsx @@ -1,5 +1,6 @@ import { useEffect, useRef } from 'react'; -import { SubstringHighlight } from '.'; + +import SubstringHighlight from './substring-highlight'; export type AutocompleteItemType = { id: string; diff --git a/src/components/autocomplete.tsx b/src/components/autocomplete.tsx index 1562d006..5aeaf11e 100644 --- a/src/components/autocomplete.tsx +++ b/src/components/autocomplete.tsx @@ -4,6 +4,8 @@ import { useState, HTMLAttributes, ReactNode, + ChangeEventHandler, + KeyboardEventHandler, } from 'react'; import cn from 'classnames'; import { Except } from 'type-fest'; @@ -94,7 +96,7 @@ const Autocomplete = ({ [onDropdownChange] ); - const handleInputChange = useCallback( + const handleInputChange = useCallback>( (event) => { const { value: textInputValue } = event.target; const showDropdown = shouldShowDropdown( @@ -128,7 +130,7 @@ const Autocomplete = ({ [clearOnSelect, onSelect, onDropdownChange] ); - const handleOnKeyDown = useCallback( + const handleOnKeyDown = useCallback>( (event) => { if (event.key === 'ArrowUp') { event.preventDefault(); diff --git a/src/components/button-modal.tsx b/src/components/button-modal.tsx index b4b182b0..76136eb3 100644 --- a/src/components/button-modal.tsx +++ b/src/components/button-modal.tsx @@ -1,4 +1,4 @@ -import { FC } from 'react'; +import { ReactNode } from 'react'; import ModalBackdrop from './modal-backdrop'; import Window from './window/window'; @@ -13,9 +13,10 @@ type DialogWindowProps = { onWindowOpen?: () => void; withHeaderCloseButton?: boolean; withFooterCloseButton?: boolean; + children: ReactNode; }; -const DialogWindow: FC = ({ +const DialogWindow = ({ title, width, height, @@ -25,7 +26,7 @@ const DialogWindow: FC = ({ withHeaderCloseButton, withFooterCloseButton, children, -}) => ( +}: DialogWindowProps) => ( = ({ +const ButtonModal = ({ buttonText, title, width = '70vw', @@ -64,7 +66,7 @@ const ButtonModal: FC = ({ withHeaderCloseButton, withFooterCloseButton = true, children, -}) => { +}: ButtonModalProps) => { const { displayModal, setDisplayModal, Modal } = useModal( ModalBackdrop, DialogWindow diff --git a/src/components/button.tsx b/src/components/button.tsx index 4e8daca5..acd51b31 100644 --- a/src/components/button.tsx +++ b/src/components/button.tsx @@ -1,8 +1,16 @@ -import { forwardRef, ComponentClass, FunctionComponent } from 'react'; +import { + forwardRef, + ComponentClass, + FunctionComponent, + ReactNode, +} from 'react'; import cn from 'classnames'; import '../styles/common/_buttons.scss'; +// NOTE: need to improve the types of this component eventually +// possible inspiration: https://twitter.com/glnnrys/status/1602202089312043009 + type PossibleElements = | HTMLAnchorElement // | HTMLButtonElement // @@ -30,41 +38,43 @@ export type ButtonProps = { * Classnames to be added to the button */ className?: string; + /** + * Children + */ + children?: ReactNode; /** * Any other prop to pass down to the rendered element */ [key: string]: unknown; }; -export const Button = forwardRef( - ( - { element = 'button', className, variant = 'primary', children, ...props }, - ref - ) => { - const rest = props; +const Button = ({ + element = 'button', + className, + variant = 'primary', + children, + type, + ...props +}: ButtonProps) => { + // eslint-disable-next-line no-underscore-dangle + let _type = type; - if (element === 'button') { - rest.type = props.type || 'button'; - } - - const Element = element; - return ( - - {children} - - ); + if (element === 'button') { + _type = type || 'button'; } -); -export default Button; + const Element = element; + return ( + + {children} + + ); +}; + +export default forwardRef((props, ref) => ( +