From 48d0e4e32e28c3d0bebcbeaa086c3b82312f0848 Mon Sep 17 00:00:00 2001 From: Vladimir Lewandowski Date: Mon, 15 Jan 2024 12:14:13 +0100 Subject: [PATCH] feat(TreeView): remove `react-treeview` package to avoid using native `addEventListener` (#64) --- package-lock.json | 137 ++++++++++-------- package.json | 3 +- src/components/TreeView/TreeView.scss | 16 +- src/components/TreeView/TreeView.tsx | 127 ++++++++-------- .../TreeView/__stories__/TreeView.stories.tsx | 66 ++++++++- 5 files changed, 210 insertions(+), 139 deletions(-) diff --git a/package-lock.json b/package-lock.json index daa0ef0..45cbfc8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2212,7 +2212,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@gravity-ui/prettier-config/-/prettier-config-1.0.1.tgz", "integrity": "sha512-VpTM+OiUMgXjwc7HBo0ekxBFghzELsnE/RFBYwbkbIqm0NrL4SiEPzne1IyEY/WsfI8om1hpn81t+qjGbicMSw==", - "dev": true + "dev": true, + "requires": {} }, "@gravity-ui/stylelint-config": { "version": "1.0.1", @@ -2635,7 +2636,8 @@ "version": "1.6.22", "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-1.6.22.tgz", "integrity": "sha512-TDoPum4SHdfPiGSAaRBw7ECyI8VaHpK8GJugbJIJuqyh6kzw9ZLJZW3HGL3NNrJGxcAixUvqROm+YuQOo5eXtg==", - "dev": true + "dev": true, + "requires": {} }, "@mdx-js/util": { "version": "1.6.22", @@ -4746,7 +4748,8 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/@storybook/preset-scss/-/preset-scss-1.0.3.tgz", "integrity": "sha512-o9Iz6wxPeNENrQa2mKlsDKynBfqU2uWaRP80HeWp4TkGgf7/x3DVF2O7yi9N0x/PI1qzzTTpxlQ90D62XmpiTw==", - "dev": true + "dev": true, + "requires": {} }, "@storybook/preview-web": { "version": "6.4.19", @@ -5716,16 +5719,6 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true }, - "JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", - "dev": true, - "requires": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - } - }, "accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -5746,7 +5739,8 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true + "dev": true, + "requires": {} }, "acorn-walk": { "version": "7.2.0", @@ -5811,13 +5805,15 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", - "dev": true + "dev": true, + "requires": {} }, "ajv-keywords": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true + "dev": true, + "requires": {} }, "ansi-align": { "version": "3.0.1", @@ -6449,7 +6445,8 @@ "version": "0.3.8", "resolved": "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.8.tgz", "integrity": "sha512-WXiAc++qo7XcJ1ZnTYGtLxmBCVbddAml3CEXgWaBzNzLNoxtQ8AiGEFDMOhot9XjTCQbvP5E77Fj9Gk924f00Q==", - "dev": true + "dev": true, + "requires": {} }, "babel-plugin-polyfill-corejs2": { "version": "0.3.1", @@ -7746,8 +7743,8 @@ "integrity": "sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q==", "dev": true, "requires": { - "JSONStream": "^1.0.4", "is-text-path": "^1.0.1", + "JSONStream": "^1.0.4", "lodash": "^4.17.15", "meow": "^8.0.0", "split2": "^3.0.0", @@ -9400,7 +9397,8 @@ "version": "8.3.0", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", - "dev": true + "dev": true, + "requires": {} }, "eslint-plugin-prettier": { "version": "4.0.0", @@ -9445,7 +9443,8 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.3.0.tgz", "integrity": "sha512-XslZy0LnMn+84NEG9jSGR6eGqaZB3133L8xewQo3fQagbQuGt7a63gf+P1NGKZavEYEC3UXaWEAA/AqDkuN6xA==", - "dev": true + "dev": true, + "requires": {} }, "eslint-plugin-security": { "version": "1.4.0", @@ -12152,7 +12151,8 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "dev": true + "dev": true, + "requires": {} }, "ieee754": { "version": "1.2.1", @@ -13027,6 +13027,16 @@ "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", "dev": true }, + "JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, "jsx-ast-utils": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz", @@ -13591,7 +13601,8 @@ "version": "7.1.7", "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.1.7.tgz", "integrity": "sha512-VI3TyyHlGkO8uFle0IOibzpO1c1iJDcXcS/zBrQrXQQvJ2tpdwVzVZ7XdKsyRz1NdRmre4dqQkMZzUHaKIG/1w==", - "dev": true + "dev": true, + "requires": {} }, "matchdep": { "version": "2.0.0", @@ -15658,7 +15669,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", - "dev": true + "dev": true, + "requires": {} }, "postcss-modules-local-by-default": { "version": "4.0.0", @@ -15699,13 +15711,15 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", "integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==", - "dev": true + "dev": true, + "requires": {} }, "postcss-scss": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.2.tgz", "integrity": "sha512-xfdkU128CkKKKVAwkyt0M8OdnelJ3MRcIRAPPQkRpoPeuzWY3RIeg7piRCpZ79MK7Q16diLXMMAD9dN5mauPlQ==", - "dev": true + "dev": true, + "requires": {} }, "postcss-selector-parser": { "version": "6.0.9", @@ -15721,7 +15735,8 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-7.0.1.tgz", "integrity": "sha512-iLBFYz6VRYyLJEJsBJ8M3TCqNcckVzz4wFounSc5Oez35ogE/X+aoC5fFu103Ot7NyvjU3/xqIXn93Gp3kJk4g==", - "dev": true + "dev": true, + "requires": {} }, "postcss-value-parser": { "version": "4.2.0", @@ -16047,7 +16062,6 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "dev": true, "requires": { "loose-envify": "^1.1.0" } @@ -16071,7 +16085,8 @@ "version": "5.5.1", "resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.5.1.tgz", "integrity": "sha512-M1TJH2X3RXEt12sWkpa6hLc/bbYS0H6F4rIqjQZ+RxNBstpY67d9TrFXtqdZwhpmBXcCwEi7stKqFue3ZRkiOg==", - "dev": true + "dev": true, + "requires": {} }, "react-copy-to-clipboard": { "version": "5.1.0", @@ -16116,7 +16131,8 @@ "version": "2.2.2", "resolved": "https://registry.npmjs.org/react-docgen-typescript/-/react-docgen-typescript-2.2.2.tgz", "integrity": "sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg==", - "dev": true + "dev": true, + "requires": {} }, "react-dom": { "version": "18.2.0", @@ -16338,19 +16354,12 @@ "prop-types": "^15.6.2" } }, - "react-treeview": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/react-treeview/-/react-treeview-0.4.7.tgz", - "integrity": "sha1-9kfgT3BJbrEfsJEsNRh+gOtg1Fg=", - "requires": { - "prop-types": "^15.5.8" - } - }, "react-virtualized-auto-sizer": { "version": "1.0.20", "resolved": "https://registry.npmjs.org/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.20.tgz", "integrity": "sha512-OdIyHwj4S4wyhbKHOKM1wLSj/UDXm839Z3Cvfg2a9j+He6yDa6i5p0qQvEiCnyQlGO/HyfSnigQwuxvYalaAXA==", - "dev": true + "dev": true, + "requires": {} }, "react-window": { "version": "1.8.10", @@ -17998,6 +18007,23 @@ "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", "dev": true }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } + } + }, "string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -18067,23 +18093,6 @@ "define-properties": "^1.1.3" } }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "requires": { - "safe-buffer": "~5.2.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - } - } - }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -18245,7 +18254,8 @@ "version": "9.0.3", "resolved": "https://registry.npmjs.org/stylelint-config-prettier/-/stylelint-config-prettier-9.0.3.tgz", "integrity": "sha512-5n9gUDp/n5tTMCq1GLqSpA30w2sqWITSSEiAWQlpxkKGAUbjcemQ0nbkRvRUa0B1LgD3+hCvdL7B1eTxy1QHJg==", - "dev": true + "dev": true, + "requires": {} }, "stylelint-order": { "version": "5.0.0", @@ -19224,13 +19234,15 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.2.1.tgz", "integrity": "sha512-6+X1FLlIcjvFMAeAD/hcxDT8tmyrWnbSPMU0EnxQuDLIxokuFzWliXBiYZuGIx+mrAMLBw0WFfCkaPw8ebzAhw==", - "dev": true + "dev": true, + "requires": {} }, "use-isomorphic-layout-effect": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.1.tgz", "integrity": "sha512-L7Evj8FGcwo/wpbv/qvSfrkHFtOpCzvM5yl2KVyDJoylVuSvzphiiasmjgQPttIGBAy2WKiBNR98q8w7PiNgKQ==", - "dev": true + "dev": true, + "requires": {} }, "use-latest": { "version": "1.2.0", @@ -19245,7 +19257,8 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz", "integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==", - "dev": true + "dev": true, + "requires": {} }, "util": { "version": "0.11.1", @@ -20013,7 +20026,8 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/webpack-filter-warnings-plugin/-/webpack-filter-warnings-plugin-1.2.1.tgz", "integrity": "sha512-Ez6ytc9IseDMLPo0qCuNNYzgtUl8NovOqjIq4uAU8LTD4uoa1w1KpZyyzFtLTEMZpkkOkLfL9eN+KGYdk1Qtwg==", - "dev": true + "dev": true, + "requires": {} }, "webpack-hot-middleware": { "version": "2.25.1", @@ -20218,7 +20232,8 @@ "version": "8.5.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", - "dev": true + "dev": true, + "requires": {} }, "xtend": { "version": "4.0.2", diff --git a/package.json b/package.json index 93e75cf..a62f54c 100644 --- a/package.json +++ b/package.json @@ -33,8 +33,7 @@ "dependencies": { "@gravity-ui/i18n": "^1.0.0", "bem-cn-lite": "^4.1.0", - "react-list": "^0.8.17", - "react-treeview": "^0.4.7" + "react-list": "^0.8.17" }, "devDependencies": { "@babel/core": "^7.17.8", diff --git a/src/components/TreeView/TreeView.scss b/src/components/TreeView/TreeView.scss index 3631681..67b2d1e 100644 --- a/src/components/TreeView/TreeView.scss +++ b/src/components/TreeView/TreeView.scss @@ -79,11 +79,13 @@ $step-offset: 24px; align-items: center; } - .tree-view_arrow { + &__arrow { flex-shrink: 0; width: 24px; height: 24px; + padding: 0; cursor: pointer; + border: none; @include arrow-background(rgba(0, 0, 0, 0.85)); @@ -91,13 +93,17 @@ $step-offset: 24px; @include arrow-background(rgba(255, 255, 255, 0.85)); } - &:not(.tree-view_arrow-collapsed) { + &:focus-visible { + outline: 2px solid var(--g-color-line-focus); + } + + &:not(&_collapsed) { transform: rotate(90deg); } - } - &_no-arrow .tree-view_arrow { - visibility: hidden; + &_hidden { + visibility: hidden; + } } & & &__item { diff --git a/src/components/TreeView/TreeView.tsx b/src/components/TreeView/TreeView.tsx index d8bee4a..6674af6 100644 --- a/src/components/TreeView/TreeView.tsx +++ b/src/components/TreeView/TreeView.tsx @@ -1,8 +1,6 @@ -import React from 'react'; -import block from 'bem-cn-lite'; -import ReactTreeView from 'react-treeview'; import {DropdownMenu, DropdownMenuItemMixed} from '@gravity-ui/uikit'; - +import block from 'bem-cn-lite'; +import React, {MouseEventHandler, useCallback} from 'react'; import './TreeView.scss'; export interface TreeViewProps { @@ -38,72 +36,75 @@ export function TreeView({ additionalNodeElements, level, }: TreeViewProps) { - const rootRef = React.useRef(null); - - const nodeLabel = ( -
- {icon &&
{icon}
} -
- {name} -
- {actions && actions.length > 0 && ( -
- {additionalNodeElements} - -
- )} -
- ); + const handleClick = useCallback( + (event) => { + if (!onClick) return; - React.useEffect(() => { - const rootEl = rootRef.current; - const itemEl = rootEl && rootEl.querySelector(`.${b('item')}`); - - if (!onClick || !itemEl) { - return; - } - - const skipClicksSelector = `.tree-view_arrow, .${b('actions')}`; - - function handleClick(event: MouseEvent) { - const path = event + const shouldSkip = event.nativeEvent .composedPath() - .filter((item) => (item as Node).nodeType === Node.ELEMENT_NODE) as Element[]; - const hasSkipped = path.some((el) => el.matches(skipClicksSelector)); + .some( + (target) => + target instanceof HTMLElement && + ((target.nodeName === 'BUTTON' && !target.hasAttribute('disabled')) || + (target.hasAttribute('tabindex') && target.tabIndex > -1)), + ); - if (!hasSkipped) { - onClick!(); - } - } + if (!shouldSkip) onClick(); + }, + [onClick], + ); - itemEl.addEventListener('click', handleClick); + const handleArrowClick = onArrowClick || onClick; - return () => { - itemEl.removeEventListener('click', handleClick); - }; - }, [onClick]); + /** + * These `className`s are left here for backward compatibility of CSS selectors. + * @link https://github.com/ydb-platform/ydb-ui-components/pull/64 + */ + const itemClassName = 'tree-view_item'; + let arrowClassName = 'tree-view_arrow'; + let containerClassName = 'tree-view_children'; + if (collapsed) { + arrowClassName += ' tree-view_arrow-collapsed'; + containerClassName += ' tree-view_children-collapsed'; + } return ( -
- - {children} - +
+
+
+
+
+ {collapsed ? null : children} +
+
); } diff --git a/src/components/TreeView/__stories__/TreeView.stories.tsx b/src/components/TreeView/__stories__/TreeView.stories.tsx index 1985189..8d94ed4 100644 --- a/src/components/TreeView/__stories__/TreeView.stories.tsx +++ b/src/components/TreeView/__stories__/TreeView.stories.tsx @@ -1,5 +1,6 @@ -import React from 'react'; +import {Button} from '@gravity-ui/uikit'; import {Meta, Story} from '@storybook/react'; +import React, {useState} from 'react'; import {TreeView, TreeViewProps} from '../TreeView'; export default { @@ -7,11 +8,60 @@ export default { component: TreeView, } as Meta; -export const Default: Story = () => ( - - - console.log(1)} /> +export const Default: Story = () => { + const [state, setState] = useState>({'0': true}); + + return ( + + Make + + } + onClick={() => + setState((current) => ({ + ...current, + '0': !current['0'], + })) + } + > + + setState((current) => ({ + ...current, + '1': !current['1'], + })) + } + > + + setState((current) => ({ + ...current, + '1-1': !current['1-1'], + })) + } + /> + + + setState((current) => ({ + ...current, + '2': !current['2'], + })) + } + /> - console.log(2)} /> - -); + ); +};