first commit
This commit is contained in:
54
calcom/packages/ui/components/table/Table.tsx
Normal file
54
calcom/packages/ui/components/table/Table.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import { classNames } from "@calcom/lib";
|
||||
|
||||
interface TableProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
interface DynamicWidth {
|
||||
widthClassNames?: string;
|
||||
}
|
||||
|
||||
const Header = ({ children }: TableProps) => (
|
||||
<thead className="rounded-md">
|
||||
<tr className="bg-default">{children}</tr>
|
||||
</thead>
|
||||
);
|
||||
|
||||
const ColumnTitle = ({ children, widthClassNames }: TableProps & DynamicWidth) => (
|
||||
<th
|
||||
scope="col"
|
||||
className={classNames(
|
||||
"text-default p-3 text-left text-xs font-medium uppercase",
|
||||
!widthClassNames ? "w-auto" : widthClassNames
|
||||
)}>
|
||||
{children}
|
||||
</th>
|
||||
);
|
||||
|
||||
const Body = ({ children }: TableProps) => (
|
||||
<tbody className="divide-subtle divide-y rounded-md">{children}</tbody>
|
||||
);
|
||||
|
||||
const Row = ({ children }: TableProps) => <tr>{children}</tr>;
|
||||
|
||||
const Cell = ({ children, widthClassNames }: TableProps & DynamicWidth) => (
|
||||
<td
|
||||
className={classNames(
|
||||
"text-default relative px-3 py-2 text-sm font-medium",
|
||||
!widthClassNames ? "w-auto" : widthClassNames
|
||||
)}>
|
||||
{children}
|
||||
</td>
|
||||
);
|
||||
|
||||
export const Table = ({ children }: TableProps) => (
|
||||
<div className="bg-default border-subtle overflow-x-auto overflow-y-hidden rounded-md border">
|
||||
<table className="divide-subtle w-full divide-y rounded-md">{children}</table>
|
||||
</div>
|
||||
);
|
||||
|
||||
Table.Header = Header;
|
||||
Table.ColumnTitle = ColumnTitle;
|
||||
Table.Body = Body;
|
||||
Table.Row = Row;
|
||||
Table.Cell = Cell;
|
||||
104
calcom/packages/ui/components/table/TableActions.tsx
Normal file
104
calcom/packages/ui/components/table/TableActions.tsx
Normal file
@@ -0,0 +1,104 @@
|
||||
import type { FC } from "react";
|
||||
import React from "react";
|
||||
|
||||
import type { IconName } from "../..";
|
||||
import type { ButtonBaseProps } from "../button";
|
||||
import { Button } from "../button";
|
||||
import {
|
||||
Dropdown,
|
||||
DropdownItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuPortal,
|
||||
DropdownMenuTrigger,
|
||||
} from "../form/dropdown";
|
||||
|
||||
export type ActionType = {
|
||||
id: string;
|
||||
icon?: IconName;
|
||||
iconClassName?: string;
|
||||
label: string;
|
||||
disabled?: boolean;
|
||||
color?: ButtonBaseProps["color"];
|
||||
bookingId?: number;
|
||||
} & (
|
||||
| { href: string; onClick?: never; actions?: never }
|
||||
| { href?: never; onClick: (e: React.MouseEvent<HTMLElement, MouseEvent>) => void; actions?: never }
|
||||
| { actions?: ActionType[]; href?: never; onClick?: never }
|
||||
);
|
||||
|
||||
interface Props {
|
||||
actions: ActionType[];
|
||||
}
|
||||
|
||||
const defaultAction = (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
export const DropdownActions = ({
|
||||
actions,
|
||||
actionTrigger,
|
||||
}: {
|
||||
actions: ActionType[];
|
||||
actionTrigger?: React.ReactNode;
|
||||
}) => {
|
||||
return (
|
||||
<Dropdown>
|
||||
{!actionTrigger ? (
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button type="button" color="secondary" variant="icon" StartIcon="ellipsis" />
|
||||
</DropdownMenuTrigger>
|
||||
) : (
|
||||
<DropdownMenuTrigger asChild>{actionTrigger}</DropdownMenuTrigger>
|
||||
)}
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuContent>
|
||||
{actions.map((action) => (
|
||||
<DropdownMenuItem key={action.id}>
|
||||
<DropdownItem
|
||||
type="button"
|
||||
color={action.color}
|
||||
data-testid={action.id}
|
||||
StartIcon={action.icon}
|
||||
href={action.href}
|
||||
data-bookingid={action.bookingId}
|
||||
onClick={action.onClick || defaultAction}>
|
||||
{action.label}
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenuPortal>
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
|
||||
export const TableActions: FC<Props> = ({ actions }) => {
|
||||
return (
|
||||
<>
|
||||
<div className="flex space-x-2 rtl:space-x-reverse">
|
||||
{actions.map((action) => {
|
||||
const button = (
|
||||
<Button
|
||||
className="whitespace-nowrap"
|
||||
key={action.id}
|
||||
data-testid={action.id}
|
||||
href={action.href}
|
||||
onClick={action.onClick || defaultAction}
|
||||
StartIcon={action.icon}
|
||||
{...(action?.actions ? { EndIcon: "chevron-down" } : null)}
|
||||
disabled={action.disabled}
|
||||
data-bookingid={action.bookingId}
|
||||
color={action.color || "secondary"}>
|
||||
{action.label}
|
||||
</Button>
|
||||
);
|
||||
if (!action.actions) {
|
||||
return button;
|
||||
}
|
||||
return <DropdownActions key={action.id} actions={action.actions} actionTrigger={button} />;
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
83
calcom/packages/ui/components/table/TableExamples.tsx
Normal file
83
calcom/packages/ui/components/table/TableExamples.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import { Table } from "./Table";
|
||||
import { TableActions } from "./TableActions";
|
||||
import {
|
||||
Table as TableNew,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
TableHead,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableFooter,
|
||||
TableCaption,
|
||||
} from "./TableNew";
|
||||
|
||||
export const TableNewExampleComponent = () => (
|
||||
<TableNew>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Header Column 1</TableHead>
|
||||
<TableHead>Header Column 2</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell>Row 1, Cell 1</TableCell>
|
||||
<TableCell>Row 1, Cell 2</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>Row 2, Cell 1</TableCell>
|
||||
<TableCell>Row 2, Cell 2</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TableCell>Row 3(footer), Cell 1</TableCell>
|
||||
<TableCell>Row 3(footer), Cell 2</TableCell>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
<TableCaption>Table Caption</TableCaption>
|
||||
</TableNew>
|
||||
);
|
||||
|
||||
export const TableExampleComponent = () => (
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.ColumnTitle>Title Column 1</Table.ColumnTitle>
|
||||
<Table.ColumnTitle>Title Column 2</Table.ColumnTitle>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>Row 1, Cell 1</Table.Cell>
|
||||
<Table.Cell>Row 1, Cell 2</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>Row 2, Cell 1</Table.Cell>
|
||||
<Table.Cell>Row 2, Cell 2</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<TableActions
|
||||
actions={[
|
||||
{
|
||||
id: "action1",
|
||||
label: "Action 1",
|
||||
href: "#1",
|
||||
},
|
||||
{
|
||||
id: "action2",
|
||||
label: "Action 2",
|
||||
actions: [
|
||||
{
|
||||
id: "action3",
|
||||
label: "Action 3",
|
||||
href: "#nested-action",
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
);
|
||||
89
calcom/packages/ui/components/table/TableNew.tsx
Normal file
89
calcom/packages/ui/components/table/TableNew.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { classNames } from "@calcom/lib";
|
||||
|
||||
const Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div className="w-full overflow-auto md:overflow-visible">
|
||||
<table
|
||||
ref={ref}
|
||||
className={classNames("border-subtle w-full caption-bottom border text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
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}
|
||||
/>
|
||||
)
|
||||
);
|
||||
TableHeader.displayName = "TableHeader";
|
||||
|
||||
const TableBody = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<tbody ref={ref} className={classNames("[&_tr:last-child]:border-0", className)} {...props} />
|
||||
)
|
||||
);
|
||||
TableBody.displayName = "TableBody";
|
||||
|
||||
const TableFooter = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<tfoot ref={ref} className={classNames("bg-default text-emphasis font-medium", className)} {...props} />
|
||||
)
|
||||
);
|
||||
TableFooter.displayName = "TableFooter";
|
||||
|
||||
const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTMLTableRowElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<tr
|
||||
ref={ref}
|
||||
className={classNames(
|
||||
"hover:bg-muted data-[state=selected]:bg-subtle border-subtle border-b",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
TableRow.displayName = "TableRow";
|
||||
|
||||
const TableHead = React.forwardRef<HTMLTableCellElement, React.ThHTMLAttributes<HTMLTableCellElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<th
|
||||
ref={ref}
|
||||
className={classNames(
|
||||
"text-default h-12 px-2 text-left align-middle font-medium [&:has([role=checkbox])]:pr-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
TableHead.displayName = "TableHead";
|
||||
|
||||
const TableCell = React.forwardRef<HTMLTableCellElement, React.TdHTMLAttributes<HTMLTableCellElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<td
|
||||
ref={ref}
|
||||
className={classNames("text-default px-2 py-2.5 align-middle [&:has([role=checkbox])]:pr-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
TableCell.displayName = "TableCell";
|
||||
|
||||
const TableCaption = React.forwardRef<HTMLTableCaptionElement, React.HTMLAttributes<HTMLTableCaptionElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<caption ref={ref} className={classNames("text-default mt-4 text-sm", className)} {...props} />
|
||||
)
|
||||
);
|
||||
TableCaption.displayName = "TableCaption";
|
||||
|
||||
export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption };
|
||||
48
calcom/packages/ui/components/table/table.stories.mdx
Normal file
48
calcom/packages/ui/components/table/table.stories.mdx
Normal file
@@ -0,0 +1,48 @@
|
||||
import { Canvas, Meta, Story } from "@storybook/addon-docs";
|
||||
|
||||
import {
|
||||
Examples,
|
||||
Example,
|
||||
Title,
|
||||
VariantsTable,
|
||||
CustomArgsTable,
|
||||
VariantRow,
|
||||
} from "@calcom/storybook/components";
|
||||
|
||||
import { Table } from "./Table";
|
||||
import { TableExampleComponent } from "./TableExamples";
|
||||
|
||||
<Meta title="UI/Table/Table" component={Table} />
|
||||
|
||||
<Title title="Table" suffix="Brief" subtitle="Version 1.0 — Last Update: 21 Aug 2023" />
|
||||
|
||||
## Definition
|
||||
|
||||
The `Table` component is a HTML table that utilizes Tailwind classes to enhance its styles.
|
||||
|
||||
## Structure
|
||||
|
||||
The `Table` component should be used with other table sub-components (present as object attributes) that follow the same pattern.
|
||||
These components render common elements that are styled with Tailwind. These components include:
|
||||
|
||||
- `Body` -> (`tbody`)
|
||||
- `Header` -> (`thead`)
|
||||
- `Row` -> (`tr`)
|
||||
- `ColumnTitle` -> (`th`)
|
||||
- `Cell` -> (`td`)
|
||||
|
||||
<CustomArgsTable of={Table} />
|
||||
|
||||
<Title offset title="Table" suffix="Variants" />
|
||||
|
||||
<Canvas>
|
||||
<Story name="Table">
|
||||
{(props) => (
|
||||
<VariantsTable titles={["Default"]} columnMinWidth={150}>
|
||||
<VariantRow>
|
||||
<TableExampleComponent />
|
||||
</VariantRow>
|
||||
</VariantsTable>
|
||||
)}
|
||||
</Story>
|
||||
</Canvas>
|
||||
52
calcom/packages/ui/components/table/table.test.tsx
Normal file
52
calcom/packages/ui/components/table/table.test.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
/* eslint-disable playwright/missing-playwright-await */
|
||||
import { fireEvent, render } from "@testing-library/react";
|
||||
import { vi } from "vitest";
|
||||
|
||||
import { TableActions } from "@calcom/ui";
|
||||
|
||||
import { TableNewExampleComponent } from "./TableExamples";
|
||||
|
||||
const mockActions = [
|
||||
{
|
||||
id: "action1",
|
||||
label: "Action 1",
|
||||
href: "#",
|
||||
},
|
||||
{
|
||||
id: "action2",
|
||||
label: "Action 2",
|
||||
onClick: vi.fn(),
|
||||
},
|
||||
];
|
||||
|
||||
describe("tests for Table component", () => {
|
||||
test("Should render Table component correctly", () => {
|
||||
const { getByRole, getByText } = render(<TableNewExampleComponent />);
|
||||
|
||||
const headerElement1 = getByRole("columnheader", { name: "Header Column 1" });
|
||||
const headerElement2 = getByRole("columnheader", { name: "Header Column 2" });
|
||||
expect(headerElement1).toBeInTheDocument();
|
||||
expect(headerElement2).toBeInTheDocument();
|
||||
|
||||
expect(getByText("Row 1, Cell 1")).toBeInTheDocument();
|
||||
expect(getByText("Row 1, Cell 2")).toBeInTheDocument();
|
||||
expect(getByText("Row 2, Cell 1")).toBeInTheDocument();
|
||||
expect(getByText("Row 2, Cell 2")).toBeInTheDocument();
|
||||
|
||||
const captionElement = getByText("Table Caption");
|
||||
expect(captionElement).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("Should render TableActions component correctly", () => {
|
||||
const { getByText } = render(<TableActions actions={mockActions} />);
|
||||
|
||||
const action1Button = getByText("Action 1");
|
||||
const action2Button = getByText("Action 2");
|
||||
|
||||
expect(action1Button).toBeInTheDocument();
|
||||
expect(action2Button).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(action2Button);
|
||||
expect(mockActions[1].onClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
50
calcom/packages/ui/components/table/tableNew.stories.mdx
Normal file
50
calcom/packages/ui/components/table/tableNew.stories.mdx
Normal file
@@ -0,0 +1,50 @@
|
||||
import { Canvas, Meta, Story } from "@storybook/addon-docs";
|
||||
|
||||
import {
|
||||
Examples,
|
||||
Example,
|
||||
Title,
|
||||
VariantsTable,
|
||||
CustomArgsTable,
|
||||
VariantRow,
|
||||
} from "@calcom/storybook/components";
|
||||
|
||||
import { TableNewExampleComponent } from "./TableExamples";
|
||||
import { Table } from "./TableNew";
|
||||
|
||||
<Meta title="UI/Table/TableNew" component={Table} />
|
||||
|
||||
<Title title="Table New" suffix="Brief" subtitle="Version 1.0 — Last Update: 21 Aug 2023" />
|
||||
|
||||
## Definition
|
||||
|
||||
The `Table` component (from TableNew.tsx) is a HTML table that utilizes Tailwind classes to enhance its styles.
|
||||
|
||||
## Structure
|
||||
|
||||
The `Table` component should be used with other table sub-components (exported in the same file) that follow the same pattern.
|
||||
These components render common elements that are styled with Tailwind. These components include:
|
||||
|
||||
- `TableBody` -> (`tbody`)
|
||||
- `TableHeader` -> (`thead`)
|
||||
- `TableRow` -> (`tr`)
|
||||
- `TableHead` -> (`th`)
|
||||
- `TableCell` -> (`td`)
|
||||
- `TableFooter` -> (`tfoot`)
|
||||
- `TableCaption` -> (`caption`)
|
||||
|
||||
<CustomArgsTable of={Table} />
|
||||
|
||||
<Title offset title="Table New" suffix="Variants" />
|
||||
|
||||
<Canvas>
|
||||
<Story name="Table New">
|
||||
{(props) => (
|
||||
<VariantsTable titles={["Default"]} columnMinWidth={150}>
|
||||
<VariantRow>
|
||||
<TableNewExampleComponent />
|
||||
</VariantRow>
|
||||
</VariantsTable>
|
||||
)}
|
||||
</Story>
|
||||
</Canvas>
|
||||
Reference in New Issue
Block a user