From 7dfa157ff06d038579ee83458a16c1ca7c390b59 Mon Sep 17 00:00:00 2001 From: SungChul Hong Date: Tue, 6 Aug 2024 13:28:18 +0900 Subject: [PATCH] feat: Introduce `resizable table` component --- package.json | 1 + pnpm-lock.yaml | 28 ++++ react/package.json | 1 + react/pnpm-lock.yaml | 16 +++ react/src/components/BAITable.tsx | 134 ++++++++++++++++++ .../components/KeypairResourcePolicyList.tsx | 1 + 6 files changed, 181 insertions(+) create mode 100644 react/src/components/BAITable.tsx diff --git a/package.json b/package.json index 4793134268..ec6848b791 100644 --- a/package.json +++ b/package.json @@ -125,6 +125,7 @@ "@types/hammerjs": "^2.0.45", "@types/jest": "^29.5.12", "@types/node": "^22.4.1", + "@types/react-resizable": "^3.0.8", "@typescript-eslint/eslint-plugin": "^7.18.0", "@typescript-eslint/parser": "^7.18.0", "@web/dev-server": "^0.4.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 48f0dc13bc..4918a83230 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -270,6 +270,9 @@ importers: '@types/node': specifier: ^22.4.1 version: 22.4.1 + '@types/react-resizable': + specifier: ^3.0.8 + version: 3.0.8 '@typescript-eslint/eslint-plugin': specifier: ^7.18.0 version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0)(typescript@5.5.4) @@ -2189,12 +2192,21 @@ packages: '@types/parse5@6.0.3': resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==} + '@types/prop-types@15.7.12': + resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} + '@types/qs@6.9.15': resolution: {integrity: sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==} '@types/range-parser@1.2.7': resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + '@types/react-resizable@3.0.8': + resolution: {integrity: sha512-Pcvt2eGA7KNXldt1hkhVhAgZ8hK41m0mp89mFgQi7LAAEZiaLgm4fHJ5zbJZ/4m2LVaAyYrrRRv1LHDcrGQanA==} + + '@types/react@18.3.3': + resolution: {integrity: sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==} + '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} @@ -3379,6 +3391,9 @@ packages: resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} engines: {node: '>=8'} + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + custom-error-instance@2.1.1: resolution: {integrity: sha512-p6JFxJc3M4OTD2li2qaHkDCw9SfMw82Ldr6OC9Je1aXiGfhx2W8p3GaoeaGrPJTUN9NirTM/KTxHWMUdR1rsUg==} @@ -10034,10 +10049,21 @@ snapshots: '@types/parse5@6.0.3': {} + '@types/prop-types@15.7.12': {} + '@types/qs@6.9.15': {} '@types/range-parser@1.2.7': {} + '@types/react-resizable@3.0.8': + dependencies: + '@types/react': 18.3.3 + + '@types/react@18.3.3': + dependencies: + '@types/prop-types': 15.7.12 + csstype: 3.1.3 + '@types/resolve@1.20.2': {} '@types/responselike@1.0.3': @@ -11764,6 +11790,8 @@ snapshots: crypto-random-string@2.0.0: {} + csstype@3.1.3: {} + custom-error-instance@2.1.1: {} d@1.0.2: diff --git a/react/package.json b/react/package.json index ae6d392e82..a5db3d65b8 100644 --- a/react/package.json +++ b/react/package.json @@ -44,6 +44,7 @@ "react-i18next": "^14.1.3", "react-markdown": "^9.0.1", "react-relay": "^16.2.0", + "react-resizable": "^3.0.5", "react-router-dom": "^6.26.0", "react-scripts": "5.0.1", "react-syntax-highlighter": "^15.5.0", diff --git a/react/pnpm-lock.yaml b/react/pnpm-lock.yaml index 28f782ead0..8375433fcd 100644 --- a/react/pnpm-lock.yaml +++ b/react/pnpm-lock.yaml @@ -131,6 +131,9 @@ importers: react-relay: specifier: ^16.2.0 version: 16.2.0(react@18.3.1) + react-resizable: + specifier: ^3.0.5 + version: 3.0.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-router-dom: specifier: ^6.26.0 version: 6.26.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -7146,6 +7149,11 @@ packages: peerDependencies: react: ^16.9.0 || ^17 || ^18 + react-resizable@3.0.5: + resolution: {integrity: sha512-vKpeHhI5OZvYn82kXOs1bC8aOXktGU5AmKAgaZS4F5JPburCtbmDPqE7Pzp+1kN4+Wb81LlF33VpGwWwtXem+w==} + peerDependencies: + react: '>= 16.3' + react-router-dom@6.26.0: resolution: {integrity: sha512-RRGUIiDtLrkX3uYcFiCIxKFWMcWQGMojpYZfcstc63A1+sSnVgILGIm9gNUA6na3Fm1QuPGSBQH2EMbAZOnMsQ==} engines: {node: '>=14.0.0'} @@ -17427,6 +17435,14 @@ snapshots: transitivePeerDependencies: - encoding + react-resizable@3.0.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + prop-types: 15.8.1 + react: 18.3.1 + react-draggable: 4.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + transitivePeerDependencies: + - react-dom + react-router-dom@6.26.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@remix-run/router': 1.19.0 diff --git a/react/src/components/BAITable.tsx b/react/src/components/BAITable.tsx new file mode 100644 index 0000000000..de373badf6 --- /dev/null +++ b/react/src/components/BAITable.tsx @@ -0,0 +1,134 @@ +import { Table } from 'antd'; +import { createStyles } from 'antd-style'; +import { ColumnsType } from 'antd/es/table'; +import { TableProps } from 'antd/lib'; +import _ from 'lodash'; +import { useMemo, useState } from 'react'; +import { Resizable, ResizeCallbackData } from 'react-resizable'; + +const useStyles = createStyles(({ token, css }) => ({ + resizableTable: css` + .react-resizable-handle { + position: absolute; + inset-inline-end: 0px; + bottom: 0; + z-index: 1; + width: 10px; + height: 100%; + cursor: col-resize; + } + .ant-table-cell { + overflow: hidden; + whitespace: 'pre'; + wordwrap: 'break-word'; + } + `, +})); + +const ResizableTitle = ( + props: React.HTMLAttributes & { + onResize: ( + e: React.SyntheticEvent, + data: ResizeCallbackData, + ) => void; + width: number; + }, +) => { + const { onResize, width, ...restProps } = props; + + if (!width) { + return ; + } + + return ( + { + e.stopPropagation(); + }} + /> + } + onResize={onResize} + draggableOpts={{ enableUserSelectHack: false }} + > + + + ); +}; + +interface BAITableProps extends Omit { + resizable?: boolean; + columns: ColumnsType; +} + +const BAITable: React.FC = ({ + resizable = false, + columns, + ...tableProps +}) => { + const { styles } = useStyles(); + const [tableColumns, setTableColumns] = useState>( + columns || [], + ); + + useMemo(() => { + if (!resizable) { + setTableColumns(columns); + return; + } + + const resizableColumns = _.map(columns, (col, index) => ({ + ...col, + onHeaderCell: (column: ColumnsType[number]) => { + return { + width: column.width, + onResize: handleResize(index) as React.ReactEventHandler, + }; + }, + })); + + const handleResize = + (index: number) => + (_: React.SyntheticEvent, { size }: ResizeCallbackData) => { + const newColumns = [...resizableColumns]; + newColumns[index] = { + ...newColumns[index], + width: size.width, + onHeaderCell: (column: ColumnsType[number]) => { + return { + width: column.width, + onResize: handleResize(index) as React.ReactEventHandler, + }; + }, + }; + setTableColumns(newColumns); + }; + + setTableColumns(resizableColumns); + }, [resizable, columns]); + + return ( + <> + + + ); +}; + +export default BAITable; diff --git a/react/src/components/KeypairResourcePolicyList.tsx b/react/src/components/KeypairResourcePolicyList.tsx index a501e5eeeb..72c96f57fa 100644 --- a/react/src/components/KeypairResourcePolicyList.tsx +++ b/react/src/components/KeypairResourcePolicyList.tsx @@ -110,6 +110,7 @@ const KeypairResourcePolicyList: React.FC = ( key: 'name', fixed: 'left', sorter: (a, b) => localeCompare(a?.name, b?.name), + // width: 200, }, { title: t('resourcePolicy.Resources'),