From d83f79969a877b533ff288f2238663035398f6ac Mon Sep 17 00:00:00 2001 From: felicia-haggqvist <144792260+felicia-haggqvist@users.noreply.github.com> Date: Thu, 16 May 2024 10:57:57 +0200 Subject: [PATCH] feat: add warp ds eslint config (#238) * feat: add warp-ds eslint-config * feat: fix lint issues * feat: add ts,tsx,jsx files in lint:check and lint * feat: add .eslintrc to add parser, plugin + extend for typescript, react, warp and prettier + fix linting issues * feat; adjust comment in attention-helper * fix: add @serverless-guru/prettier-plugin-import-order devDep * fix: bump @warp-ds/eslint-config + run pnpm lint * fix: fix issues with bumping happy-dom that caused tests to fail * fix: fix broken AttentionTest + add eslint-disable-next-line for TextAreaTest * revert installing eslint-plugin-prettier + eslint-config-prettier * fix: bump version to 9 * fix: remove eslint-plugin-import since it is already a peerDep in the @warp-ds/eslint-config --- .eslintrc | 12 + .github/workflows/lint.pr.yaml | 21 + @types/index.d.ts | 4 +- build.js | 35 +- esbuild.js | 8 +- lingui.config.ts | 1 + package.json | 8 +- packages/_helpers/affix.tsx | 21 +- packages/_helpers/attention.tsx | 89 +- packages/_helpers/clickable.tsx | 13 +- packages/_helpers/dead-toggle.tsx | 9 +- packages/_helpers/expand-transition.tsx | 12 +- packages/_helpers/unstyled-heading.tsx | 4 +- packages/alert/src/component.tsx | 37 +- packages/alert/stories/Alert.stories.tsx | 51 +- packages/attention/src/component.tsx | 247 +- packages/attention/src/props.ts | 42 +- .../attention/stories/Attention.stories.tsx | 269 +- packages/badge/src/component.tsx | 22 +- packages/badge/src/props.tsx | 3 +- packages/badge/stories/Badge.stories.tsx | 29 +- packages/box/src/component.tsx | 18 +- packages/box/src/props.tsx | 5 +- packages/box/stories/Box.stories.tsx | 9 +- packages/breadcrumbs/src/component.tsx | 44 +- packages/breadcrumbs/src/props.tsx | 5 +- .../stories/Breadcrumbs.stories.tsx | 61 +- packages/button/src/component.tsx | 75 +- packages/button/stories/Button.stories.tsx | 530 +- packages/card/src/component.tsx | 30 +- packages/card/stories/Card.stories.tsx | 771 +- packages/combobox/src/component.tsx | 589 +- packages/combobox/src/props.ts | 10 +- packages/combobox/src/utils.ts | 19 +- .../combobox/stories/Combobox.stories.tsx | 91 +- packages/expandable/src/component.tsx | 65 +- packages/expandable/src/props.tsx | 1 + .../expandable/stories/Expandable.stories.tsx | 97 +- packages/i18n.ts | 29 +- packages/modal/src/component.tsx | 126 +- packages/modal/stories/Modal.stories.tsx | 36 +- packages/pagination/src/CurrentPage.tsx | 45 +- packages/pagination/src/FirstPage.tsx | 47 +- packages/pagination/src/NextPage.tsx | 71 +- packages/pagination/src/Page.tsx | 59 +- packages/pagination/src/Pages.tsx | 50 +- packages/pagination/src/Pagination.tsx | 66 +- .../pagination/src/PaginationContainer.tsx | 43 +- packages/pagination/src/PrevPage.tsx | 73 +- packages/pagination/src/index.ts | 5 +- .../pagination/stories/Pagination.stories.tsx | 3 +- packages/pill/src/component.tsx | 34 +- packages/pill/stories/Button.stories.tsx | 69 +- packages/select/src/component.tsx | 49 +- packages/select/src/props.tsx | 5 +- packages/select/stories/Select.stories.tsx | 69 +- packages/slider/src/component.tsx | 26 +- packages/slider/stories/Slider.stories.tsx | 18 +- packages/steps/src/component.tsx | 12 +- packages/steps/src/step.tsx | 58 +- packages/steps/stories/Steps.stories.tsx | 139 +- packages/switch/src/component.tsx | 23 +- packages/switch/stories/Switch.stories.tsx | 25 +- packages/tabs/__tests__/Tabs.test.tsx | 6 +- packages/tabs/src/component-tab-panel.tsx | 9 +- packages/tabs/src/component-tab.tsx | 16 +- packages/tabs/src/component-tabs.tsx | 72 +- packages/tabs/stories/Tabs.stories.tsx | 182 +- packages/textarea/__tests__/TextArea.test.tsx | 6 +- packages/textarea/src/component.tsx | 187 +- packages/textarea/src/useTextAreaHeight.ts | 18 +- .../textarea/stories/TextArea.stories.tsx | 31 +- .../textfield/__tests__/Textfield.test.tsx | 6 +- packages/textfield/src/component.tsx | 174 +- .../textfield/stories/Textfield.stories.tsx | 25 +- packages/toggle/src/component.tsx | 63 +- packages/toggle/src/item.tsx | 25 +- packages/toggle/stories/Checkbox.stories.tsx | 166 +- packages/toggle/stories/Radio.stories.tsx | 150 +- .../toggle/stories/RadioButtons.stories.tsx | 151 +- packages/utils/src/useElementSizeObserver.ts | 2 + packages/utils/src/useId.ts | 18 +- .../utils/src/useIsomorphicLayoutEffect.ts | 5 +- .../utils/src/useLogDeprecationWarning.ts | 8 +- pnpm-lock.yaml | 8851 ++++++++--------- setup.ts | 2 +- tests/components/AlertTest.tsx | 40 +- tests/components/AttentionTest.tsx | 578 +- tests/components/BadgeTest.tsx | 30 +- tests/components/BoxTest.tsx | 44 +- tests/components/BreadcrumbsTest.tsx | 37 +- tests/components/ButtonTest.tsx | 46 +- tests/components/CardTest.tsx | 18 +- tests/components/PaginationTest.tsx | 106 +- tests/components/PillTest.tsx | 27 +- tests/components/SelectTest.tsx | 12 +- tests/components/TextAreaTest.tsx | 49 +- tests/components/TextfieldTest.tsx | 32 +- tests/components/ToggleTest.tsx | 224 +- tests/eik-react-jsx/index.jsx | 5 +- tests/eik-react/index.js | 5 +- tests/ssr-react-16-cjs/src/client.jsx | 2 + tests/ssr-react-16-cjs/src/index.jsx | 52 +- tests/ssr-react-16-cjs/src/server.js | 6 +- tests/ssr-react-16/client.jsx | 2 + tests/ssr-react-16/index.jsx | 10 +- tests/ssr-react-16/server.js | 7 +- tests/ssr-react-ts-16-cjs/src/client.tsx | 2 + tests/ssr-react-ts-16-cjs/src/index.tsx | 52 +- tests/ssr-react-ts-16-cjs/src/server.ts | 5 +- tests/ssr-react-ts-16/src/client.tsx | 2 + tests/ssr-react-ts-16/src/index.tsx | 67 +- tests/ssr-react-ts-16/src/server.ts | 7 +- vite.config.js | 32 +- 114 files changed, 7252 insertions(+), 8957 deletions(-) create mode 100644 .eslintrc create mode 100644 .github/workflows/lint.pr.yaml diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 00000000..7ea1b1ce --- /dev/null +++ b/.eslintrc @@ -0,0 +1,12 @@ +{ + "parser": "@typescript-eslint/parser", + "plugins": [ + "@typescript-eslint", + "react-hooks" + ], + "extends": [ + "@warp-ds", + "plugin:react-hooks/recommended", + "plugin:prettier/recommended" + ] +} diff --git a/.github/workflows/lint.pr.yaml b/.github/workflows/lint.pr.yaml new file mode 100644 index 00000000..7d26c976 --- /dev/null +++ b/.github/workflows/lint.pr.yaml @@ -0,0 +1,21 @@ +name: Lint + +on: [pull_request] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: lts/* + - name: Install pnpm and dependencies + uses: pnpm/action-setup@v2 + with: + version: 9 + run_install: true + - name: Lint + run: pnpm lint:check \ No newline at end of file diff --git a/@types/index.d.ts b/@types/index.d.ts index b321eb95..965155bb 100644 --- a/@types/index.d.ts +++ b/@types/index.d.ts @@ -1,3 +1,3 @@ declare module '@warp-ds/icons/react' { - export * from '@warp-ds/icons/dist/types/react' -} \ No newline at end of file + export * from '@warp-ds/icons/dist/types/react'; +} diff --git a/build.js b/build.js index 4edd10c9..e4143eba 100644 --- a/build.js +++ b/build.js @@ -1,25 +1,26 @@ -import esbuild from "esbuild"; -import { glob } from "glob"; import fs from 'fs'; + +import esbuild from 'esbuild'; +import { glob } from 'glob'; import ts from 'typescript'; -const components = glob.sync("packages/**/src/index.ts"); -const indexPath = "packages/index.ts"; +const components = glob.sync('packages/**/src/index.ts'); +const indexPath = 'packages/index.ts'; const esbuildDefaults = { bundle: true, - format: "esm", + format: 'esm', sourcemap: true, - target: "es2017", + target: 'es2017', minify: false, - external: ["react", "@lingui/core"], + external: ['react', '@lingui/core'], }; function generateTypeDefinitions(inputFilePath, packageName, outDir) { let listOfTsFiles = []; const options = { module: ts.ModuleKind.NodeNext, - lib: ["DOM", "ES2020"], + lib: ['DOM', 'ES2020'], target: ts.ScriptTarget.ES2020, sourceMap: true, strict: true, @@ -36,7 +37,7 @@ function generateTypeDefinitions(inputFilePath, packageName, outDir) { allowSyntheticDefaultImports: true, strictNullChecks: true, outDir: outDir, - } + }; const host = ts.createCompilerHost(options); host.writeFile = (fileName, contents) => listOfTsFiles.push({ fileName, contents }); @@ -51,7 +52,7 @@ function generateTypeDefinitions(inputFilePath, packageName, outDir) { // Doing this hack since typescript sometimes doesn't output the correct path let packageTypePath = file.fileName.replace(outDir, '').replace('src/', '').replace(`${packageName}/`, ''); const updatedFilename = outDir + packageTypePath; - + fs.mkdirSync(updatedFilename.split('/').slice(0, -1).join('/'), { recursive: true }); fs.writeFileSync(updatedFilename, file.contents); }); @@ -62,7 +63,7 @@ function buildComponents(outDir, extraBuildOptions = {}) { const regex = /\/(\w+)\//; const match = item.match(regex); - if (item.includes("utils")) return; + if (item.includes('utils')) return; console.log(`react: building ${match[1]}.js`); try { @@ -72,7 +73,7 @@ function buildComponents(outDir, extraBuildOptions = {}) { ...esbuildDefaults, ...extraBuildOptions, }); - generateTypeDefinitions(item, match[1],`${outDir}/packages/${match[1]}/`); + generateTypeDefinitions(item, match[1], `${outDir}/packages/${match[1]}/`); } catch (err) { console.error(err); } @@ -80,7 +81,7 @@ function buildComponents(outDir, extraBuildOptions = {}) { } async function buildIndex(outDir, extraBuildOptions = {}) { - console.log("react: building index.js"); + console.log('react: building index.js'); try { // ESM build await esbuild.build({ @@ -94,7 +95,7 @@ async function buildIndex(outDir, extraBuildOptions = {}) { ...esbuildDefaults, entryPoints: [indexPath], outfile: `${outDir}/index.cjs`, - format: "cjs", + format: 'cjs', ...extraBuildOptions, }); } catch (err) { @@ -102,7 +103,7 @@ async function buildIndex(outDir, extraBuildOptions = {}) { } } -console.log("Building react"); +console.log('Building react'); -buildComponents("dist/npm"); -buildIndex("dist/npm"); +buildComponents('dist/npm'); +buildIndex('dist/npm'); diff --git a/esbuild.js b/esbuild.js index 73813cc6..44993aa7 100644 --- a/esbuild.js +++ b/esbuild.js @@ -1,4 +1,5 @@ import { ok } from 'node:assert'; + import * as eik from '@eik/esbuild-plugin'; import esbuild from 'esbuild'; @@ -11,12 +12,7 @@ const versions = new Map([ const version = process.argv[2]; const reactVersions = Array.from(versions.keys()); -ok( - reactVersions.includes(version), - `Version argument is required. Must be one of: ${reactVersions.join( - ',', - )}. Eg. 'node esbuild.mjs 18'`, -); +ok(reactVersions.includes(version), `Version argument is required. Must be one of: ${reactVersions.join(',')}. Eg. 'node esbuild.mjs 18'`); await eik.load({ urls: [`https://assets.finn.no/map/react/${versions.get(version)}`], diff --git a/lingui.config.ts b/lingui.config.ts index 3d953eb3..c0db167f 100644 --- a/lingui.config.ts +++ b/lingui.config.ts @@ -1,4 +1,5 @@ import type { LinguiConfig } from '@lingui/conf'; + import { supportedLocales } from './packages/i18n'; const config: LinguiConfig = { diff --git a/package.json b/package.json index e1eda0f2..b526b8c1 100644 --- a/package.json +++ b/package.json @@ -65,10 +65,8 @@ "build": "pnpm run build:clean && pnpm run build:npm && pnpm run build:eik && pnpm run build:types", "commit": "cz", "dev": "storybook dev -p 9003 --ci", - "format": "prettier --write . --ignore-path .gitignore", - "lint:eslint": "eslint . --ext ts,tsx,js,jsx,cjs --max-warnings 0 --ignore-path .gitignore", - "lint:format": "prettier --check . --ignore-path .gitignore", - "lint": "pnpm run lint:format && pnpm run lint:eslint", + "lint:check": "eslint . --ext ts,tsx,js,jsx,cjs,mjs --ignore-path .gitignore", + "lint": "eslint . --fix --ext ts,tsx,js,jsx,cjs,mjs --ignore-path .gitignore", "messages:compile": "lingui compile", "messages:extract": "lingui extract", "semantic-release": "semantic-release", @@ -112,9 +110,9 @@ "element-collapse": "^1.0.1", "esbuild": "0.19.11", "eslint": "^8.56.0", + "@warp-ds/eslint-config": "1.0.5", "eslint-config-react-app": "^7.0.1", "eslint-plugin-flowtype": "^8.0.3", - "eslint-plugin-import": "^2.29.1", "eslint-plugin-jsx-a11y": "^6.8.0", "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", diff --git a/packages/_helpers/affix.tsx b/packages/_helpers/affix.tsx index 4bd237eb..1cc0ea28 100644 --- a/packages/_helpers/affix.tsx +++ b/packages/_helpers/affix.tsx @@ -1,12 +1,13 @@ -import React from "react"; -import { suffix, prefix } from "@warp-ds/css/component-classes"; -import { classNames } from "@chbphone55/classnames"; -import IconClose16 from "@warp-ds/icons/react/close-16"; -import IconSearch16 from "@warp-ds/icons/react/search-16"; +import React from 'react'; + +import { classNames } from '@chbphone55/classnames'; +import { prefix, suffix } from '@warp-ds/css/component-classes'; +import IconClose16 from '@warp-ds/icons/react/close-16'; +import IconSearch16 from '@warp-ds/icons/react/search-16'; interface AffixProps { /** Defines a string value that labels the affix element. */ - "aria-label"?: string; + 'aria-label'?: string; /** Affix added at the beginning of input */ prefix?: boolean; @@ -31,10 +32,10 @@ export function Affix(props: AffixProps) { const classBase = props.prefix ? prefix : suffix; return React.createElement( - props.label ? "div" : "button", + props.label ? 'div' : 'button', { - "aria-label": !props.label ? props["aria-label"] : undefined, - type: props.search ? "submit" : props.clear ? "reset" : undefined, + 'aria-label': !props.label ? props['aria-label'] : undefined, + type: props.search ? 'submit' : props.clear ? 'reset' : undefined, onClick: props.onClick, className: classNames({ [classBase.wrapper]: true, @@ -48,6 +49,6 @@ export function Affix(props: AffixProps) { {props.search && } {props.label && {props.label}} - + , ); } diff --git a/packages/_helpers/attention.tsx b/packages/_helpers/attention.tsx index b69146bb..31d6dcd5 100644 --- a/packages/_helpers/attention.tsx +++ b/packages/_helpers/attention.tsx @@ -1,11 +1,13 @@ -import { AttentionState, Directions, opposites } from '@warp-ds/core/attention' -import { AttentionVariants, AttentionProps, ReferenceElement } from '../attention/src/props.js' -import { i18n } from '@lingui/core' -import { MutableRefObject, useEffect } from 'react' +import { MutableRefObject, useCallback, useEffect } from 'react'; -export const getVariant = (variantProps: AttentionVariants, variantClasses: any) => { - return Object.keys(variantClasses).find((b) => !!variantProps[b]) || '' -} +import { i18n } from '@lingui/core'; +import { autoUpdatePosition, opposites } from '@warp-ds/core/attention'; +import type { AttentionState, Directions } from '@warp-ds/core/attention'; + +import type { AttentionProps, AttentionVariants, ReferenceElement } from '../attention/src/props.js'; + +export const getVariant = (variantProps: AttentionVariants, variantClasses: any) => + Object.keys(variantClasses).find((b) => !!variantProps[b]) || ''; export const pointingAtDirection = (actualDirection: Directions) => { switch (opposites[actualDirection]) { case 'top-start': @@ -14,40 +16,36 @@ export const pointingAtDirection = (actualDirection: Directions) => { return i18n._({ id: 'attention.aria.pointingUp', message: 'pointing up', - comment: - 'Default screenreader message for top direction in the attention component', - }) + comment: 'Default screenreader message for top direction in the attention component', + }); case 'right-start': case 'right': case 'right-end': return i18n._({ id: 'attention.aria.pointingRight', message: 'pointing right', - comment: - 'Default screenreader message for right direction in the attention component', - }) + comment: 'Default screenreader message for right direction in the attention component', + }); case 'bottom-start': case 'bottom': case 'bottom-end': return i18n._({ id: 'attention.aria.pointingDown', message: 'pointing down', - comment: - 'Default screenreader message for bottom direction in the attention component', - }) + comment: 'Default screenreader message for bottom direction in the attention component', + }); case 'left-start': case 'left': case 'left-end': return i18n._({ id: 'attention.aria.pointingLeft', message: 'pointing left', - comment: - 'Default screenreader message for left direction in the attention component', - }) + comment: 'Default screenreader message for left direction in the attention component', + }); default: - return '' + return ''; } -} +}; export const activeAttentionType = (props: AttentionProps) => { switch (true) { @@ -55,46 +53,47 @@ export const activeAttentionType = (props: AttentionProps) => { return i18n._({ id: 'attention.aria.tooltip', message: 'tooltip', - comment: - 'Default screenreader message for tooltip in the attention component', - }) + comment: 'Default screenreader message for tooltip in the attention component', + }); case props.callout: return i18n._({ id: 'attention.aria.callout', message: 'callout speech bubble', - comment: - 'Default screenreader message for callout speech bubble in the attention component', - }) + comment: 'Default screenreader message for callout speech bubble in the attention component', + }); case props.popover: return i18n._({ id: 'attention.aria.popover', message: 'popover speech bubble', - comment: - 'Default screenreader message for popover speech bubble in the attention component', - }) + comment: 'Default screenreader message for popover speech bubble in the attention component', + }); case props.highlight: return i18n._({ id: 'attention.aria.highlight', message: 'highlighted speech bubble', - comment: - 'Default screenreader message for highlighted speech bubble in the attention component', - }) + comment: 'Default screenreader message for highlighted speech bubble in the attention component', + }); default: - return '' + return ''; } -} +}; export const useAutoUpdatePosition = ( - targetEl: MutableRefObject | undefined, - isShowing: boolean | undefined, + targetEl: MutableRefObject | undefined, + isShowing: boolean | undefined, attentionEl: MutableRefObject, - autoUpdatePosition: (state: AttentionState) => void, - attentionState: AttentionState) => { + attentionState: AttentionState, +) => { + const memoizedAutoUpdatePosition = useCallback(() => autoUpdatePosition(attentionState), [attentionState]); + useEffect(() => { - if (isShowing && targetEl && attentionEl) { - // starts the autoUpdate, making sure the attention elements's position stays anchored to the target element - const cleanup = autoUpdatePosition(attentionState) - return cleanup + if (isShowing && targetEl?.current && attentionEl?.current) { + const cleanup = memoizedAutoUpdatePosition(); + return cleanup; } - }, [targetEl, isShowing, attentionEl]) -} \ No newline at end of file + // We should return a cleanup function to maintain consistent return type and handle potential cleanups explicitly. + // This ensures compatibility with TypeScript's type expectations and React's cleanup mechanism. + // Therefore we return an empty function here: + return () => {}; + }, [targetEl, isShowing, attentionEl, memoizedAutoUpdatePosition]); +}; diff --git a/packages/_helpers/clickable.tsx b/packages/_helpers/clickable.tsx index 15e3939b..9cf43265 100644 --- a/packages/_helpers/clickable.tsx +++ b/packages/_helpers/clickable.tsx @@ -1,6 +1,8 @@ import React from 'react'; + import { classNames } from '@chbphone55/classnames'; import { clickable as ccClickable } from '@warp-ds/css/component-classes'; + import { Item as ToggleItem } from '../toggle/src/item.js'; import { useId } from '../utils/src/useId.js'; @@ -43,13 +45,7 @@ export type ClickableProps = { } & Partial> & Partial>; -export function Clickable({ - children, - radio, - checkbox, - value, - ...props -}: ClickableProps) { +export function Clickable({ children, radio, checkbox, value, ...props }: ClickableProps) { const id = useId(); const type = radio ? 'radio' : 'checkbox'; @@ -61,8 +57,7 @@ export function Clickable({ controlled={false} onChange={props.onClick ? props.onClick : () => undefined} value={value} - name={`${props.name || id}:toggle`} - > + name={`${props.name || id}:toggle`}> {children} ) : ( diff --git a/packages/_helpers/dead-toggle.tsx b/packages/_helpers/dead-toggle.tsx index 64b1b513..bca12ec7 100644 --- a/packages/_helpers/dead-toggle.tsx +++ b/packages/_helpers/dead-toggle.tsx @@ -1,6 +1,8 @@ import React from 'react'; + import { classNames } from '@chbphone55/classnames'; import { deadToggle as ccDeadToggle } from '@warp-ds/css/component-classes'; + import { Item } from '../toggle/src/item.js'; export interface DeadToggleProps { @@ -44,10 +46,7 @@ export function DeadToggle(props: DeadToggleProps) { const type = props.radio ? 'radio' : 'checkbox'; return ( -