Skip to content

Commit

Permalink
Add Table changes
Browse files Browse the repository at this point in the history
  • Loading branch information
vineethasok committed Nov 7, 2023
1 parent 62c7d96 commit 35752b3
Show file tree
Hide file tree
Showing 4 changed files with 217 additions and 75 deletions.
24 changes: 24 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,30 @@ import {
Select,
Text,
EllipsisContent,
Table,
} from "@/components";
import { Dialog } from "@/components/Dialog/Dialog";
import ConfirmationDialog from "@/components/ConfirmationDialog/ConfirmationDialog";

const headers = [{ label: "Company" }, { label: "Contact" }, { label: "Country" }];
const rows = [
{
id: "row-1",
cells: [
{ label: "Alfreds Futterkiste" },
{ label: "Maria Anders" },
{ label: "Germany" },
],
},
{
id: "row-2",
cells: [
{ label: "Centro comercial Moctezuma" },
{ label: "Francisco Chang" },
{ label: "Mexico" },
],
},
];
const App = () => {
const [currentTheme, setCurrentTheme] = useState<ThemeName>("dark");
const [selectedButton, setSelectedButton] = useState("center1");
Expand Down Expand Up @@ -430,6 +450,10 @@ const App = () => {
faucibus mi egestas interdum.
</EllipsisContent>
</ClickUIProvider>
<Table
headers={headers}
rows={rows}
/>
</div>
);
};
Expand Down
10 changes: 9 additions & 1 deletion src/components/Table/Table.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ import { Table } from "./Table";
const headers = [{ label: "Company" }, { label: "Contact" }, { label: "Country" }];
const rows = [
{
id: "row-1",
cells: [
{ label: "Alfreds Futterkiste" },
{ label: "Maria Anders" },
{ label: "Germany" },
],
},
{
checked: true,
id: "row-2",
cells: [
{ label: "Centro comercial Moctezuma" },
{ label: "Francisco Chang" },
Expand All @@ -23,11 +24,18 @@ export default {
component: Table,
title: "Display/Table",
tags: ["table", "autodocs"],
argTypes: {
selectedIndices: {
control: { type: "object" },
if: { arg: "isSelectable", exists: true },
},
},
};

export const Playground = {
args: {
headers,
rows,
selectedIndices: [],
},
};
71 changes: 71 additions & 0 deletions src/components/Table/Table.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { fireEvent } from "@testing-library/react";
import { Table, TableProps } from "./Table";
import { renderCUI } from "@/utils/test-utils";

const headers = [{ label: "Company" }, { label: "Contact" }, { label: "Country" }];

const rows = [
{
id: "row-1",
cells: [
{ label: "Alfreds Futterkiste" },
{ label: "Maria Anders" },
{ label: "Germany" },
],
},
{
id: "row-2",
cells: [
{ label: "Centro comercial Moctezuma" },
{ label: "Francisco Chang" },
{ label: "Mexico" },
],
},
];

describe("Table", () => {
const renderTable = (props: Omit<TableProps, "headers" | "rows">) =>
renderCUI(
<Table
headers={headers}
rows={rows}
data-testid="table"
{...props}
/>
);

it("should render the Table", () => {
const { queryByTestId } = renderTable({});
expect(queryByTestId("table")).not.toBeNull();
expect(queryByTestId("checkbox")).toBeNull();
});

it("should show checkbox on isSelectable", () => {
const { queryByTestId, queryAllByTestId } = renderTable({
isSelectable: true,
selectedIndices: [],
});
expect(queryByTestId("table")).not.toBeNull();
expect(queryAllByTestId("checkbox")[0]).not.toBeNull();
expect(queryAllByTestId("checkbox")[1]).not.toBeNull();
});

it("should trigger onSelect on clicking checkbox", () => {
const onSelect = jest.fn();
const { queryByTestId, queryAllByTestId } = renderTable({
isSelectable: true,
selectedIndices: [],
onSelect,
});
expect(queryByTestId("table")).not.toBeNull();
expect(queryAllByTestId("checkbox")).toHaveLength(4);
const selectAllCheckbox = queryAllByTestId("checkbox")[1];
const rowCheckbox = queryAllByTestId("checkbox")[2];
expect(selectAllCheckbox).not.toBeNull();
fireEvent.click(selectAllCheckbox);
expect(onSelect).toBeCalledTimes(1);
expect(rowCheckbox).not.toBeNull();
fireEvent.click(selectAllCheckbox);
expect(onSelect).toBeCalledTimes(2);
});
});
187 changes: 113 additions & 74 deletions src/components/Table/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const TableHeader = ({
</StyledHeader>
);

const TableRow = styled.tr<{ $selectable?: boolean }>`
const TableRow = styled.tr<{ $isSelectable?: boolean }>`
overflow: hidden;
${({ theme }) => `
background-color: ${theme.click.table.row.color.background.default};
Expand All @@ -71,11 +71,11 @@ const TableRow = styled.tr<{ $selectable?: boolean }>`
position: relative;
display: flex;
flex-wrap: wrap;
${({ theme, $selectable = false }) => `
${({ theme, $isSelectable = false }) => `
border: 1px solid ${theme.click.table.row.color.stroke.default};
border-radius: ${theme.click.table.radii.all};
${
$selectable
$isSelectable
? `padding-left: calc(${theme.click.table.body.cell.space.sm.x} + ${theme.click.table.body.cell.space.sm.x} + 1rem);`
: ""
}
Expand Down Expand Up @@ -172,8 +172,9 @@ const MobileActions = styled.div`
interface TableCellType extends HTMLAttributes<HTMLTableCellElement> {
label: ReactNode;
}
interface TableRowType extends HTMLAttributes<HTMLTableRowElement> {
checked?: boolean;
interface TableRowType
extends Omit<HTMLAttributes<HTMLTableRowElement>, "onSelect" | "id"> {
id: string | number;
cells: Array<TableCellType>;
}

Expand All @@ -183,87 +184,125 @@ interface CommonTableProps
rows: Array<TableRowType>;
}

interface SelectionType extends CommonTableProps {
selectable?: boolean;
onSelect: (index: "all" | number, checked: boolean) => void;
interface SelectionType {
isSelectable?: boolean;
selectedIndices?: Array<number | string>;
onSelect?: (indices: Array<string | number>) => void;
}

interface NoSelectionType extends CommonTableProps {
selectable?: never;
interface NoSelectionType {
isSelectable?: never;
selectedIndices?: never;
onSelect?: never;
}

type TableProps = SelectionType | NoSelectionType;
export type TableProps = CommonTableProps & (SelectionType | NoSelectionType);

const Table = forwardRef<HTMLTableElement, TableProps>(
({ headers, rows, selectable, onSelect, ...props }, ref) => (
<TableOuterContainer>
<MobileActions>
{selectable && (
interface TableRowProps extends TableRowType {
headers: Array<TableHeaderProps>;
}

type TableBodyRowProps = TableRowProps & (SelectionType | NoSelectionType);
const TableBodyRow = ({
id,

headers,
cells,
onSelect: onSelectProp,
isSelectable,
selectedIndices,
...rowProps
}: TableBodyRowProps) => {
const onSelect = (checked: boolean): void => {
if (selectedIndices && typeof onSelectProp === "function") {
const ids = checked
? [...selectedIndices, id]
: selectedIndices.filter(selectedId => id !== selectedId);
onSelectProp(ids);
}
};
return (
<TableRow
$isSelectable={isSelectable}
{...rowProps}
>
{isSelectable && (
<SelectData>
<Checkbox
label="Select All"
checked={rows.every(row => row.checked)}
onCheckedChange={(checked: boolean): void => onSelect("all", checked)}
checked={selectedIndices?.includes(id)}
onCheckedChange={onSelect}
/>
)}
</MobileActions>
<TableWrapper>
<StyledTable
ref={ref}
{...props}
</SelectData>
)}
{cells.map(({ label, ...cellProps }, cellIndex) => (
<TableData
key={`table-cell-${cellIndex}`}
{...cellProps}
>
<THead>
<tr>
{selectable && (
<StyledHeader>
<Checkbox
onCheckedChange={(checked: boolean) =>
onSelect && onSelect("all", checked)
}
{headers[cellIndex] && <MobileHeader>{headers[cellIndex].label}</MobileHeader>}
<span>{label}</span>
</TableData>
))}
</TableRow>
);
};

const Table = forwardRef<HTMLTableElement, TableProps>(
({ headers, rows, isSelectable, selectedIndices = [], onSelect, ...props }, ref) => {
const onSelectAll = (checked: boolean): void => {
if (typeof onSelect === "function") {
const ids = checked ? rows.map(row => row.id) : [];
onSelect(ids);
}
};
return (
<TableOuterContainer>
<MobileActions>
{isSelectable && (
<Checkbox
label="Select All"
checked={selectedIndices.length === rows.length}
onCheckedChange={onSelectAll}
/>
)}
</MobileActions>
<TableWrapper>
<StyledTable
ref={ref}
{...props}
>
<THead>
<tr>
{isSelectable && (
<StyledHeader>
<Checkbox onCheckedChange={onSelectAll} />
</StyledHeader>
)}
{headers.map((headerProps, index) => (
<TableHeader
key={`table-header-${index}`}
{...headerProps}
/>
</StyledHeader>
)}
{headers.map((headerProps, index) => (
<TableHeader
key={`table-header-${index}`}
{...headerProps}
))}
</tr>
</THead>
<Tbody>
{rows.map((rowProps, rowIndex) => (
<TableBodyRow
key={`table-body-row-${rowIndex}`}
headers={headers}
selectedIndices={selectedIndices}
isSelectable={isSelectable}
onSelect={onSelect}
{...rowProps}
/>
))}
</tr>
</THead>
<Tbody>
{rows.map(({ cells, checked, ...rowProps }, rowIndex) => (
<TableRow
key={`table-body-row-${rowIndex}`}
$selectable={selectable}
{...rowProps}
>
{selectable && (
<SelectData>
<Checkbox
checked={checked}
onCheckedChange={(checked: boolean) => onSelect(rowIndex, checked)}
/>
</SelectData>
)}
{cells.map(({ label, ...cellProps }, index) => (
<TableData
key={`table-cell-${index}`}
{...cellProps}
>
{headers[index] && (
<MobileHeader>{headers[index].label}</MobileHeader>
)}
<span>{label}</span>
</TableData>
))}
</TableRow>
))}
</Tbody>
</StyledTable>
</TableWrapper>
</TableOuterContainer>
)
</Tbody>
</StyledTable>
</TableWrapper>
</TableOuterContainer>
);
}
);

const StyledTable = styled.table`
Expand Down

0 comments on commit 35752b3

Please sign in to comment.