From 22cd94b974ea285998e8ce510a06329de48e4f2f 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 | 132 ++++++++++++++++++ .../components/KeypairResourcePolicyList.tsx | 3 + 6 files changed, 181 insertions(+) create mode 100644 react/src/components/BAITable.tsx diff --git a/package.json b/package.json index e6b46b574..88e1f0ad1 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 4535b0fee..38e63b558 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -267,6 +267,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) @@ -2286,12 +2289,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==} @@ -3560,6 +3572,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==} @@ -10778,10 +10793,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': @@ -12590,6 +12616,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 145735aff..27834ecf0 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 acedc95a1..e93464e86 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) @@ -7137,6 +7140,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'} @@ -17418,6 +17426,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 000000000..e88c1dc99 --- /dev/null +++ b/react/src/components/BAITable.tsx @@ -0,0 +1,132 @@ +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; + } + `, +})); + +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 a501e5eee..26de1318a 100644 --- a/react/src/components/KeypairResourcePolicyList.tsx +++ b/react/src/components/KeypairResourcePolicyList.tsx @@ -1,6 +1,7 @@ import { localeCompare, numberSorterWithInfinityValue } from '../helper'; import { exportCSVWithFormattingRules } from '../helper/csv-util'; import { useSuspendedBackendaiClient, useUpdatableState } from '../hooks'; +import BAITable from './BAITable'; import Flex from './Flex'; import KeypairResourcePolicySettingModal, { UNLIMITED_MAX_CONCURRENT_SESSIONS, @@ -110,6 +111,8 @@ const KeypairResourcePolicyList: React.FC = ( key: 'name', fixed: 'left', sorter: (a, b) => localeCompare(a?.name, b?.name), + // to use resiazble table, width should be set e.g.) + // width: 200, }, { title: t('resourcePolicy.Resources'),