feat(v3): table designs (#21147)
This commit is contained in:
@@ -39,6 +39,7 @@ export type DataTablePropsFromWrapper<TData> = {
|
||||
headerClassName?: string;
|
||||
rowClassName?: string;
|
||||
paginationMode?: "infinite" | "standard";
|
||||
hasWrapperContext?: boolean;
|
||||
};
|
||||
|
||||
export type DataTableProps<TData> = DataTablePropsFromWrapper<TData> & {
|
||||
@@ -64,6 +65,7 @@ export function DataTable<TData>({
|
||||
headerClassName,
|
||||
rowClassName,
|
||||
paginationMode = "infinite",
|
||||
hasWrapperContext = false,
|
||||
...rest
|
||||
}: DataTableProps<TData> & React.ComponentPropsWithoutRef<"div">) {
|
||||
const { rows } = table.getRowModel();
|
||||
@@ -104,7 +106,10 @@ export function DataTable<TData>({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames("grid", className)}
|
||||
className={classNames(
|
||||
!hasWrapperContext ? "grid" : "bg-muted grid rounded-xl px-0.5 pb-0.5",
|
||||
className
|
||||
)}
|
||||
style={{
|
||||
gridTemplateRows: "auto 1fr auto",
|
||||
gridTemplateAreas: "'header' 'body' 'footer'",
|
||||
@@ -129,20 +134,23 @@ export function DataTable<TData>({
|
||||
onScroll={onScroll}
|
||||
className={classNames(
|
||||
"relative overflow-auto",
|
||||
"scrollbar-thin border-subtle relative rounded-md border",
|
||||
"scrollbar-thin relative rounded-md ",
|
||||
paginationMode === "infinite" && "h-[80dvh]", // Set a fixed height for the container
|
||||
containerClassName
|
||||
)}
|
||||
style={{ gridArea: "body" }}>
|
||||
<TableNew
|
||||
className="data-table grid border-0"
|
||||
className={classNames(
|
||||
"data-table grid border-0",
|
||||
!hasWrapperContext && "bg-muted rounded-xl px-0.5 pb-0.5"
|
||||
)}
|
||||
style={{
|
||||
...columnSizingVars,
|
||||
...(Boolean(enableColumnResizing) && { width: table.getTotalSize() }),
|
||||
}}>
|
||||
<TableHeader className={classNames("sticky top-0 z-10", headerClassName)}>
|
||||
{table.getHeaderGroups().map((headerGroup: HeaderGroup<TData>) => (
|
||||
<TableRow key={headerGroup.id} className="hover:bg-subtle flex w-full">
|
||||
<TableRow key={headerGroup.id} className="hover:bg-subtle flex w-full border-none">
|
||||
{headerGroup.headers.map((header: Header<TData, unknown>) => {
|
||||
const { column } = header;
|
||||
return (
|
||||
@@ -155,7 +163,6 @@ export function DataTable<TData>({
|
||||
}}
|
||||
className={classNames(
|
||||
"relative flex shrink-0 items-center",
|
||||
"bg-subtle",
|
||||
column.getIsPinned() && "top-0 z-20 sm:sticky"
|
||||
)}>
|
||||
<TableHeadLabel header={header} />
|
||||
@@ -164,7 +171,7 @@ export function DataTable<TData>({
|
||||
onMouseDown={header.getResizeHandler()}
|
||||
onTouchStart={header.getResizeHandler()}
|
||||
className={classNames(
|
||||
"group absolute right-0 top-0 h-full w-[5px] cursor-col-resize touch-none select-none opacity-[0.1] hover:opacity-50",
|
||||
"group absolute right-0 top-0 h-full w-[5px] cursor-col-resize touch-none select-none opacity-0 hover:opacity-50",
|
||||
header.column.getIsResizing() && "!opacity-75"
|
||||
)}>
|
||||
<div className="bg-inverted mx-auto h-full w-[1px]" />
|
||||
@@ -279,7 +286,10 @@ function DataTableBody<TData>({
|
||||
}
|
||||
|
||||
return (
|
||||
<TableBody className="relative grid" data-testid={testId} style={{ height: tableHeight }}>
|
||||
<TableBody
|
||||
className="border-subtle relative grid overflow-hidden rounded-xl border"
|
||||
data-testid={testId}
|
||||
style={{ height: tableHeight }}>
|
||||
{rowsToRender.map(({ row, virtualItem }) => (
|
||||
<TableRow
|
||||
ref={virtualItem ? (node) => rowVirtualizer.measureElement(node) : undefined}
|
||||
@@ -307,7 +317,7 @@ function DataTableBody<TData>({
|
||||
width: `var(--col-${kebabCase(cell.column.id)}-size)`,
|
||||
}}
|
||||
className={classNames(
|
||||
"flex shrink-0 items-center overflow-hidden",
|
||||
"bg-default group-hover:!bg-muted group-data-[state=selected]:bg-subtle flex shrink-0 items-center overflow-hidden",
|
||||
variant === "compact" && "p-0",
|
||||
column.getIsPinned() &&
|
||||
"bg-default group-hover:!bg-muted group-data-[state=selected]:bg-subtle sm:sticky"
|
||||
@@ -351,7 +361,7 @@ const TableHeadLabel = ({ header }: { header: Header<any, any> }) => {
|
||||
open && "bg-muted"
|
||||
)}>
|
||||
<div
|
||||
className="truncate"
|
||||
className="text-default truncate text-sm leading-none"
|
||||
title={
|
||||
typeof header.column.columnDef.header === "string" ? header.column.columnDef.header : undefined
|
||||
}>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import type { VisibilityState } from "@tanstack/react-table";
|
||||
import type { Row, VisibilityState } from "@tanstack/react-table";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { noop } from "lodash";
|
||||
import { useEffect, useRef } from "react";
|
||||
@@ -22,6 +22,7 @@ type BaseDataTableWrapperProps<TData> = Omit<
|
||||
EmptyView?: React.ReactNode;
|
||||
LoaderView?: React.ReactNode;
|
||||
tableContainerRef?: React.RefObject<HTMLDivElement>;
|
||||
onRowMouseclick?: (row: Row<TData>) => void;
|
||||
};
|
||||
|
||||
type InfinitePaginationProps<TData> = BaseDataTableWrapperProps<TData> & {
|
||||
@@ -61,6 +62,7 @@ export function DataTableWrapper<TData>({
|
||||
children,
|
||||
tableContainerRef: externalRef,
|
||||
paginationMode,
|
||||
onRowMouseclick,
|
||||
}: DataTableWrapperProps<TData>) {
|
||||
const internalRef = useRef<HTMLDivElement>(null);
|
||||
const tableContainerRef = externalRef || internalRef;
|
||||
@@ -130,13 +132,15 @@ export function DataTableWrapper<TData>({
|
||||
headerClassName={headerClassName}
|
||||
rowClassName={rowClassName}
|
||||
paginationMode={paginationMode}
|
||||
onRowMouseclick={onRowMouseclick}
|
||||
hasWrapperContext={true}
|
||||
onScroll={
|
||||
paginationMode === "infinite"
|
||||
? (e: Pick<React.UIEvent<HTMLDivElement, UIEvent>, "target">) =>
|
||||
fetchMoreOnBottomReached(e.target as HTMLDivElement)
|
||||
: undefined
|
||||
}>
|
||||
<div style={{ gridArea: "footer", marginTop: "1rem" }}>
|
||||
<div style={{ gridArea: "footer" }} className="px-3 py-2">
|
||||
<DataTablePagination<TData>
|
||||
table={table}
|
||||
totalRowCount={totalRowCount}
|
||||
|
||||
@@ -19,10 +19,10 @@ import type { Dispatch, SetStateAction } from "react";
|
||||
import { checkAdminOrOwner } from "@calcom/features/auth/lib/checkAdminOrOwner";
|
||||
import { Dialog } from "@calcom/features/components/controlled-dialog";
|
||||
import {
|
||||
DataTable,
|
||||
DataTableProvider,
|
||||
DataTableToolbar,
|
||||
DataTableFilters,
|
||||
DataTableWrapper,
|
||||
DataTableSelectionBar,
|
||||
useDataTable,
|
||||
useFetchMoreOnBottomReached,
|
||||
@@ -305,7 +305,6 @@ function MemberListContent(props: Props) {
|
||||
checked={table.getIsAllPageRowsSelected()}
|
||||
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
||||
aria-label="Select all"
|
||||
className="translate-y-[2px]"
|
||||
/>
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
@@ -660,18 +659,27 @@ function MemberListContent(props: Props) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<DataTable
|
||||
<DataTableWrapper
|
||||
testId="team-member-list-container"
|
||||
table={table}
|
||||
tableContainerRef={tableContainerRef}
|
||||
isPending={isPending}
|
||||
enableColumnResizing={true}
|
||||
onScroll={(e) => fetchMoreOnBottomReached(e.target as HTMLDivElement)}>
|
||||
<DataTableToolbar.Root>
|
||||
<div className="flex w-full gap-2">
|
||||
paginationMode="infinite"
|
||||
hasNextPage={hasNextPage}
|
||||
fetchNextPage={fetchNextPage}
|
||||
isFetching={isFetching}
|
||||
totalRowCount={totalRowCount}
|
||||
ToolbarLeft={
|
||||
<>
|
||||
<DataTableToolbar.SearchBar />
|
||||
<DataTableFilters.AddFilterButton table={table} />
|
||||
<DataTableFilters.ColumnVisibilityButton table={table} />
|
||||
<DataTableFilters.FilterBar table={table} />
|
||||
</>
|
||||
}
|
||||
ToolbarRight={
|
||||
<>
|
||||
<DataTableFilters.ClearFiltersButton />
|
||||
{isAdminOrOwner && (
|
||||
<DataTableToolbar.CTA
|
||||
type="button"
|
||||
@@ -682,12 +690,8 @@ function MemberListContent(props: Props) {
|
||||
{t("add")}
|
||||
</DataTableToolbar.CTA>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-2 justify-self-start">
|
||||
<DataTableFilters.ActiveFilters table={table} />
|
||||
</div>
|
||||
</DataTableToolbar.Root>
|
||||
|
||||
</>
|
||||
}>
|
||||
{numberOfSelectedRows >= 2 && dynamicLinkVisible && (
|
||||
<DataTableSelectionBar.Root className="!bottom-[7.3rem] md:!bottom-32">
|
||||
<DynamicLink table={table} domain={domain} />
|
||||
@@ -715,7 +719,7 @@ function MemberListContent(props: Props) {
|
||||
/>
|
||||
</DataTableSelectionBar.Root>
|
||||
)}
|
||||
</DataTable>
|
||||
</DataTableWrapper>
|
||||
{state.deleteMember.showModal && (
|
||||
<Dialog
|
||||
open={true}
|
||||
|
||||
@@ -113,7 +113,6 @@ function UserListTableContent({ oAuthClientId }: PlatformManagedUsersTableProps)
|
||||
checked={table.getIsAllPageRowsSelected()}
|
||||
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
||||
aria-label="Select all"
|
||||
className="translate-y-[2px]"
|
||||
/>
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
|
||||
@@ -257,7 +257,6 @@ function UserListTableContent() {
|
||||
checked={table.getIsAllPageRowsSelected()}
|
||||
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
||||
aria-label="Select all"
|
||||
className="translate-y-[2px]"
|
||||
/>
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
|
||||
@@ -68,7 +68,7 @@ export const Pagination = ({
|
||||
const endItem = Math.min(currentPage * pageSize, totalItems);
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between space-x-2 py-4">
|
||||
<div className="flex items-center justify-between space-x-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Select
|
||||
options={pageSizeSelectOptions}
|
||||
|
||||
@@ -15,11 +15,7 @@ Table.displayName = "Table";
|
||||
|
||||
const TableHeader = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<thead
|
||||
ref={ref}
|
||||
className={classNames("[&_tr]:bg-subtle md:z-10 [&_tr]:border-b", className)}
|
||||
{...props}
|
||||
/>
|
||||
<thead ref={ref} className={classNames("md:z-10", className)} {...props} />
|
||||
)
|
||||
);
|
||||
TableHeader.displayName = "TableHeader";
|
||||
|
||||
Reference in New Issue
Block a user