From 420a0d5326428775098cae69a47d31bcd227f20f Mon Sep 17 00:00:00 2001 From: Armano Date: Tue, 30 Nov 2021 21:27:05 +0100 Subject: [PATCH 1/3] docs(website): optimize playground config editor to only trigger lint when needed --- packages/website/package.json | 1 + .../src/components/OptionsSelector.tsx | 35 +++++++++++----- .../src/components/config/ConfigEslint.tsx | 30 +++++++------ .../components/config/ConfigTypeScript.tsx | 20 +++++---- .../src/components/hooks/useHashState.ts | 21 +--------- .../src/components/lib/shallowEqual.ts | 21 ++++++++++ .../src/components/modals/Modal.module.css | 2 + .../website/src/components/modals/Modal.tsx | 42 ++++++++++++++----- 8 files changed, 111 insertions(+), 61 deletions(-) create mode 100644 packages/website/src/components/lib/shallowEqual.ts diff --git a/packages/website/package.json b/packages/website/package.json index 2058c4598359..1a69a53ea58b 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -6,6 +6,7 @@ "build": "docusaurus build", "clear": "docusaurus clear", "format": "prettier --write \"./**/*.{md,mdx,ts,js,tsx,jsx}\" --ignore-path ../../.prettierignore", + "lint": "eslint . --ext .js,.ts --ignore-path ../../.eslintignore", "serve": "docusaurus serve", "start": "docusaurus start", "swizzle": "docusaurus swizzle" diff --git a/packages/website/src/components/OptionsSelector.tsx b/packages/website/src/components/OptionsSelector.tsx index 71f83a95902a..023efce6315d 100644 --- a/packages/website/src/components/OptionsSelector.tsx +++ b/packages/website/src/components/OptionsSelector.tsx @@ -43,19 +43,32 @@ function OptionsSelector({ const [copyLink, setCopyLink] = useState(false); const [copyMarkdown, setCopyMarkdown] = useState(false); - const updateTS = useCallback((version: string) => { - setState({ ts: version }); - }, []); + const updateTS = useCallback( + (version: string) => { + setState({ ts: version }); + }, + [setState], + ); - const updateRules = useCallback((rules: RulesRecord) => { - setState({ rules: rules }); - setEslintModal(false); - }, []); + const updateRules = useCallback( + (rules?: RulesRecord) => { + if (rules) { + setState({ rules: rules }); + } + setEslintModal(false); + }, + [setState], + ); - const updateTsConfig = useCallback((config: CompilerFlags) => { - setState({ tsConfig: config }); - setTypeScriptModal(false); - }, []); + const updateTsConfig = useCallback( + (config?: CompilerFlags) => { + if (config) { + setState({ tsConfig: config }); + } + setTypeScriptModal(false); + }, + [setState], + ); const copyLinkToClipboard = useCallback(async () => { await navigator.clipboard.writeText(document.location.toString()); diff --git a/packages/website/src/components/config/ConfigEslint.tsx b/packages/website/src/components/config/ConfigEslint.tsx index b14baa69983d..cb64ae0a46ad 100644 --- a/packages/website/src/components/config/ConfigEslint.tsx +++ b/packages/website/src/components/config/ConfigEslint.tsx @@ -2,11 +2,12 @@ import React, { useCallback, useEffect, useState } from 'react'; import type { RulesRecord, RuleEntry } from '@typescript-eslint/website-eslint'; import ConfigEditor, { ConfigOptionsType } from './ConfigEditor'; -import { RuleDetails } from '../types'; +import type { RuleDetails } from '../types'; +import { shallowEqual } from '../lib/shallowEqual'; export interface ModalEslintProps { readonly isOpen: boolean; - readonly onClose: (rules: RulesRecord) => void; + readonly onClose: (value?: RulesRecord) => void; readonly ruleOptions: RuleDetails[]; readonly rules: RulesRecord; } @@ -55,19 +56,22 @@ function ConfigEslint(props: ModalEslintProps): JSX.Element { const onClose = useCallback( (newConfig: Record) => { - props.onClose( - Object.fromEntries( - Object.entries(newConfig) - .map<[string, unknown]>(([name, value]) => - Array.isArray(value) && value.length === 1 - ? [name, value[0]] - : [name, value], - ) - .filter(checkOptions), - ), + const cfg = Object.fromEntries( + Object.entries(newConfig) + .map<[string, unknown]>(([name, value]) => + Array.isArray(value) && value.length === 1 + ? [name, value[0]] + : [name, value], + ) + .filter(checkOptions), ); + if (!shallowEqual(cfg, props.rules)) { + props.onClose(cfg); + } else { + props.onClose(); + } }, - [props.onClose], + [props.onClose, props.rules], ); return ( diff --git a/packages/website/src/components/config/ConfigTypeScript.tsx b/packages/website/src/components/config/ConfigTypeScript.tsx index a86f66aff961..27cc2cbdd97b 100644 --- a/packages/website/src/components/config/ConfigTypeScript.tsx +++ b/packages/website/src/components/config/ConfigTypeScript.tsx @@ -1,13 +1,14 @@ import React, { useCallback } from 'react'; import tsConfigOptions from '../tsConfigOptions.json'; -import type { CompilerFlags } from '../types'; import ConfigEditor from './ConfigEditor'; +import type { CompilerFlags } from '../types'; +import { shallowEqual } from '../lib/shallowEqual'; interface ModalTypeScriptProps { - isOpen: boolean; - onClose: (config: CompilerFlags) => void; - config?: CompilerFlags; + readonly isOpen: boolean; + readonly onClose: (config?: CompilerFlags) => void; + readonly config?: CompilerFlags; } function checkOptions(item: [string, unknown]): item is [string, boolean] { @@ -17,11 +18,16 @@ function checkOptions(item: [string, unknown]): item is [string, boolean] { function ConfigTypeScript(props: ModalTypeScriptProps): JSX.Element { const onClose = useCallback( (newConfig: Record) => { - props.onClose( - Object.fromEntries(Object.entries(newConfig).filter(checkOptions)), + const cfg = Object.fromEntries( + Object.entries(newConfig).filter(checkOptions), ); + if (!shallowEqual(cfg, props.config)) { + props.onClose(cfg); + } else { + props.onClose(); + } }, - [props.onClose], + [props.onClose, props.config], ); return ( diff --git a/packages/website/src/components/hooks/useHashState.ts b/packages/website/src/components/hooks/useHashState.ts index d6a8e8cc8e91..b1263ce04ccb 100644 --- a/packages/website/src/components/hooks/useHashState.ts +++ b/packages/website/src/components/hooks/useHashState.ts @@ -3,6 +3,7 @@ import { useCallback, useEffect, useState } from 'react'; import type { CompilerFlags, ConfigModel, RulesRecord } from '../types'; import * as lz from 'lzstring.ts'; +import { shallowEqual } from '../lib/shallowEqual'; function writeQueryParam(value: string): string { return lz.LZString.compressToEncodedURIComponent(value); @@ -76,26 +77,6 @@ const writeStateToUrl = (newState: ConfigModel): string => { return ''; }; -function shallowEqual( - object1: Record | ConfigModel | undefined, - object2: Record | ConfigModel | undefined, -): boolean { - if (object1 === object2) { - return true; - } - const keys1 = Object.keys(object1 ?? {}); - const keys2 = Object.keys(object2 ?? {}); - if (keys1.length !== keys2.length) { - return false; - } - for (const key of keys1) { - if (object1![key] !== object2![key]) { - return false; - } - } - return true; -} - function useHashState( initialState: ConfigModel, ): [ConfigModel, (cfg: Partial) => void] { diff --git a/packages/website/src/components/lib/shallowEqual.ts b/packages/website/src/components/lib/shallowEqual.ts new file mode 100644 index 000000000000..2f34444c5874 --- /dev/null +++ b/packages/website/src/components/lib/shallowEqual.ts @@ -0,0 +1,21 @@ +import type { ConfigModel } from '@site/src/components/types'; + +export function shallowEqual( + object1: Record | ConfigModel | undefined, + object2: Record | ConfigModel | undefined, +): boolean { + if (object1 === object2) { + return true; + } + const keys1 = Object.keys(object1 ?? {}); + const keys2 = Object.keys(object2 ?? {}); + if (keys1.length !== keys2.length) { + return false; + } + for (const key of keys1) { + if (object1![key] !== object2![key]) { + return false; + } + } + return true; +} diff --git a/packages/website/src/components/modals/Modal.module.css b/packages/website/src/components/modals/Modal.module.css index 45c0099482d2..c0fd7b64119f 100644 --- a/packages/website/src/components/modals/Modal.module.css +++ b/packages/website/src/components/modals/Modal.module.css @@ -39,6 +39,8 @@ .modalClose { cursor: pointer; + background: transparent; + border: none; } .modalClose:hover, diff --git a/packages/website/src/components/modals/Modal.tsx b/packages/website/src/components/modals/Modal.tsx index ff17a0d89f6b..358db020ca81 100644 --- a/packages/website/src/components/modals/Modal.tsx +++ b/packages/website/src/components/modals/Modal.tsx @@ -1,5 +1,5 @@ /* eslint-disable jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */ -import React from 'react'; +import React, { MouseEvent, useCallback, useEffect } from 'react'; import clsx from 'clsx'; import styles from './Modal.module.css'; import CloseIcon from '../icons/CloseIcon'; @@ -12,24 +12,46 @@ interface ModalProps { } function Modal(props: ModalProps): JSX.Element { + const closeOnEscapeKeyDown = useCallback( + (e: KeyboardEvent): void => { + if (e.key === 'Escape' || e.keyCode === 27) { + props.onClose(); + } + }, + [props.onClose], + ); + + useEffect(() => { + document.body.addEventListener('keydown', closeOnEscapeKeyDown); + return (): void => { + document.body.removeEventListener('keydown', closeOnEscapeKeyDown); + }; + }, []); + + const onClick = useCallback( + (e: MouseEvent) => { + if (e.currentTarget === e.target) { + props.onClose(); + } + }, + [props.onClose], + ); + return (
{ - e.stopPropagation(); - }} >

{props.header}

- +
{React.Children.map(props.children, child => child)} From 6191ab76bbd9e90b979d10c7dbf4cf2c269ca16a Mon Sep 17 00:00:00 2001 From: Armano Date: Wed, 1 Dec 2021 04:46:03 +0100 Subject: [PATCH 2/3] docs(website): simplify esc keydown callback --- packages/website/src/components/modals/Modal.tsx | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/website/src/components/modals/Modal.tsx b/packages/website/src/components/modals/Modal.tsx index 358db020ca81..4c7c7c401fe1 100644 --- a/packages/website/src/components/modals/Modal.tsx +++ b/packages/website/src/components/modals/Modal.tsx @@ -12,16 +12,13 @@ interface ModalProps { } function Modal(props: ModalProps): JSX.Element { - const closeOnEscapeKeyDown = useCallback( - (e: KeyboardEvent): void => { + useEffect(() => { + const closeOnEscapeKeyDown = (e: KeyboardEvent): void => { if (e.key === 'Escape' || e.keyCode === 27) { props.onClose(); } - }, - [props.onClose], - ); + }; - useEffect(() => { document.body.addEventListener('keydown', closeOnEscapeKeyDown); return (): void => { document.body.removeEventListener('keydown', closeOnEscapeKeyDown); From 9f6a8997c23540bd1f5456870663237fa1c96a6a Mon Sep 17 00:00:00 2001 From: Armano Date: Wed, 1 Dec 2021 19:06:26 +0100 Subject: [PATCH 3/3] docs(website): add missing aria-label and cleanup styles --- packages/website/src/components/modals/Modal.module.css | 5 ++--- packages/website/src/components/modals/Modal.tsx | 7 ++++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/website/src/components/modals/Modal.module.css b/packages/website/src/components/modals/Modal.module.css index c0fd7b64119f..694b1b113fc5 100644 --- a/packages/website/src/components/modals/Modal.module.css +++ b/packages/website/src/components/modals/Modal.module.css @@ -38,9 +38,8 @@ } .modalClose { - cursor: pointer; - background: transparent; - border: none; + transition: color var(--ifm-transition-fast) + var(--ifm-transition-timing-default); } .modalClose:hover, diff --git a/packages/website/src/components/modals/Modal.tsx b/packages/website/src/components/modals/Modal.tsx index 4c7c7c401fe1..fe380b1ccb6d 100644 --- a/packages/website/src/components/modals/Modal.tsx +++ b/packages/website/src/components/modals/Modal.tsx @@ -46,7 +46,12 @@ function Modal(props: ModalProps): JSX.Element { >

{props.header}

-