diff --git a/.gitignore b/.gitignore index 405df696727..c2f6e984622 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,5 @@ amplify/backend/api/colonycdapp/resolvers/* # Temp schema file generated by the codegen tmp-schema.graphql + +storybook-static diff --git a/src/components/common/ColonyActionsTable/ColonyActionsTable.tsx b/src/components/common/ColonyActionsTable/ColonyActionsTable.tsx index 4f5082277fb..b6fe65d7b01 100644 --- a/src/components/common/ColonyActionsTable/ColonyActionsTable.tsx +++ b/src/components/common/ColonyActionsTable/ColonyActionsTable.tsx @@ -2,7 +2,7 @@ import React, { type FC } from 'react'; import { type ColonyAction } from '~types/graphql.ts'; import { formatText } from '~utils/intl.ts'; -import Table from '~v5/common/Table/index.ts'; +import { Table } from '~v5/common/Table/Table.tsx'; import TableHeader from '~v5/common/TableHeader/TableHeader.tsx'; import { useActionsTableProps } from './hooks/useActionsTableProps.tsx'; @@ -17,7 +17,7 @@ const ColonyActionsTable: FC = ({ actionProps, ...rest }) => { - const { tableProps, renderSubComponent } = useActionsTableProps( + const { tableProps } = useActionsTableProps( rest, actionProps.setSelectedAction, ); @@ -35,7 +35,10 @@ const ColonyActionsTable: FC = ({ )} {...tableProps} - renderSubComponent={renderSubComponent} + borders={{ + type: 'wide', + visible: true, + }} /> ); diff --git a/src/components/common/ColonyActionsTable/RecentActivityTable.tsx b/src/components/common/ColonyActionsTable/RecentActivityTable.tsx index 53a3b2b75df..1cdedb11d46 100644 --- a/src/components/common/ColonyActionsTable/RecentActivityTable.tsx +++ b/src/components/common/ColonyActionsTable/RecentActivityTable.tsx @@ -3,7 +3,7 @@ import React, { type FC } from 'react'; import useActionsCount from '~hooks/useActionsCount.ts'; import useGetSelectedDomainFilter from '~hooks/useGetSelectedDomainFilter.tsx'; import { type ColonyAction } from '~types/graphql.ts'; -import Table from '~v5/common/Table/index.ts'; +import { Table } from '~v5/common/Table/Table.tsx'; import { useActionsTableProps } from './hooks/useActionsTableProps.tsx'; import { useHandleRedoAction } from './hooks/useHandleRedoAction.ts'; @@ -13,6 +13,7 @@ const displayName = 'common.RecentActivityTable'; const RecentActivityTable: FC = ({ actionProps, + pageSize = 10, ...props }) => { const selectedDomain = useGetSelectedDomainFilter(); @@ -23,13 +24,20 @@ const RecentActivityTable: FC = ({ domainId: nativeDomainId, }); - const pageCount = Math.ceil(totalActions / (props.pageSize ?? 10)); + const pageCount = Math.ceil(totalActions / pageSize); - const { tableProps, renderSubComponent } = useActionsTableProps( + const { tableProps } = useActionsTableProps( { ...props, + pageSize, + pagination: { + visible: totalActions > 0, + pageTotalVisible: !loadingActionsCount && pageCount > 1, + }, + overrides: { + pageCount, + }, showUserAvatar: false, - showTotalPagesNumber: !loadingActionsCount, isRecentActivityVariant: true, }, actionProps.setSelectedAction, @@ -41,13 +49,10 @@ const RecentActivityTable: FC = ({ {...tableProps} showTableHead={false} - showTableBorder={false} - renderSubComponent={renderSubComponent} - pageCount={pageCount} - withBorder={false} - withNarrowBorder - alwaysShowPagination={totalActions > 0} - showTotalPagesNumber={pageCount > 1} + borders={{ + type: 'narrow', + visible: false, + }} /> ); }; diff --git a/src/components/common/ColonyActionsTable/hooks/useActionsTableProps.tsx b/src/components/common/ColonyActionsTable/hooks/useActionsTableProps.tsx index 37d90408913..6b54c3a64e9 100644 --- a/src/components/common/ColonyActionsTable/hooks/useActionsTableProps.tsx +++ b/src/components/common/ColonyActionsTable/hooks/useActionsTableProps.tsx @@ -26,11 +26,10 @@ export const useActionsTableProps = ( const { className, pageSize = 10, - additionalPaginationButtonsContent, - showTotalPagesNumber, + pagination, + overrides, showUserAvatar, isRecentActivityVariant, - ...rest } = props; const { searchFilter, selectedFiltersCount } = useFiltersContext(); @@ -49,23 +48,24 @@ export const useActionsTableProps = ( pageNumber, } = useActionsTableData(pageSize); + const getMenuProps = useGetMenuProps({ + setAction, + colonyActions, + colonyActionsLoading, + }); + const columns = useColonyActionsTableColumns({ loading: colonyActionsLoading, loadingMotionStates, refetchMotionStates, showUserAvatar, isRecentActivityVariant, + getMenuProps, }); const { actionSidebarToggle: [, { toggleOn: toggleActionSidebarOn }], } = useActionSidebarContext(); - const getMenuProps = useGetMenuProps({ - setAction, - colonyActions, - colonyActionsLoading, - }); - const renderRowLink = useRenderRowLink( colonyActionsLoading, isRecentActivityVariant, @@ -80,87 +80,101 @@ export const useActionsTableProps = ( const isMobile = useMobile(); - const tableProps = merge( - { - className: clsx( - className, - 'sm:[&_td:first-child]:pl-[1.125rem] sm:[&_td]:pr-[1.125rem] sm:[&_th:first-child]:pl-[1.125rem] sm:[&_th:not(:first-child)]:pl-0 sm:[&_th]:pr-[1.125rem]', - { - 'sm:[&_td]:h-[66px]': isRecentActivityVariant, - 'sm:[&_td]:h-[70px]': !isRecentActivityVariant, - 'sm:[&_tr:hover]:bg-gray-25': - colonyActions.length > 0 && !colonyActionsLoading, + const tableProps = merge({ + className: clsx( + className, + 'sm:[&_td:first-child]:pl-[1.125rem] sm:[&_td]:pr-[1.125rem] sm:[&_th:first-child]:pl-[1.125rem] sm:[&_th:not(:first-child)]:pl-0 sm:[&_th]:pr-[1.125rem]', + { + 'sm:[&_td]:h-[66px]': isRecentActivityVariant, + 'sm:[&_td]:h-[70px]': !isRecentActivityVariant, + 'sm:[&_tr:hover]:bg-gray-25': + colonyActions.length > 0 && !colonyActionsLoading, + }, + ), + overrides: merge( + { + enableSortingRemoval: false, + onSortingChange: setSorting, + state: { + columnVisibility: isMobile + ? { + description: true, + motionState: true, + team: false, + createdAt: false, + [MEATBALL_MENU_COLUMN_ID]: false, + } + : { + description: true, + motionState: true, + team: false, + createdAt: true, + expander: false, + }, + sorting, + pagination: { + pageIndex: pageNumber - 1, + pageSize, + }, }, - ), - enableSortingRemoval: false, - renderCellWrapper: isMobile ? undefined : renderRowLink, - state: { - columnVisibility: isMobile - ? { - description: true, - motionState: true, - team: false, - createdAt: false, - [MEATBALL_MENU_COLUMN_ID]: false, - } - : { - expander: false, - }, - sorting, - pagination: { - pageIndex: pageNumber - 1, - pageSize, + manualPagination: true, + getRowId: (row) => row.transactionHash, + }, + overrides, + ), + pagination: merge( + { + children: colonyActionsLoading ? undefined : pagination?.children, + canNextPage: hasNextPage || colonyActionsLoading, + canPreviousPage: hasPrevPage, + onPageChange: (direction) => { + if (direction === 'previous') { + goToPreviousPage(); + } else { + goToNextPage(); + } }, + disabled: colonyActionsLoading, }, - additionalPaginationButtonsContent: colonyActionsLoading - ? undefined - : additionalPaginationButtonsContent, - onSortingChange: setSorting, - getRowId: (row) => row.transactionHash, - meatBallMenuStaticSize: isRecentActivityVariant ? '2rem' : '3rem', - getMenuProps, - columns, - data: colonyActions, - manualPagination: true, - canNextPage: hasNextPage || colonyActionsLoading, - canPreviousPage: hasPrevPage, - showTotalPagesNumber, - nextPage: goToNextPage, - previousPage: goToPreviousPage, - paginationDisabled: colonyActionsLoading, - getRowCanExpand: () => isMobile, - emptyContent: ( - isMobile, + renderSubComponent, + }, + columns, + renderCellWrapper: isMobile ? undefined : renderRowLink, + data: colonyActions, + emptyContent: ( + toggleActionSidebarOn() - } - withoutButtonIcon - /> - ), - } as TableProps, - rest, - ); + ? 'activityFeedTable.table.search.emptyDescription' + : 'activityFeedTable.table.emptyDescription', + }} + buttonText={ + searchFilter || selectedFiltersCount + ? undefined + : { id: 'activityFeedTable.table.emptyButtonLabel' } + } + onClick={ + searchFilter || selectedFiltersCount + ? undefined + : () => toggleActionSidebarOn() + } + withoutButtonIcon + /> + ), + } as TableProps); - return { tableProps, renderSubComponent }; + return { tableProps }; }; diff --git a/src/components/common/ColonyActionsTable/hooks/useColonyActionsTableColumns.tsx b/src/components/common/ColonyActionsTable/hooks/useColonyActionsTableColumns.tsx index 3504b107b1e..e03ed80d3b9 100644 --- a/src/components/common/ColonyActionsTable/hooks/useColonyActionsTableColumns.tsx +++ b/src/components/common/ColonyActionsTable/hooks/useColonyActionsTableColumns.tsx @@ -9,6 +9,7 @@ import { type ActivityFeedColonyAction } from '~hooks/useActivityFeed/types.ts'; import { getFormattedDateFrom } from '~utils/getFormattedDateFrom.ts'; import { formatText } from '~utils/intl.ts'; import TeamBadge from '~v5/common/Pills/TeamBadge/index.ts'; +import { makeMenuColumn } from '~v5/common/Table/utils.tsx'; import ActionBadge from '../partials/ActionBadge/ActionBadge.tsx'; import ActionDescription from '../partials/ActionDescription/index.ts'; @@ -28,6 +29,7 @@ const useColonyActionsTableColumns = ({ refetchMotionStates, showUserAvatar = true, isRecentActivityVariant = false, + getMenuProps, }) => { const isMobile = useMobile(); @@ -127,6 +129,13 @@ const useColonyActionsTableColumns = ({ ? 'flex items-end' : '', }), + makeMenuColumn({ + helper, + getMenuProps, + cellProps: { + staticSize: isRecentActivityVariant ? '2rem' : '3rem', + }, + }), helper.display({ id: 'expander', staticSize: '2.25rem', @@ -154,6 +163,7 @@ const useColonyActionsTableColumns = ({ loadingMotionStates, refetchMotionStates, showUserAvatar, + getMenuProps, ]); }; diff --git a/src/components/common/ColonyActionsTable/hooks/useGetMenuProps.tsx b/src/components/common/ColonyActionsTable/hooks/useGetMenuProps.tsx index 4a2c44d05c7..2df5845d07b 100644 --- a/src/components/common/ColonyActionsTable/hooks/useGetMenuProps.tsx +++ b/src/components/common/ColonyActionsTable/hooks/useGetMenuProps.tsx @@ -4,6 +4,7 @@ import { ShareNetwork, Repeat, } from '@phosphor-icons/react'; +import { type Row } from '@tanstack/react-table'; import React from 'react'; import { generatePath, useNavigate } from 'react-router-dom'; @@ -17,7 +18,7 @@ import { } from '~routes'; import TransactionLink from '~shared/TransactionLink/index.ts'; import { formatText } from '~utils/intl.ts'; -import { type TableProps } from '~v5/common/Table/types.ts'; +import { type MeatBallMenuProps } from '~v5/shared/MeatBallMenu/types.ts'; import MeatballMenuCopyItem from '../partials/MeatballMenuCopyItem/index.ts'; import { type ColonyActionsTableProps } from '../types.ts'; @@ -44,9 +45,9 @@ export const useGetMenuProps = ({ colonyActionsLoading, }); - const getMenuProps: TableProps['getMenuProps'] = ({ - original: colonyAction, - }) => { + const getMenuProps: ( + row: Row, + ) => MeatBallMenuProps | undefined = ({ original: colonyAction }) => { const { transactionHash } = colonyAction; return { diff --git a/src/components/common/ColonyActionsTable/partials/ActionMobileDescription/ActionMobileDescription.tsx b/src/components/common/ColonyActionsTable/partials/ActionMobileDescription/ActionMobileDescription.tsx index 6ad3067104f..f1e9ce18611 100644 --- a/src/components/common/ColonyActionsTable/partials/ActionMobileDescription/ActionMobileDescription.tsx +++ b/src/components/common/ColonyActionsTable/partials/ActionMobileDescription/ActionMobileDescription.tsx @@ -144,7 +144,7 @@ const ActionMobileDescription: FC = ({ {meatBallMenuProps && ( clsx({ '!text-gray-600': !isMenuOpen }) } diff --git a/src/components/frame/v5/pages/ActivityPage/ActivityPage.tsx b/src/components/frame/v5/pages/ActivityPage/ActivityPage.tsx index 54c4a8cccc3..1ad560bcb7e 100644 --- a/src/components/frame/v5/pages/ActivityPage/ActivityPage.tsx +++ b/src/components/frame/v5/pages/ActivityPage/ActivityPage.tsx @@ -35,7 +35,9 @@ const ActivityPage: FC = () => { defaultValues, }} className="pb-4 [&_tr.expanded-below:not(last-child)_td>*:not(.expandable)]:!pb-2 [&_tr.expanded-below_td]:border-none" - showTotalPagesNumber={false} + pagination={{ + pageTotalVisible: false, + }} /> diff --git a/src/components/frame/v5/pages/BalancePage/partials/BalanceTable/BalanceTable.tsx b/src/components/frame/v5/pages/BalancePage/partials/BalanceTable/BalanceTable.tsx index 0169efda026..efef3e8967c 100644 --- a/src/components/frame/v5/pages/BalancePage/partials/BalanceTable/BalanceTable.tsx +++ b/src/components/frame/v5/pages/BalancePage/partials/BalanceTable/BalanceTable.tsx @@ -9,6 +9,7 @@ import { import { getPaginationRowModel, getSortedRowModel, + type Row, type SortingState, } from '@tanstack/react-table'; import clsx from 'clsx'; @@ -30,10 +31,10 @@ import { ACTION_TYPE_FIELD_NAME } from '~v5/common/ActionSidebar/consts.ts'; import EmptyContent from '~v5/common/EmptyContent/index.ts'; import { AddFundsModal } from '~v5/common/Modals/AddFundsModal/AddFundsModal.tsx'; import { MEATBALL_MENU_COLUMN_ID } from '~v5/common/Table/consts.ts'; -import Table from '~v5/common/Table/index.ts'; -import { type TableProps } from '~v5/common/Table/types.ts'; +import { Table } from '~v5/common/Table/Table.tsx'; import TableHeader from '~v5/common/TableHeader/TableHeader.tsx'; import Link from '~v5/shared/Link/index.ts'; +import { type MeatBallMenuProps } from '~v5/shared/MeatBallMenu/types.ts'; import BalanceFilters from './Filters/BalanceFilters/BalanceFilters.tsx'; import { useBalanceTableColumns, useBalancesData } from './hooks.tsx'; @@ -82,8 +83,9 @@ const BalanceTable: FC = () => { { toggleOn: toggleAddFundsModalOn, toggleOff: toggleAddFundsModalOff }, ] = useToggle(); - const columns = useBalanceTableColumns(nativeToken, nativeTokenStatus); - const getMenuProps: TableProps['getMenuProps'] = ({ + const getMenuProps: ( + row: Row, + ) => MeatBallMenuProps | undefined = ({ original: { token: selectedTokenData, loading }, }) => { if (loading) return undefined; @@ -171,32 +173,49 @@ const BalanceTable: FC = () => { }; }; + const columns = useBalanceTableColumns( + nativeToken, + nativeTokenStatus, + getMenuProps, + ); + + const hasPagination = data.length >= 10; + return ( <> - getRowId={({ token }) => (token ? token.tokenAddress : uniqueId())} - columns={columns} - data={data || []} - state={{ - sorting, - rowSelection, - columnVisibility: { - type: !isMobile, + overrides={{ + getRowId: ({ token }) => (token ? token.tokenAddress : uniqueId()), + state: { + sorting, + rowSelection, + columnVisibility: { + type: !isMobile, + }, }, - }} - initialState={{ - pagination: { - pageSize: 10, + initialState: { + pagination: { + pageIndex: 0, + pageSize: 10, + }, }, + onSortingChange: setSorting, + onRowSelectionChange: setRowSelection, + getSortedRowModel: getSortedRowModel(), + getPaginationRowModel: getPaginationRowModel(), + }} + className={clsx({ + 'pb-4': hasPagination, + })} + columns={columns} + data={data || []} + pagination={{ + visible: hasPagination, + pageNumberVisible: true, }} - showPageNumber={data.length >= 10} - onSortingChange={setSorting} - onRowSelectionChange={setRowSelection} - getSortedRowModel={getSortedRowModel()} - getPaginationRowModel={getPaginationRowModel()} emptyContent={ !tokensDataLength && ( { /> ) } - getMenuProps={getMenuProps} renderCellWrapper={(className, content, { cell }) => (
{ {content}
)} - meatBallMenuStaticSize={isMobile ? '2.25rem' : undefined} /> { export const useBalanceTableColumns = ( nativeToken: Token, nativeTokenStatus?: NativeTokenStatus | null, + getMenuProps?: ( + row: Row, + ) => MeatBallMenuProps | undefined, ): ColumnDef[] => { const isMobile = useMobile(); - const columns: ColumnDef[] = useMemo(() => { - const columnHelper = createColumnHelper(); + const columnHelper = useMemo( + () => createColumnHelper(), + [], + ); + const menuColumn: NullableColumnDef = useMemo( + () => + getMenuProps + ? makeMenuColumn({ + helper: columnHelper, + getMenuProps, + cellProps: { + staticSize: isMobile ? '2.25rem' : undefined, + size: 60, + }, + }) + : null, + [columnHelper, getMenuProps, isMobile], + ); + + const columns: ColumnDef[] = useMemo(() => { return [ columnHelper.display({ id: 'asset', @@ -215,7 +243,7 @@ export const useBalanceTableColumns = ( }, }), ]; - }, [isMobile, nativeToken.tokenAddress, nativeTokenStatus]); + }, [isMobile, nativeToken.tokenAddress, nativeTokenStatus, columnHelper]); - return columns; + return menuColumn ? [...columns, menuColumn] : columns; }; diff --git a/src/components/frame/v5/pages/FundsPage/partials/FundsTable/FundsTable.tsx b/src/components/frame/v5/pages/FundsPage/partials/FundsTable/FundsTable.tsx index 40cb835f34d..21984997f85 100644 --- a/src/components/frame/v5/pages/FundsPage/partials/FundsTable/FundsTable.tsx +++ b/src/components/frame/v5/pages/FundsPage/partials/FundsTable/FundsTable.tsx @@ -3,6 +3,7 @@ import { getFilteredRowModel, getPaginationRowModel, } from '@tanstack/react-table'; +import clsx from 'clsx'; import React, { type FC } from 'react'; import { IncomingFundsLoadingContextProvider } from '~frame/v5/pages/FundsPage/context/IncomingFundsLoadingContextProvider.tsx'; @@ -10,7 +11,7 @@ import { useMobile } from '~hooks'; import useColonyFundsClaims from '~hooks/useColonyFundsClaims.ts'; import { formatText } from '~utils/intl.ts'; import EmptyContent from '~v5/common/EmptyContent/index.ts'; -import Table from '~v5/common/Table/index.ts'; +import { Table } from '~v5/common/Table/Table.tsx'; import TableHeader from '~v5/common/TableHeader/TableHeader.tsx'; import CloseButton from '~v5/shared/Button/CloseButton.tsx'; @@ -83,14 +84,22 @@ const FundsTable: FC = () => { data={searchedTokens} columns={columns} - initialState={{ - pagination: { - pageSize: 10, + overrides={{ + initialState: { + pagination: { + pageIndex: 0, + pageSize: 10, + }, }, + getFilteredRowModel: getFilteredRowModel(), + getPaginationRowModel: getPaginationRowModel(), }} - getFilteredRowModel={getFilteredRowModel()} - getPaginationRowModel={getPaginationRowModel()} - className="w-full [&_td>div]:p-0 [&_td]:border-b [&_td]:border-gray-100 [&_th:empty]:border-none [&_th:last-child]:text-right [&_tr:last-child>td]:border-0" + className={clsx( + 'w-full [&_td>div]:p-0 [&_td]:border-b [&_td]:border-gray-100 [&_th:empty]:border-none [&_th:last-child]:text-right [&_tr:last-child>td]:border-0', + { + 'pb-4': searchedTokens.length > 10, + }, + )} emptyContent={ (!searchedTokens.length || claims.length <= 0) && (
diff --git a/src/components/frame/v5/pages/FundsPage/partials/TokenTable/TokenTable.tsx b/src/components/frame/v5/pages/FundsPage/partials/TokenTable/TokenTable.tsx index c8a6be1090a..73928ad3b82 100644 --- a/src/components/frame/v5/pages/FundsPage/partials/TokenTable/TokenTable.tsx +++ b/src/components/frame/v5/pages/FundsPage/partials/TokenTable/TokenTable.tsx @@ -18,7 +18,7 @@ import Numeral from '~shared/Numeral/index.ts'; import TokenInfo from '~shared/TokenInfo/index.ts'; import { formatText } from '~utils/intl.ts'; import PillsBase from '~v5/common/Pills/index.ts'; -import Table from '~v5/common/Table/index.ts'; +import { Table } from '~v5/common/Table/Table.tsx'; import AccordionItem from '~v5/shared/Accordion/partials/AccordionItem/index.ts'; import MenuContainer from '~v5/shared/MenuContainer/index.ts'; import Modal from '~v5/shared/Modal/index.ts'; @@ -136,29 +136,37 @@ const TokenTable: FC = ({ token }) => { > 10, + }, + )} tableClassName="rounded-none border-none" - initialState={{ - sorting: [ - { - id: 'isClaimed', - desc: true, - }, - { - id: 'amount', - desc: true, + overrides={{ + initialState: { + sorting: [ + { + id: 'isClaimed', + desc: true, + }, + { + id: 'amount', + desc: true, + }, + ], + pagination: { + pageIndex: 0, + pageSize: 10, }, - ], - pagination: { - pageSize: 10, }, + // This ensures that all sort actions made by the user are multisort. + // In other words, it's always sorting by all columns. + isMultiSortEvent: () => true, + getSortedRowModel: getSortedRowModel(), + getPaginationRowModel: getPaginationRowModel(), }} - // This ensures that all sort actions made by the user are multisort. - // In other words, it's always sorting by all columns. - isMultiSortEvent={() => true} /> {isTokenNativeChainToken && (
diff --git a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/FiatTransfersTable/FiatTransfersTable.tsx b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/FiatTransfersTable/FiatTransfersTable.tsx index a896e2dac97..41901e3214d 100644 --- a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/FiatTransfersTable/FiatTransfersTable.tsx +++ b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/FiatTransfersTable/FiatTransfersTable.tsx @@ -10,7 +10,7 @@ import { defineMessages, useIntl } from 'react-intl'; import { useMobile } from '~hooks/index.ts'; import { type BridgeDrain } from '~types/graphql.ts'; import EmptyContent from '~v5/common/EmptyContent/index.ts'; -import Table from '~v5/common/Table/index.ts'; +import { Table } from '~v5/common/Table/Table.tsx'; import TableHeader from '~v5/common/TableHeader/TableHeader.tsx'; import FiatTransferDescription from './FiatTransferDescription.tsx'; @@ -54,27 +54,35 @@ const FiatTransfersTable = () => { columns={columns} data={loading ? Array(3).fill({}) : sortedData} - state={{ - columnVisibility: isMobile - ? { - createdAt: false, - receipt: false, - } - : { - expander: false, - }, - sorting, + overrides={{ + getRowId: (row) => row.id, + initialState: { + pagination: { pageIndex: 0, pageSize: 10 }, + }, + state: { + columnVisibility: isMobile + ? { + createdAt: false, + receipt: false, + } + : { + expander: false, + }, + sorting, + }, + onSortingChange: setSorting, + getSortedRowModel: getSortedRowModel(), + getPaginationRowModel: getPaginationRowModel(), + }} + pagination={{ + pageNumberVisible: sortedData.length >= 10, + }} + rows={{ + canExpand: () => isMobile, + renderSubComponent: ({ row }) => ( + + ), }} - getRowId={(row) => row.id} - initialState={{ pagination: { pageSize: 10 } }} - showPageNumber={sortedData.length >= 10} - onSortingChange={setSorting} - getSortedRowModel={getSortedRowModel()} - getPaginationRowModel={getPaginationRowModel()} - getRowCanExpand={() => isMobile} - renderSubComponent={({ row }) => ( - - )} className="pb-5 [&_td:nth-child(1)>div]:font-medium [&_td:nth-child(1)>div]:text-gray-900 [&_td:nth-child(2)>div]:text-gray-600 [&_tr.expanded-below:not(last-child)_td>*:not(.expandable)]:!pb-2 [&_tr.expanded-below_td]:border-none" emptyContent={ !sortedData.length && diff --git a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/FiatTransfersTable/hooks.tsx b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/FiatTransfersTable/hooks.tsx index 50b1666bc0a..9e801bbb92f 100644 --- a/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/FiatTransfersTable/hooks.tsx +++ b/src/components/frame/v5/pages/UserCryptoToFiatPage/partials/FiatTransfersTable/hooks.tsx @@ -55,7 +55,7 @@ export const useFiatTransfersTableColumns = ( loading: boolean, ): ColumnDef[] => { const isMobile = useMobile(); - const columnHelper = createColumnHelper(); + const columnHelper = useMemo(() => createColumnHelper(), []); return useMemo(() => { return [ diff --git a/src/components/frame/v5/pages/VerifiedPage/partials/VerifiedTable/VerifiedTable.tsx b/src/components/frame/v5/pages/VerifiedPage/partials/VerifiedTable/VerifiedTable.tsx index bdb878ea9c9..b9f1a561288 100644 --- a/src/components/frame/v5/pages/VerifiedPage/partials/VerifiedTable/VerifiedTable.tsx +++ b/src/components/frame/v5/pages/VerifiedPage/partials/VerifiedTable/VerifiedTable.tsx @@ -13,7 +13,7 @@ import { useMobile } from '~hooks/index.ts'; import { formatText } from '~utils/intl.ts'; import EmptyContent from '~v5/common/EmptyContent/index.ts'; import Filter from '~v5/common/Filter/index.ts'; -import Table from '~v5/common/Table/index.ts'; +import { Table } from '~v5/common/Table/Table.tsx'; import TableHeader from '~v5/common/TableHeader/TableHeader.tsx'; import Button from '~v5/shared/Button/index.ts'; @@ -73,48 +73,48 @@ const VerifiedTable: FC = ({ list }) => { + columns={columns} + data={list} + className={clsx('w-full', { 'pb-4': listLength > 10 })} emptyContent={ !listLength && ( -
- -
+ ) } - className="w-full" - getRowId={({ contributorAddress }) => contributorAddress} - columns={columns} - data={list} - state={{ - sorting, - rowSelection, - columnVisibility: { - colonyReputationPercentage: !isMobile, - status: !isMobile, - }, - }} - initialState={{ - pagination: { - pageSize: 10, - }, - }} - onSortingChange={setSorting} - onRowSelectionChange={setRowSelection} - getSortedRowModel={getSortedRowModel()} - getPaginationRowModel={getPaginationRowModel()} - enableSortingRemoval={false} - enableSorting - sortDescFirst={false} - sizeUnit="px" renderCellWrapper={(classNames, content) => (
{content}
)} + overrides={{ + getRowId: ({ contributorAddress }) => contributorAddress, + state: { + sorting, + rowSelection, + columnVisibility: { + reputation: !isMobile, + status: !isMobile, + }, + }, + initialState: { + pagination: { + pageIndex: 0, + pageSize: 10, + }, + }, + onSortingChange: setSorting, + onRowSelectionChange: setRowSelection, + getSortedRowModel: getSortedRowModel(), + getPaginationRowModel: getPaginationRowModel(), + enableSortingRemoval: false, + enableSorting: true, + sortDescFirst: false, + }} /> ); diff --git a/src/components/v5/common/ActionSidebar/partials/BatchPaymentsTable/BatchPaymentsTable.tsx b/src/components/v5/common/ActionSidebar/partials/BatchPaymentsTable/BatchPaymentsTable.tsx index b8ed5b402d5..27040faa188 100644 --- a/src/components/v5/common/ActionSidebar/partials/BatchPaymentsTable/BatchPaymentsTable.tsx +++ b/src/components/v5/common/ActionSidebar/partials/BatchPaymentsTable/BatchPaymentsTable.tsx @@ -5,7 +5,9 @@ import { useFieldArray, useFormContext } from 'react-hook-form'; import { useMobile } from '~hooks/index.ts'; import { formatText } from '~utils/intl.ts'; -import Table from '~v5/common/Table/index.ts'; +import { MEATBALL_MENU_COLUMN_ID } from '~v5/common/Table/consts.ts'; +import { Table } from '~v5/common/Table/Table.tsx'; +import { getMoreActionsMenu } from '~v5/common/Table/utils.tsx'; import Button from '~v5/shared/Button/index.ts'; import { useBatchPaymentsTableColumns } from './hooks.tsx'; @@ -28,7 +30,6 @@ const BatchPaymentsTable: FC = ({ name }) => { ); const { getFieldState } = useFormContext(); const fieldState = getFieldState(name); - const columns = useBatchPaymentsTableColumns(); const getMenuProps = ({ index }) => ({ cardClassName: 'min-w-[9.625rem] whitespace-nowrap', items: [ @@ -40,6 +41,7 @@ const BatchPaymentsTable: FC = ({ name }) => { }, ], }); + const columns = useBatchPaymentsTableColumns(getMenuProps); return (
@@ -52,11 +54,21 @@ const BatchPaymentsTable: FC = ({ name }) => { className={clsx('mb-6', { '!border-negative-400': !!fieldState.error, })} - getRowId={({ key }) => key} columns={columns} data={data} - getMenuProps={getMenuProps} - verticalLayout={isMobile} + layout={isMobile ? 'vertical' : 'horizontal'} + overrides={{ + getRowId: ({ key }) => key, + state: { + columnVisibility: { + [MEATBALL_MENU_COLUMN_ID]: !isMobile, + }, + }, + }} + moreActions={getMoreActionsMenu({ + getMenuProps, + visible: isMobile, + })} /> )} diff --git a/src/components/v5/common/ActionSidebar/partials/BatchPaymentsTable/hooks.tsx b/src/components/v5/common/ActionSidebar/partials/BatchPaymentsTable/hooks.tsx index 98d569821dd..d0b342c123e 100644 --- a/src/components/v5/common/ActionSidebar/partials/BatchPaymentsTable/hooks.tsx +++ b/src/components/v5/common/ActionSidebar/partials/BatchPaymentsTable/hooks.tsx @@ -1,14 +1,21 @@ -import { createColumnHelper, type ColumnDef } from '@tanstack/react-table'; +import { + createColumnHelper, + type Row, + type ColumnDef, +} from '@tanstack/react-table'; import { useMemo } from 'react'; import { formatText } from '~utils/intl.ts'; +import { makeMenuColumn } from '~v5/common/Table/utils.tsx'; +import { type MeatBallMenuProps } from '~v5/shared/MeatBallMenu/types.ts'; import { type BatchPaymentsTableModel } from './types.ts'; -export const useBatchPaymentsTableColumns = (): ColumnDef< - BatchPaymentsTableModel, - string ->[] => { +export const useBatchPaymentsTableColumns = ( + getMenuProps: ( + row: Row, + ) => MeatBallMenuProps | undefined, +): ColumnDef[] => { const columnHelper = useMemo( () => createColumnHelper(), [], @@ -37,8 +44,12 @@ export const useBatchPaymentsTableColumns = (): ColumnDef< // @TODO: display data }, }), + makeMenuColumn({ + helper: columnHelper, + getMenuProps, + }), ], - [columnHelper], + [columnHelper, getMenuProps], ); return columns; diff --git a/src/components/v5/common/ActionSidebar/partials/MembersTable/MembersTable.tsx b/src/components/v5/common/ActionSidebar/partials/MembersTable/MembersTable.tsx index 90f3160a1b2..1599e9abf4e 100644 --- a/src/components/v5/common/ActionSidebar/partials/MembersTable/MembersTable.tsx +++ b/src/components/v5/common/ActionSidebar/partials/MembersTable/MembersTable.tsx @@ -8,7 +8,7 @@ import { useAdditionalFormOptionsContext } from '~context/AdditionalFormOptionsC import { useMobile } from '~hooks/index.ts'; import { type ManageVerifiedMembersOperation } from '~types'; import { formatText } from '~utils/intl.ts'; -import Table from '~v5/common/Table/Table.tsx'; +import { Table } from '~v5/common/Table/Table.tsx'; import Button from '~v5/shared/Button/Button.tsx'; import { useVerifiedMembersTableColumns } from './hooks.tsx'; @@ -33,11 +33,6 @@ const VerifiedMembersTable: FC = ({ name }) => { }); const { getFieldState, getValues } = useFormContext(); const { readonly } = useAdditionalFormOptionsContext(); - const columns = useVerifiedMembersTableColumns( - name, - fieldArrayMethods, - manageMembers, - ); const isMobile = useMobile(); const isEmptyData = getValues(name)?.[0]?.value === undefined; const getMenuProps = ({ index }) => { @@ -45,7 +40,7 @@ const VerifiedMembersTable: FC = ({ name }) => { ? { cardClassName: 'min-w-[9.625rem] whitespace-nowrap', contentWrapperClassName: clsx({ - '!left-6 right-6': isMobile, + '!left-6 right-6': !isMobile, }), items: [ { @@ -58,6 +53,13 @@ const VerifiedMembersTable: FC = ({ name }) => { } : undefined; }; + const columns = useVerifiedMembersTableColumns({ + name, + fieldArrayMethods, + manageMembers, + getMenuProps, + }); + const fieldState = getFieldState(name); useEffect(() => { @@ -75,11 +77,22 @@ const VerifiedMembersTable: FC = ({ name }) => { className={clsx({ '!border-negative-400': !!fieldState.error, })} - withBorder={false} - getRowId={({ key }) => key} columns={columns} data={data} - getMenuProps={getMenuProps} + borders={{ + visible: true, + type: 'unset', + }} + moreActions={{}} + overrides={{ + getRowId: ({ key }) => key, + initialState: { + pagination: { + pageIndex: 0, + pageSize: 400, + }, + }, + }} /> {!readonly && (
); diff --git a/src/components/v5/common/CompletedAction/partials/SplitPayment/partials/SplitPaymentTable/SplitPaymentTable.tsx b/src/components/v5/common/CompletedAction/partials/SplitPayment/partials/SplitPaymentTable/SplitPaymentTable.tsx index 9d522309bc3..5f47558f6ff 100644 --- a/src/components/v5/common/CompletedAction/partials/SplitPayment/partials/SplitPaymentTable/SplitPaymentTable.tsx +++ b/src/components/v5/common/CompletedAction/partials/SplitPayment/partials/SplitPaymentTable/SplitPaymentTable.tsx @@ -3,7 +3,8 @@ import React, { type FC, useMemo } from 'react'; import { useTablet } from '~hooks'; import { formatText } from '~utils/intl.ts'; -import Table from '~v5/common/Table/index.ts'; +import { Table } from '~v5/common/Table/Table.tsx'; +import { renderCellContent } from '~v5/common/Table/utils.tsx'; import { useGetSplitPaymentColumns } from './hooks.tsx'; import { @@ -80,9 +81,12 @@ const SplitPaymentTable: FC = ({ : data } columns={columns} - renderCellWrapper={(_, content) => content} - verticalLayout={isTablet} - withBorder={false} + renderCellWrapper={renderCellContent} + layout={isTablet ? 'vertical' : 'horizontal'} + borders={{ + visible: true, + type: 'unset', + }} />
); diff --git a/src/components/v5/common/CompletedAction/partials/SplitPayment/partials/SplitPaymentTable/hooks.tsx b/src/components/v5/common/CompletedAction/partials/SplitPayment/partials/SplitPaymentTable/hooks.tsx index bcfc677d2d2..2c6b98182d5 100644 --- a/src/components/v5/common/CompletedAction/partials/SplitPayment/partials/SplitPaymentTable/hooks.tsx +++ b/src/components/v5/common/CompletedAction/partials/SplitPayment/partials/SplitPaymentTable/hooks.tsx @@ -22,7 +22,10 @@ export const useGetSplitPaymentColumns = ( isLoading?: boolean, ) => { const isTablet = useTablet(); - const splitPaymentColumnHelper = createColumnHelper(); + const splitPaymentColumnHelper = useMemo( + () => createColumnHelper(), + [], + ); const { colony } = useColonyContext(); const { loading: isColonyContributorDataLoading } = useMemberContext(); const isDataLoading = isLoading || isColonyContributorDataLoading; diff --git a/src/components/v5/common/CompletedAction/partials/rows/PermissionsTable.tsx b/src/components/v5/common/CompletedAction/partials/rows/PermissionsTable.tsx index d958d2e113d..3a5983da368 100644 --- a/src/components/v5/common/CompletedAction/partials/rows/PermissionsTable.tsx +++ b/src/components/v5/common/CompletedAction/partials/rows/PermissionsTable.tsx @@ -11,7 +11,7 @@ import { } from '~types/permissions.ts'; import { CUSTOM_PERMISSION_TABLE_CONTENT } from '~utils/colonyActions.ts'; import { UserRoleModifier } from '~v5/common/ActionSidebar/partials/forms/ManagePermissionsForm/consts.ts'; -import Table from '~v5/common/Table/index.ts'; +import { Table } from '~v5/common/Table/Table.tsx'; import { getCustomPermissionsTableColumns } from './utils.tsx'; @@ -67,7 +67,7 @@ const PermissionsTable = ({ )} data={ALLOWED_CUSTOM_PERMISSIONS_TABLE_CONTENT} columns={customPermissionsTableColumns} - verticalLayout={isMobile} + layout={isMobile ? 'vertical' : 'horizontal'} /> )} diff --git a/src/components/v5/common/CompletedAction/partials/rows/SocialLinksTable.tsx b/src/components/v5/common/CompletedAction/partials/rows/SocialLinksTable.tsx index aff5c37127f..72d1b5f636b 100644 --- a/src/components/v5/common/CompletedAction/partials/rows/SocialLinksTable.tsx +++ b/src/components/v5/common/CompletedAction/partials/rows/SocialLinksTable.tsx @@ -5,7 +5,7 @@ import { useMobile } from '~hooks/index.ts'; import { useSocialLinksTableColumns } from '~hooks/useSocialLinksTableColumns.tsx'; import { type SocialLinksTableModel } from '~types/colony.ts'; import { formatText } from '~utils/intl.ts'; -import Table from '~v5/common/Table/index.ts'; +import { Table } from '~v5/common/Table/Table.tsx'; const displayName = 'v5.common.ActionsContent.partials.SocialLinksTable'; @@ -30,13 +30,16 @@ const SocialLinksTable = ({ socialLinks }: Props) => { {formatText({ id: 'editColony.socialLinks.table.title' })} - sizeUnit={isMobile ? undefined : '%'} - meatBallMenuSize={isMobile ? undefined : 10} - getRowId={({ key }) => key} + overrides={{ + getRowId: ({ key }) => key, + }} + borders={{ + type: 'unset', + visible: true, + }} + layout={isMobile ? 'vertical' : 'horizontal'} columns={columns} data={data} - verticalLayout={isMobile} - withBorder={false} /> )} diff --git a/src/components/v5/common/Table/Table.tsx b/src/components/v5/common/Table/Table.tsx index 9252f1e83a2..98ac538ef59 100644 --- a/src/components/v5/common/Table/Table.tsx +++ b/src/components/v5/common/Table/Table.tsx @@ -1,191 +1,58 @@ -import { - useReactTable, - getCoreRowModel as libGetCoreRowModel, - createColumnHelper, - getExpandedRowModel, -} from '@tanstack/react-table'; -import clsx from 'clsx'; -import React, { useEffect, useMemo } from 'react'; +import React from 'react'; -import { useMobile } from '~hooks'; import { formatText } from '~utils/intl.ts'; -import { TableFooter } from './partials/TableFooter.tsx'; -import { TableLayout } from './partials/TableLayout/TableLayout.tsx'; -import TablePagination from './partials/TablePagination/index.ts'; +import { useTable } from './hooks.ts'; +import { BaseTable } from './partials/BaseTable.tsx'; +import TablePagination from './partials/TablePagination/TablePagination.tsx'; import { type TableProps } from './types.ts'; -import { getDefaultRenderCellWrapper, makeMenuColumn } from './utils.tsx'; +import { getPaginationConfig } from './utils.tsx'; -const displayName = 'v5.common.Table'; +export const Table = (props: TableProps) => { + const { pagination = {}, ...rest } = props; + const { pageTotalVisible = true, children } = pagination; + const { data, rows, columns, overrides = {} } = rest; -const Table = ({ - className, - getCoreRowModel, - getRowClassName = () => undefined, - sizeUnit = 'px', - canNextPage, - canPreviousPage, - previousPage, - nextPage, - showPageNumber = true, - showTotalPagesNumber = true, - paginationDisabled, - renderCellWrapper = getDefaultRenderCellWrapper(), - additionalPaginationButtonsContent, - emptyContent, - data, - getMenuProps, - meatBallMenuSize = 60, - meatBallMenuStaticSize, - columns, - renderSubComponent, - getRowCanExpand, - withBorder = true, - withNarrowBorder = false, - isDisabled = false, - verticalLayout, - virtualizedProps, - tableClassName, - tableBodyRowKeyProp, - showTableHead = true, - showTableBorder = true, - alwaysShowPagination = false, - footerColSpan, - ...rest -}: TableProps) => { - const helper = useMemo(() => createColumnHelper(), []); - const isMobile = useMobile(); - - const columnsWithMenu = useMemo( - () => [ - ...columns, - ...(getMenuProps && !verticalLayout - ? [ - makeMenuColumn({ - helper, - getMenuProps, - meatBallMenuSize, - meatBallMenuStaticSize, - }), - ] - : []), - ], - [ - columns, - getMenuProps, - verticalLayout, - helper, - meatBallMenuSize, - meatBallMenuStaticSize, - ], - ); - - const table = useReactTable({ - getCoreRowModel: getCoreRowModel || libGetCoreRowModel(), + const table = useTable({ data, - columns: columnsWithMenu, - enableSortingRemoval: false, - getRowCanExpand, - getExpandedRowModel: getExpandedRowModel(), - ...rest, + rows, + overrides, + columns, }); - const { rows } = table.getRowModel(); - const headerGroups = table.getHeaderGroups(); - const footerGroups = table.getFooterGroups(); - const goToNextPage = nextPage || table.nextPage; - const goToPreviousPage = previousPage || table.previousPage; - const canGoToNextPage = - canNextPage === undefined ? table.getCanNextPage() : canNextPage; - const canGoToPreviousPage = - canPreviousPage === undefined - ? table.getCanPreviousPage() - : canPreviousPage; - const pageCount = table.getPageCount(); - const hasPagination = pageCount > 1 || canGoToNextPage || canGoToPreviousPage; - const showPagination = - alwaysShowPagination || (hasPagination && showPageNumber); - const totalColumnsCount = table.getVisibleFlatColumns().length; - const hasExpandableRows = !!renderSubComponent; - useEffect(() => { - if (!isMobile && hasExpandableRows) { - rows.forEach((row) => { - if (row.getIsExpanded()) { - row.toggleExpanded(false); - } - }); - } - }, [isMobile, hasExpandableRows, rows]); + const { + disabled, + showPagination, + actualPage, + pageNumber, + goToNextPage, + goToPreviousPage, + canGoToNextPage, + canGoToPreviousPage, + } = getPaginationConfig(table, pagination); return ( -
-
- - -
- {showPagination && ( + + {showPagination ? ( {}} - onPrevClick={hasPagination ? goToPreviousPage : () => {}} - canGoToNextPage={ - hasPagination ? canGoToNextPage : alwaysShowPagination - } - canGoToPreviousPage={hasPagination ? canGoToPreviousPage : false} + onNextClick={goToNextPage} + onPrevClick={goToPreviousPage} + canGoToNextPage={canGoToNextPage} + canGoToPreviousPage={canGoToPreviousPage} pageNumberLabel={formatText( { - id: showTotalPagesNumber - ? 'table.pageNumberWithTotal' - : 'table.pageNumber', + id: `table.${pageTotalVisible ? 'pageNumberWithTotal' : 'pageNumber'}`, }, { - actualPage: hasPagination - ? table.getState().pagination.pageIndex + 1 - : 1, - pageNumber: hasPagination ? pageCount : 1, + actualPage, + pageNumber, }, )} - disabled={!hasPagination || paginationDisabled} + disabled={disabled} > - {additionalPaginationButtonsContent} + {children} - )} -
+ ) : null} + ); }; - -Table.displayName = displayName; - -export default Table; diff --git a/src/components/v5/common/Table/hooks.ts b/src/components/v5/common/Table/hooks.ts new file mode 100644 index 00000000000..dd0a7a026ea --- /dev/null +++ b/src/components/v5/common/Table/hooks.ts @@ -0,0 +1,36 @@ +import { + useReactTable, + getExpandedRowModel, + getCoreRowModel as libGetCoreRowModel, + type Row, +} from '@tanstack/react-table'; +import { useEffect } from 'react'; + +export const useTable = ({ data, columns, rows, overrides }) => { + const table = useReactTable({ + getCoreRowModel: libGetCoreRowModel(), + data, + columns, + enableSortingRemoval: false, + getRowCanExpand: rows?.canExpand, + getExpandedRowModel: getExpandedRowModel(), + ...overrides, + }); + + return table; +}; + +export const useCollapseRows = ( + rows: Row[], + isSomeRowsExpanded: boolean, +) => { + useEffect(() => { + if (isSomeRowsExpanded) { + rows.forEach((row) => { + if (row.getIsExpanded() && !row.getCanExpand()) { + row.toggleExpanded(false); + } + }); + } + }, [isSomeRowsExpanded, rows]); +}; diff --git a/src/components/v5/common/Table/index.ts b/src/components/v5/common/Table/index.ts index c2b29e4b1fb..6157ef92912 100644 --- a/src/components/v5/common/Table/index.ts +++ b/src/components/v5/common/Table/index.ts @@ -1 +1 @@ -export { default } from './Table.tsx'; +export { Table } from './Table.tsx'; diff --git a/src/components/v5/common/Table/partials/BaseTable.tsx b/src/components/v5/common/Table/partials/BaseTable.tsx new file mode 100644 index 00000000000..bdcbddb2434 --- /dev/null +++ b/src/components/v5/common/Table/partials/BaseTable.tsx @@ -0,0 +1,75 @@ +import clsx from 'clsx'; +import React from 'react'; + +import { TableFooter } from '~v5/common/Table/partials/TableFooter.tsx'; +import { getDefaultRenderCellWrapper } from '~v5/common/Table/utils.tsx'; + +import { + type HorizontalTableLayoutProps, + type VerticalTableLayoutProps, + type BaseTableProps, +} from '../types.ts'; + +import { HorizontalTableLayout } from './TableLayout/HorizontalTableLayout.tsx'; +import { VerticalTableLayout } from './TableLayout/VerticalTableLayout.tsx'; + +export const BaseTable = ({ + className, + tableClassName, + borders = { + visible: true, + type: 'wide', + }, + layout = 'horizontal', + footerColSpan, + table, + renderCellWrapper = getDefaultRenderCellWrapper(), + isDisabled = false, + showTableHead = true, + children, + ...rest +}: BaseTableProps) => { + return ( +
+ + {layout === 'vertical' ? ( + )} + /> + ) : ( + )} + /> + )} + +
+ {children} +
+ ); +}; diff --git a/src/components/v5/common/Table/partials/EmptyContentWrapper.tsx b/src/components/v5/common/Table/partials/EmptyContentWrapper.tsx new file mode 100644 index 00000000000..c2539b01174 --- /dev/null +++ b/src/components/v5/common/Table/partials/EmptyContentWrapper.tsx @@ -0,0 +1,20 @@ +import React, { type FC, type PropsWithChildren } from 'react'; + +interface EmptyContentWrapperProps extends PropsWithChildren { + colSpan: number; +} + +export const EmptyContentWrapper: FC = ({ + colSpan, + children, +}) => { + return ( + + +
+ {children} +
+ + + ); +}; diff --git a/src/components/v5/common/Table/partials/ExpandableRowWrapper.tsx b/src/components/v5/common/Table/partials/ExpandableRowWrapper.tsx new file mode 100644 index 00000000000..fd165bef68c --- /dev/null +++ b/src/components/v5/common/Table/partials/ExpandableRowWrapper.tsx @@ -0,0 +1,26 @@ +import clsx from 'clsx'; +import React, { type PropsWithChildren, type FC } from 'react'; + +interface ExpandableRowWrapperProps extends PropsWithChildren { + row: any; + showExpandableContent?: boolean; + hasNarrowBorders?: boolean; +} + +export const ExpandableRowWrapper: FC = ({ + row, + showExpandableContent, + hasNarrowBorders, + children, +}) => { + return showExpandableContent ? ( + td]:border-b [&:not(:last-child)>td]:border-gray-100': + !hasNarrowBorders, + })} + > + {children} + + ) : null; +}; diff --git a/src/components/v5/common/Table/partials/TableFooter.tsx b/src/components/v5/common/Table/partials/TableFooter.tsx index e5784c008a4..14313851036 100644 --- a/src/components/v5/common/Table/partials/TableFooter.tsx +++ b/src/components/v5/common/Table/partials/TableFooter.tsx @@ -4,13 +4,13 @@ import React from 'react'; interface TableFooterProps { colSpan?: number; - verticalLayout?: boolean; + hasBorder?: boolean; groups: HeaderGroup[]; } export const TableFooter = ({ colSpan, - verticalLayout, + hasBorder, groups, }: TableFooterProps) => { const hasFooter = groups.map((footerGroup) => @@ -35,7 +35,7 @@ export const TableFooter = ({ colSpan={isLastColumn ? colSpan : 1} key={column.id} className={clsx('h-full px-[1.1rem] text-md text-gray-500', { - 'border-t border-gray-200': !verticalLayout, + 'border-t border-gray-200': hasBorder, })} > {flexRender( diff --git a/src/components/v5/common/Table/partials/TableLayout/HorizontalLayout.tsx b/src/components/v5/common/Table/partials/TableLayout/HorizontalLayout.tsx deleted file mode 100644 index d6eb2285c75..00000000000 --- a/src/components/v5/common/Table/partials/TableLayout/HorizontalLayout.tsx +++ /dev/null @@ -1,126 +0,0 @@ -import clsx from 'clsx'; -import React from 'react'; - -import { TableRowDivider } from '../TableRowDivider.tsx'; -import { TableRow } from '../VirtualizedRow/VirtualizedRow.tsx'; - -import { HorizontalTableCell } from './HorizontalTableCell.tsx'; -import { HorizontalTableHeader } from './HorizontalTableHeader.tsx'; -import { type HorizontalLayoutProps } from './types.ts'; - -export const HorizontalLayout = ({ - rows, - headerGroups, - showTableHead, - emptyContent, - totalColumnsCount, - getRowClassName, - withBorder, - sizeUnit, - virtualizedProps, - renderSubComponent, - getMenuProps, - tableBodyRowKeyProp, - withNarrowBorder, - renderCellWrapper, - isDisabled, - data, -}: HorizontalLayoutProps) => { - const shouldShowEmptyContent = !!emptyContent && data.length === 0; - - const getTableCellStyle = (index: number) => - !showTableHead - ? { - width: - headerGroups[0].headers[index].column.columnDef.staticSize || - (headerGroups[0].headers[index].getSize() !== 150 - ? `${headerGroups[0].headers[index].column.getSize()}${sizeUnit}` - : undefined), - } - : undefined; - - return ( - <> - {showTableHead && ( - - )} - - {shouldShowEmptyContent ? ( - - -
- {emptyContent} -
- - - ) : ( - rows.map((row) => { - const showExpandableContent = - row.getIsExpanded() && renderSubComponent; - - const rowKey = tableBodyRowKeyProp - ? data[row.index]?.[tableBodyRowKeyProp] - : null; - - const key = - typeof rowKey === 'string' || typeof rowKey === 'number' - ? rowKey - : row.id; - - return ( - - tr:first-child>td]:pr-9 [&>tr:last-child>td]:p-0 [&>tr:last-child>th]:p-0': - getMenuProps, - '[&:not(:last-child)>td]:border-b [&:not(:last-child)>td]:border-gray-100': - (!showExpandableContent && - row.getCanExpand() && - !withNarrowBorder) || - withBorder, - 'expanded-below': showExpandableContent, - })} - > - {row.getVisibleCells().map((cell, index) => ( - - ))} - - {showExpandableContent && ( - td]:border-b [&:not(:last-child)>td]:border-gray-100': - !withNarrowBorder, - })} - > - - {renderSubComponent({ row })} - - - )} - { - /** Unfortunately Safari is not yet that friendly to allow the usage of absolutely positioned (pseudo)-element inside tables - * So, for the moment, this is our best shot for showing borders with paddings from the tr margins - */ - withNarrowBorder && - } - - ); - }) - )} - - - ); -}; diff --git a/src/components/v5/common/Table/partials/TableLayout/HorizontalTableCell.tsx b/src/components/v5/common/Table/partials/TableLayout/HorizontalTableCell.tsx index 1340668c157..d538367e40a 100644 --- a/src/components/v5/common/Table/partials/TableLayout/HorizontalTableCell.tsx +++ b/src/components/v5/common/Table/partials/TableLayout/HorizontalTableCell.tsx @@ -2,16 +2,15 @@ import { flexRender, type Row, type Cell } from '@tanstack/react-table'; import clsx from 'clsx'; import React, { type CSSProperties } from 'react'; +import { type HorizontalTableLayoutProps } from '~v5/common/Table/types.ts'; import { getDefaultRenderCellWrapper } from '~v5/common/Table/utils.tsx'; -import { type HorizontalLayoutProps } from './types.ts'; - type HorizontalTableCellProps = { isDisabled?: boolean; style?: CSSProperties; row: Row; cell: Cell; -} & Pick, 'renderCellWrapper'>; +} & Pick, 'renderCellWrapper'>; export const HorizontalTableCell = ({ isDisabled, diff --git a/src/components/v5/common/Table/partials/TableLayout/HorizontalTableHeader.tsx b/src/components/v5/common/Table/partials/TableLayout/HorizontalTableHeader.tsx index 735f4bab0b5..9dac0c88345 100644 --- a/src/components/v5/common/Table/partials/TableLayout/HorizontalTableHeader.tsx +++ b/src/components/v5/common/Table/partials/TableLayout/HorizontalTableHeader.tsx @@ -3,12 +3,7 @@ import { flexRender, type HeaderGroup } from '@tanstack/react-table'; import clsx from 'clsx'; import React from 'react'; -import { type HorizontalLayoutProps } from './types.ts'; - -type HorizontalTableHeaderProps = Pick< - HorizontalLayoutProps, - 'sizeUnit' -> & { +type HorizontalTableHeaderProps = { groups: HeaderGroup[]; disabled?: boolean; }; @@ -16,7 +11,6 @@ type HorizontalTableHeaderProps = Pick< export const HorizontalTableHeader = ({ groups, disabled, - sizeUnit, }: HorizontalTableHeaderProps) => { return ( @@ -38,8 +32,8 @@ export const HorizontalTableHeader = ({ style={{ width: header.column.columnDef.staticSize || - (header.getSize() !== 150 - ? `${header.column.getSize()}${sizeUnit}` + (header.column.getSize() !== 150 + ? `${header.column.getSize()}px` : undefined), }} > diff --git a/src/components/v5/common/Table/partials/TableLayout/HorizontalTableLayout.tsx b/src/components/v5/common/Table/partials/TableLayout/HorizontalTableLayout.tsx new file mode 100644 index 00000000000..4c168b39227 --- /dev/null +++ b/src/components/v5/common/Table/partials/TableLayout/HorizontalTableLayout.tsx @@ -0,0 +1,117 @@ +import React from 'react'; + +import { useCollapseRows } from '~v5/common/Table/hooks.ts'; +import { ExpandableRowWrapper } from '~v5/common/Table/partials/ExpandableRowWrapper.tsx'; +import { HorizontalTableCell } from '~v5/common/Table/partials/TableLayout/HorizontalTableCell.tsx'; +import { HorizontalTableHeader } from '~v5/common/Table/partials/TableLayout/HorizontalTableHeader.tsx'; +import { TableRowDivider } from '~v5/common/Table/partials/TableRowDivider.tsx'; +import { TableRow } from '~v5/common/Table/partials/VirtualizedRow/VirtualizedRow.tsx'; +import { type HorizontalTableLayoutProps } from '~v5/common/Table/types.ts'; +import { + getHorizontalRowClasses, + getHorizontalRowKey, + renderEmptyContent, +} from '~v5/common/Table/utils.tsx'; + +export const HorizontalTableLayout = ({ + showTableHead, + emptyContent, + moreActions, + rows: rowsConfig, + borders, + renderCellWrapper, + isDisabled, + data, + table, +}: HorizontalTableLayoutProps) => { + const { rows } = table.getRowModel(); + const headerGroups = table.getHeaderGroups(); + const totalColumnsCount = table.getVisibleFlatColumns().length; + const shouldShowEmptyContent = !!emptyContent && data.length === 0; + const hasNarrowBorders = borders?.type === 'narrow'; + const hasWideBorders = borders?.type === 'wide'; + const hasMoreActions = !!moreActions; + useCollapseRows(rows, table.getIsSomeRowsExpanded()); + + const getTableCellStyle = (index: number) => + !showTableHead + ? { + width: + headerGroups[0].headers[index].column.columnDef.staticSize || + (headerGroups[0].headers[index].column.getSize() !== 150 + ? `${headerGroups[0].headers[index].column.getSize()}px` + : undefined), + } + : undefined; + + return ( + <> + {showTableHead && ( + + )} + + {renderEmptyContent({ + shouldShowEmptyContent, + emptyContent, + colSpan: totalColumnsCount, + }) || + rows.map((row) => { + const renderSubComponent = rowsConfig?.renderSubComponent; + const showExpandableContent = + row.getIsExpanded() && !!renderSubComponent; + + const key = getHorizontalRowKey({ + row, + rowKeyProp: rowsConfig?.key, + data, + }); + + return ( + + + {row.getVisibleCells().map((cell, index) => ( + + ))} + + + {renderSubComponent?.({ row })} + + { + /** + * Unfortunately Safari is not yet that friendly to allow the usage of absolutely positioned (pseudo)-element inside tables + * So, for the moment, this is our best shot for showing borders with paddings from the tr margins + */ + hasNarrowBorders && + } + + ); + })} + + + ); +}; diff --git a/src/components/v5/common/Table/partials/TableLayout/TableLayout.tsx b/src/components/v5/common/Table/partials/TableLayout/TableLayout.tsx deleted file mode 100644 index ce4d5931252..00000000000 --- a/src/components/v5/common/Table/partials/TableLayout/TableLayout.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; - -import { HorizontalLayout } from './HorizontalLayout.tsx'; -import { - type HorizontalLayoutProps, - type TableLayoutProps, - type VerticalLayoutProps, -} from './types.ts'; -import { VerticalLayout } from './VerticalLayout.tsx'; - -export const TableLayout = ({ - verticalLayout, - ...props -}: TableLayoutProps) => { - return verticalLayout ? ( - )} /> - ) : ( - )} /> - ); -}; diff --git a/src/components/v5/common/Table/partials/TableLayout/VerticalLayout.tsx b/src/components/v5/common/Table/partials/TableLayout/VerticalTableLayout.tsx similarity index 53% rename from src/components/v5/common/Table/partials/TableLayout/VerticalLayout.tsx rename to src/components/v5/common/Table/partials/TableLayout/VerticalTableLayout.tsx index 0cc902b9b69..72e13eb158a 100644 --- a/src/components/v5/common/Table/partials/TableLayout/VerticalLayout.tsx +++ b/src/components/v5/common/Table/partials/TableLayout/VerticalTableLayout.tsx @@ -2,23 +2,17 @@ import { flexRender } from '@tanstack/react-table'; import clsx from 'clsx'; import React from 'react'; -import MeatBallMenu from '~v5/shared/MeatBallMenu/MeatBallMenu.tsx'; +import { TableRow } from '~v5/common/Table/partials/VirtualizedRow/VirtualizedRow.tsx'; +import { type VerticalTableLayoutProps } from '~v5/common/Table/types.ts'; -import { TableRow } from '../VirtualizedRow/VirtualizedRow.tsx'; - -import { type VerticalLayoutProps } from './types.ts'; - -export const VerticalLayout = ({ - rows, - headerGroups, - getRowClassName, - getMenuProps, - virtualizedProps, - sizeUnit, - meatBallMenuSize, - meatBallMenuStaticSize, - withBorder, -}: VerticalLayoutProps) => { +export const VerticalTableLayout = ({ + rows: rowsConfig, + borders, + moreActions, + table, +}: VerticalTableLayoutProps) => { + const { rows } = table.getRowModel(); + const headerGroups = table.getHeaderGroups(); return ( <> {rows.map((row) => { @@ -28,31 +22,28 @@ export const VerticalLayout = ({ tr:last-child>td]:border-b [&:not(:last-child)>tr:last-child>th]:border-b [&_tr:first-child_td]:pt-2 [&_tr:first-child_th]:h-[2.875rem] [&_tr:first-child_th]:pt-2 [&_tr:last-child_td]:pb-2 [&_tr:last-child_th]:h-[2.875rem] [&_tr:last-child_th]:pb-2', + rowsConfig?.getRowClassName?.(row), + '[&:not(:last-child)>tr:last-child>td]:border-b [&:not(:last-child)>tr:last-child>th]:border-b [&:nth-child(1)>tr:first-child>th]:rounded-tl-lg [&_tr:first-child_td]:pt-2 [&_tr:first-child_th]:h-[2.875rem] [&_tr:first-child_th]:pt-2 [&_tr:last-child_td]:pb-2 [&_tr:last-child_th]:h-[2.875rem] [&_tr:last-child_th]:pb-2', )} > {headerGroups.map((headerGroup) => headerGroup.headers.map((header, index) => { - const rowWithMeatBallMenu = index === 0 && !!getMenuProps; - const meatBallMenuProps = getMenuProps?.(row); - const hasMeatballMenu = - !!meatBallMenuProps && rowWithMeatBallMenu; - const width = meatBallMenuSize || meatBallMenuStaticSize; - const colSpan = hasMeatballMenu ? undefined : 2; + const hasMoreActions = index === 0 && !!moreActions; + const colSpan = hasMoreActions ? undefined : 2; return ( td]:border-b [&:not(:last-child)>td]:border-gray-100': - withBorder, + borders?.type === 'wide', })} > ({ text-left text-sm font-normal - first:rounded-tl-lg - last:rounded-tr-lg `} style={{ width: header.column.columnDef.staticSize || - (header.getSize() !== 150 - ? `${header.column.getSize()}${sizeUnit}` + (header.column.getSize() !== 150 + ? `${header.column.getSize()}px` : undefined), }} > @@ -92,21 +81,11 @@ export const VerticalLayout = ({ cells[index].getContext(), )} - {hasMeatballMenu && ( + {hasMoreActions && ( - + {moreActions.renderMoreActions?.(row)} )} diff --git a/src/components/v5/common/Table/partials/TableLayout/types.ts b/src/components/v5/common/Table/partials/TableLayout/types.ts deleted file mode 100644 index 7b08aabd998..00000000000 --- a/src/components/v5/common/Table/partials/TableLayout/types.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { type HeaderGroup, type Row } from '@tanstack/react-table'; -import { type PropsWithChildren } from 'react'; - -import { type TableProps } from '~v5/common/Table/types.ts'; - -export type BaseTableLayoutProps = Pick< - TableProps, - | 'verticalLayout' - | 'getMenuProps' - | 'virtualizedProps' - | 'sizeUnit' - | 'withBorder' -> & - Required, 'getRowClassName'>> & { - rows: Row[]; - headerGroups: HeaderGroup[]; - } & PropsWithChildren; - -export type HorizontalLayoutProps = BaseTableLayoutProps & - Pick< - TableProps, - | 'showTableHead' - | 'emptyContent' - | 'renderSubComponent' - | 'tableBodyRowKeyProp' - | 'withNarrowBorder' - | 'isDisabled' - | 'data' - > & - Required, 'renderCellWrapper'>> & { - totalColumnsCount: number; - }; - -export type VerticalLayoutProps = BaseTableLayoutProps & - Pick, 'meatBallMenuSize' | 'meatBallMenuStaticSize'>; - -export type TableLayoutProps = - | HorizontalLayoutProps - | VerticalLayoutProps; diff --git a/src/components/v5/common/Table/partials/VirtualizedRow/VirtualizedRow.tsx b/src/components/v5/common/Table/partials/VirtualizedRow/VirtualizedRow.tsx index c7110a680b5..4f56b70b0da 100644 --- a/src/components/v5/common/Table/partials/VirtualizedRow/VirtualizedRow.tsx +++ b/src/components/v5/common/Table/partials/VirtualizedRow/VirtualizedRow.tsx @@ -3,6 +3,7 @@ import { useVisible } from 'react-hooks-visible'; import { type TableRowProps, type VirtualizedRowProps } from './types.ts'; +/** @TODO refactor this to use @tanstack/react-virtual */ const VirtualizedRow: FC = ({ children, className, diff --git a/src/components/v5/common/Table/types.ts b/src/components/v5/common/Table/types.ts index cc76561762e..b345d30283f 100644 --- a/src/components/v5/common/Table/types.ts +++ b/src/components/v5/common/Table/types.ts @@ -1,8 +1,16 @@ -import { type TableOptions, type Row, type Cell } from '@tanstack/react-table'; +import { + type TableOptions, + type Row, + type Cell, + type Table, + type ColumnDef, + type RowModel, + type OnChangeFn, + type RowSelectionState, +} from '@tanstack/react-table'; +import { type PropsWithChildren } from 'react'; -import { type MeatBallMenuProps } from '~v5/shared/MeatBallMenu/types.ts'; - -import type React from 'react'; +export type NullableColumnDef = ColumnDef | null; export type RenderCellWrapper = ( className: string, @@ -14,38 +22,113 @@ export type RenderCellWrapper = ( }, ) => React.ReactNode; -export interface TableProps - extends Omit, 'getCoreRowModel'> { +export interface MoreActionsOptions { + renderMoreActions?: (row: Row) => React.ReactElement | null; + wrapperClassName?: string; +} + +export interface TableRowOptions { + getRowClassName?: (row: Row) => string | undefined; // Class name generator for rows + key?: Extract; // Unique key for rows + virtualizedRowHeight?: number; // Enable row virtualization + renderSubComponent?: (props: { row: Row }) => React.ReactElement; // Sub-component rendering + canExpand?: (row: Row) => boolean; // Row expansion logic +} + +type PaginationDirection = 'previous' | 'next'; + +export interface PaginationOptions { + pageSize?: number; // Number of rows per page + canNextPage?: boolean; // Enable "next page" + canPreviousPage?: boolean; // Enable "previous page" + onPageChange?: (direction: PaginationDirection) => void; + disabled?: boolean; // Disable pagination + visible?: boolean; // Control visibility + pageNumberVisible?: boolean; // Show current page number + pageTotalVisible?: boolean; // Show total pages + children?: React.ReactNode; // Custom buttons +} + +export interface TableOptionsOverrides { getCoreRowModel?: TableOptions['getCoreRowModel']; - className?: string; - getRowClassName?: (row: Row) => string | undefined; - verticalLayout?: boolean; - sizeUnit?: 'px' | 'rem' | 'em' | '%'; - canNextPage?: boolean; - canPreviousPage?: boolean; - previousPage?: VoidFunction; - nextPage?: VoidFunction; - showPageNumber?: boolean; - showTotalPagesNumber?: boolean; - paginationDisabled?: boolean; - renderCellWrapper?: RenderCellWrapper; - additionalPaginationButtonsContent?: React.ReactNode; - emptyContent?: React.ReactNode; - getMenuProps?: (row: Row) => MeatBallMenuProps | undefined; - meatBallMenuSize?: number; - meatBallMenuStaticSize?: string; - renderSubComponent?: (props: { row: Row }) => React.ReactElement; - getRowCanExpand?: (row: Row) => boolean; - withBorder?: boolean; - withNarrowBorder?: boolean; - isDisabled?: boolean; - footerColSpan?: number; - virtualizedProps?: { - virtualizedRowHeight?: number; + initialState?: Partial<{ + sorting: Array<{ id: string; desc: boolean }>; + pagination: { pageIndex: number; pageSize: number }; + }>; + state?: { + columnVisibility?: Record; + sorting?: Array<{ + id: string; + desc: boolean; + }>; + pagination?: { + pageIndex: number; + pageSize: number; + }; + rowSelection?: Record; }; - tableClassName?: string; - tableBodyRowKeyProp?: Extract; - showTableHead?: boolean; - showTableBorder?: boolean; - alwaysShowPagination?: boolean; + manualPagination?: boolean; + pageCount?: number; + onSortingChange?: (sorting: Array<{ id: string; desc: boolean }>) => void; + getRowId?: (row: T, index: number) => string; + isMultiSortEvent?: () => boolean; + enableSorting?: boolean; + enableSortingRemoval?: boolean; + sortDescFirst?: boolean; + getSortedRowModel?: (table: Table) => () => RowModel; + getPaginationRowModel?: (table: Table) => () => RowModel; + onRowSelectionChange?: OnChangeFn; + getFilteredRowModel?: (table: Table) => () => RowModel; } + +export interface BaseTableOptions + extends Pick, 'data' | 'columns'> { + className?: string; // General class name + tableClassName?: string; // Table-specific class name + emptyContent?: React.ReactNode; // Custom empty state + isDisabled?: boolean; // Disable interactions + layout?: 'horizontal' | 'vertical'; // Table layout + borders?: { + type?: 'narrow' | 'wide' | 'unset'; // Border width + visible?: boolean; // Show/hide borders + }; + showTableHead?: boolean; // Show/hide table header + footerColSpan?: number; // Span of footer columns + renderCellWrapper?: RenderCellWrapper; // Wrapper for cell rendering +} + +export interface BaseTableProps extends BaseTableOptions { + table: Table; // React Table instance + rows?: TableRowOptions; // Row-specific options + moreActions?: MoreActionsOptions; + overrides?: TableOptionsOverrides; // Overrides for advanced customization based on the TableOptions provided by @tanstack/react-table + children?: React.ReactNode; // Custom children +} + +export interface TableProps extends Omit, 'table'> { + pagination?: PaginationOptions; +} + +export type BaseTableLayoutProps = Pick< + BaseTableProps, + 'table' | 'layout' | 'moreActions' | 'borders' | 'rows' +> & + PropsWithChildren; + +export type HorizontalTableLayoutProps = BaseTableLayoutProps & + Pick< + BaseTableProps, + | 'showTableHead' + | 'moreActions' + | 'emptyContent' + | 'borders' + | 'isDisabled' + | 'data' + > & + Required, 'renderCellWrapper'>>; + +export type VerticalTableLayoutProps = BaseTableLayoutProps; + +export type TableLayoutProps = + | HorizontalTableLayoutProps + | VerticalTableLayoutProps; diff --git a/src/components/v5/common/Table/utils.tsx b/src/components/v5/common/Table/utils.tsx index 1f8682c071f..d77d649c6bd 100644 --- a/src/components/v5/common/Table/utils.tsx +++ b/src/components/v5/common/Table/utils.tsx @@ -1,35 +1,87 @@ -import { type ColumnHelper } from '@tanstack/react-table'; +import { type Table, type ColumnHelper, type Row } from '@tanstack/react-table'; import clsx from 'clsx'; import React from 'react'; -import MeatBallMenu from '~v5/shared/MeatBallMenu/index.ts'; +import MeatBallMenu from '~v5/shared/MeatBallMenu/MeatBallMenu.tsx'; +import { type MeatBallMenuProps } from '~v5/shared/MeatBallMenu/types.ts'; import { MEATBALL_MENU_COLUMN_ID } from './consts.ts'; -import { type RenderCellWrapper, type TableProps } from './types.ts'; +import { EmptyContentWrapper } from './partials/EmptyContentWrapper.tsx'; +import { type RenderCellWrapper, type PaginationOptions } from './types.ts'; export const getDefaultRenderCellWrapper = (): RenderCellWrapper => (cellClassName, content) =>
{content}
; +export const getHorizontalRowClasses = ({ + className = '', + isExpandable, + showExpandableContent, + hasNarrowBorders, + hasWideBorders, + hasMoreActions, +}) => + clsx(className, { + 'translate-z-0 relative [&>tr:first-child>td]:pr-9 [&>tr:last-child>td]:p-0 [&>tr:last-child>th]:p-0': + hasMoreActions, + '[&:not(:last-child)>td]:border-b [&:not(:last-child)>td]:border-gray-100': + (!showExpandableContent && isExpandable && !hasNarrowBorders) || + hasWideBorders, + 'expanded-below': showExpandableContent, + }); + +export const getHorizontalRowKey = ({ row, rowKeyProp, data }) => { + const rowKey = rowKeyProp ? data[row.index]?.[rowKeyProp] : null; + return typeof rowKey === 'string' || typeof rowKey === 'number' + ? rowKey + : row.id; +}; + +export const renderCellContent = ( + _, + content: React.ReactNode, +): React.ReactNode => content; + +export const getMoreActionsMenu = ({ + getMenuProps, + wrapperClassName = 'w-[3.75rem]', + visible = true, +}) => ({ + wrapperClassName, + renderMoreActions: (row: Row) => { + const menuProps = getMenuProps(row); + return visible && menuProps ? ( + + ) : null; + }, +}); + export const makeMenuColumn = ({ helper, getMenuProps, - meatBallMenuSize = 60, - meatBallMenuStaticSize, + cellProps = { + size: 60, + }, }: { helper: ColumnHelper; - getMenuProps: TableProps['getMenuProps']; - meatBallMenuSize?: number; - meatBallMenuStaticSize: string | undefined; -}) => - helper.display({ + getMenuProps: (row: Row) => MeatBallMenuProps | undefined; + cellProps?: { + size?: number; + staticSize?: string; + }; +}) => { + return helper.display({ id: MEATBALL_MENU_COLUMN_ID, - ...(meatBallMenuStaticSize - ? { staticSize: meatBallMenuStaticSize } - : { - size: meatBallMenuSize, - minSize: meatBallMenuSize, - }), + size: cellProps.size, + minSize: cellProps.size, + staticSize: cellProps.staticSize, cell: ({ row }) => { const props = getMenuProps?.(row); @@ -45,3 +97,72 @@ export const makeMenuColumn = ({ ) : undefined; }, }); +}; + +export const renderEmptyContent = ({ + shouldShowEmptyContent, + emptyContent, + colSpan, +}) => { + if (!shouldShowEmptyContent) return null; + + return ( + {emptyContent} + ); +}; + +export const getPaginationConfig = ( + table: Table, + pagination: PaginationOptions, +) => { + const { + onPageChange, + canNextPage, + canPreviousPage, + pageNumberVisible = true, + visible, + disabled, + } = pagination; + + const canGoToNextPage = canNextPage ?? table.getCanNextPage(); + const canGoToPreviousPage = canPreviousPage ?? table.getCanPreviousPage(); + const pageCount = table.getPageCount(); + const hasPagination = pageCount > 1 || canGoToNextPage || canGoToPreviousPage; + + const showPagination = visible ?? (hasPagination && pageNumberVisible); + + const goToNextPage = () => { + onPageChange?.('next'); + table.nextPage(); + }; + + const goToPreviousPage = () => { + onPageChange?.('previous'); + table.previousPage(); + }; + + const paginationConfigCommon = { + disabled: !hasPagination || disabled, + showPagination, + goToNextPage, + goToPreviousPage, + }; + + if (hasPagination) { + return { + ...paginationConfigCommon, + canGoToNextPage, + canGoToPreviousPage, + actualPage: table.getState().pagination.pageIndex + 1, + pageNumber: pageCount, + }; + } + + return { + ...paginationConfigCommon, + canGoToNextPage: visible, + canGoToPreviousPage: false, + actualPage: 1, + pageNumber: 1, + }; +}; diff --git a/src/components/v5/frame/ColonyHome/ColonyHome.tsx b/src/components/v5/frame/ColonyHome/ColonyHome.tsx index f27d399c9c1..178ddab293d 100644 --- a/src/components/v5/frame/ColonyHome/ColonyHome.tsx +++ b/src/components/v5/frame/ColonyHome/ColonyHome.tsx @@ -3,7 +3,6 @@ import React, { useState } from 'react'; import FiltersContextProvider from '~common/ColonyActionsTable/FiltersContext/FiltersContextProvider.tsx'; import RecentActivityTable from '~common/ColonyActionsTable/RecentActivityTable.tsx'; import { TourTargets } from '~common/Tours/enums.ts'; -import { useMobile } from '~hooks/index.ts'; import { // @BETA: Disabled for now // COLONY_TEAMS_ROUTE, @@ -23,7 +22,6 @@ import TotalInOutBalance from './partials/TotalInOutBalance/TotalInOutBalance.ts const displayName = 'v5.frame.ColonyHome'; const ColonyHome = () => { - const isMobile = useMobile(); const [selectedAction, setSelectedAction] = useState( undefined, ); @@ -66,21 +64,6 @@ const ColonyHome = () => { }} className="flex w-full flex-grow flex-col justify-between [&_tr.expanded-below:not(last-child)_td>*:not(.expandable)]:!pb-2 [&_tr.expanded-below_td]:border-none [&_tr:last-child_td>*:not(.expandable)]:!py-[13px] [&_tr:not(last-child)_td>*:not(.expandable)]:!pb-[13px] [&_tr:not(last-child)_td>*:not(.expandable)]:!pt-[13px]" pageSize={7} - state={{ - columnVisibility: isMobile - ? { - description: true, - motionState: true, - team: false, - createdAt: false, - } - : { - description: true, - motionState: true, - team: false, - createdAt: true, - }, - }} /> diff --git a/src/hooks/useSocialLinksTableColumns.tsx b/src/hooks/useSocialLinksTableColumns.tsx index a0aa0683042..1b6190c2fda 100644 --- a/src/hooks/useSocialLinksTableColumns.tsx +++ b/src/hooks/useSocialLinksTableColumns.tsx @@ -1,4 +1,8 @@ -import { createColumnHelper, type ColumnDef } from '@tanstack/react-table'; +import { + createColumnHelper, + type Row, + type ColumnDef, +} from '@tanstack/react-table'; import clsx from 'clsx'; import React, { useMemo } from 'react'; @@ -6,11 +10,14 @@ import { useMobile } from '~hooks'; import { type SocialLinksTableModel } from '~types/colony.ts'; import { formatText } from '~utils/intl.ts'; import useHasNoDecisionMethods from '~v5/common/ActionSidebar/hooks/permissions/useHasNoDecisionMethods.ts'; +import { makeMenuColumn } from '~v5/common/Table/utils.tsx'; +import { type MeatBallMenuProps } from '~v5/shared/MeatBallMenu/types.ts'; -export const useSocialLinksTableColumns = (): ColumnDef< - SocialLinksTableModel, - string ->[] => { +export const useSocialLinksTableColumns = ( + getMenuProps?: ( + row: Row, + ) => MeatBallMenuProps | undefined, +): ColumnDef[] => { const columnHelper = useMemo( () => createColumnHelper(), [], @@ -20,6 +27,20 @@ export const useSocialLinksTableColumns = (): ColumnDef< const isMobile = useMobile(); + const menuColumn = useMemo( + () => + getMenuProps + ? makeMenuColumn({ + helper: columnHelper, + getMenuProps, + cellProps: { + staticSize: isMobile ? '60px' : '10%', + }, + }) + : null, + [getMenuProps, columnHelper, isMobile], + ); + const columns: ColumnDef[] = useMemo( () => [ columnHelper.accessor('name', { @@ -29,6 +50,7 @@ export const useSocialLinksTableColumns = (): ColumnDef< {formatText({ id: 'table.row.type' })} ), + cellContentWrapperClassName: 'flex justify-between', cell: ({ getValue }) => ( ), - size: isMobile ? 118 : 23, + staticSize: isMobile ? '118px' : '23%', }), columnHelper.accessor('link', { enableSorting: false, @@ -61,11 +83,11 @@ export const useSocialLinksTableColumns = (): ColumnDef< {getValue()} ), - size: 67, + staticSize: isMobile ? '67px' : '67%', }), ], [columnHelper, hasNoDecisionMethods, isMobile], ); - return columns; + return menuColumn ? [...columns, menuColumn] : columns; }; diff --git a/src/stories/common/Table.stories.tsx b/src/stories/common/Table.stories.tsx index 15dabef7f27..cc198539a7d 100644 --- a/src/stories/common/Table.stories.tsx +++ b/src/stories/common/Table.stories.tsx @@ -2,13 +2,15 @@ import { Binoculars } from '@phosphor-icons/react'; import { createColumnHelper, getPaginationRowModel, + type Row, } from '@tanstack/react-table'; import clsx from 'clsx'; import React from 'react'; +import { Link } from 'react-router-dom'; -import EmptyContent from '~v5/common/EmptyContent/index.ts'; -import Table from '~v5/common/Table/index.ts'; -import Link from '~v5/shared/Link/index.ts'; +import EmptyContent from '~v5/common/EmptyContent/EmptyContent.tsx'; +import { Table } from '~v5/common/Table/Table.ts'; +import MeatBallMenu from '~v5/shared/MeatBallMenu/MeatBallMenu.tsx'; import type { Meta, StoryObj } from '@storybook/react'; @@ -19,7 +21,7 @@ interface TestTableFieldModel { const columnHelper = createColumnHelper(); -const tableMeta: Meta> = { +const meta: Meta> = { title: 'Common/Table', component: Table, parameters: { @@ -55,34 +57,95 @@ const tableMeta: Meta> = { }, }; -export default tableMeta; +export default meta; +// type Story = StoryObj; -export const Base: StoryObj> = {}; +export const Base: StoryObj> = { + args: { + pagination: { + visible: true, + }, + }, +}; + +export const WithPaginatedContent: StoryObj> = + { + args: { + pagination: { + visible: true, + }, + overrides: { + initialState: { + pagination: { + pageIndex: 0, + pageSize: 1, + }, + }, + getPaginationRowModel: getPaginationRowModel(), + }, + }, + }; export const VerticalLayout: StoryObj> = { args: { - verticalLayout: true, + ...Base.args, + layout: 'vertical', }, }; +const getMenuProps = () => ({ + items: [ + { + key: 'edit', + label: 'Edit', + // eslint-disable-next-line no-alert + onClick: () => alert('Edit'), + }, + { + key: 'delete', + label: 'Delete', + // eslint-disable-next-line no-alert + onClick: () => alert('Delete'), + }, + ], +}); + export const WithMenu: StoryObj> = { args: { - getMenuProps: () => ({ - items: [ - { - key: 'edit', - label: 'Edit', - // eslint-disable-next-line no-alert - onClick: () => alert('Edit'), - }, - { - key: 'delete', - label: 'Delete', - // eslint-disable-next-line no-alert - onClick: () => alert('Delete'), + ...Base.args, + columns: [ + ...(meta.args?.columns ?? []), + columnHelper.display({ + id: 'menu', + size: 60, + minSize: 60, + cell: () => { + const props = getMenuProps(); + + return props ? ( + + ) : undefined; }, - ], - }), + }), + ], + moreActions: { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + renderMoreActions: (_: Row) => { + return ( + + ); + }, + }, }, }; @@ -90,37 +153,32 @@ export const VerticalLayoutWithMenu: StoryObj< typeof Table > = { args: { + ...Base.args, ...VerticalLayout.args, ...WithMenu.args, }, }; -export const WithPagination: StoryObj> = { - args: { - initialState: { - pagination: { - pageSize: 1, - }, - }, - getPaginationRowModel: getPaginationRowModel(), - }, -}; - export const WithAdditionalContent: StoryObj< typeof Table > = { args: { - ...WithPagination.args, - additionalPaginationButtonsContent: ( - - View all - - ), + pagination: { + visible: true, + children: ( + + View all + + ), + }, }, }; export const WithEmptyContent: StoryObj> = { args: { + pagination: { + visible: false, + }, data: [], emptyContent: ( > = { args: { + ...Base.args, renderCellWrapper: (classNames, content) => (
{content}
),