From 274dab78837f6be3674cd95d9f4b5fa0302ed9cc Mon Sep 17 00:00:00 2001 From: Ariel Ferro Date: Wed, 20 Mar 2024 14:52:43 -0300 Subject: [PATCH] chore: implemented table row selection --- packages/react/src/components/Table/Table.tsx | 2 - .../src/components/Table/TableHeader.tsx | 10 +-- .../react/src/components/Table/TableRow.tsx | 21 ++++-- .../react/src/components/Table/context.ts | 55 +++++++++----- .../src/components/Table/index.stories.tsx | 72 +++++++++++-------- packages/react/src/components/Table/types.ts | 13 +++- 6 files changed, 112 insertions(+), 61 deletions(-) diff --git a/packages/react/src/components/Table/Table.tsx b/packages/react/src/components/Table/Table.tsx index 3363e3769..7a5fe6900 100644 --- a/packages/react/src/components/Table/Table.tsx +++ b/packages/react/src/components/Table/Table.tsx @@ -4,8 +4,6 @@ import { TableContext, useInitTableState } from './context'; export const Table: React.FC = ({ className: tableClassName = 'nds-table', - // TODO: do we need this? - // selectableClass = `${tableClassName}--selectable`, children, selectable, onSelect, diff --git a/packages/react/src/components/Table/TableHeader.tsx b/packages/react/src/components/Table/TableHeader.tsx index a50a8fc1d..31b124464 100644 --- a/packages/react/src/components/Table/TableHeader.tsx +++ b/packages/react/src/components/Table/TableHeader.tsx @@ -3,17 +3,19 @@ import { TableHeaderProps } from './types'; import { useTableState } from './context'; import { TableHeadCell } from './TableHeadCell'; import { Checkbox } from '../Checkbox'; +import { useId } from '../../utilities'; import { TableRow } from './TableRow'; export const TableHeader: React.FC = ({ children, className = 'nds-table__header' }) => { - const { selectable, onSelect } = useTableState(); + const { selectable, onSelect, onSelectedAll, isSelectedAll } = useTableState(); + const uniqueId = useId(); return ( - { selectable ? ( - - + { selectable && onSelect ? ( + + ) : null } {children} diff --git a/packages/react/src/components/Table/TableRow.tsx b/packages/react/src/components/Table/TableRow.tsx index de93397ce..9749c7fac 100644 --- a/packages/react/src/components/Table/TableRow.tsx +++ b/packages/react/src/components/Table/TableRow.tsx @@ -1,22 +1,35 @@ -import React from 'react'; -import classNames from 'classnames'; -import { TableRowProps } from './types'; +import React from "react"; +import classNames from "classnames"; +import { TableRowProps } from "./types"; +import { useTableState } from './context'; +import { Checkbox } from '../Checkbox'; +import { useId } from '../../utilities'; export const TableRow: React.FC = ({ - baseName = 'nds-table-row', + baseName = "nds-table-row", headerClass = `${baseName}__header`, sectionHeaderClass = `${baseName}__header--section`, isHeader, isSectionHeader, children, }) => { + + const { selectable, onSelect, selected, onSelected, isSelectedAll } = useTableState(); + const uniqueId = useId(); + const trClassName = classNames(baseName, { [`${headerClass}`]: isHeader, [`${sectionHeaderClass}`]: isSectionHeader, }); +const isSelected =isSelectedAll || selected?.includes(`${uniqueId}`); +const showCheckbox = selectable && onSelect && !isHeader && !isSectionHeader return ( + { showCheckbox ? ( + + + ) : null } {children} ); diff --git a/packages/react/src/components/Table/context.ts b/packages/react/src/components/Table/context.ts index 73b4952b3..a2caf9e00 100644 --- a/packages/react/src/components/Table/context.ts +++ b/packages/react/src/components/Table/context.ts @@ -1,30 +1,49 @@ -import React, { useMemo, useState } from 'react'; +import React, { useState } from "react"; +import { TableState } from "./types"; -export interface TableState { - selectable?: boolean; - onSelect?: () => void; -} - -export const TableContext = React.createContext(undefined); +export const TableContext = React.createContext( + undefined +); export function useTableState() { const result = React.useContext(TableContext); if (!result) { - throw new Error('TableContext not initialized'); + throw new Error("TableContext not initialized"); } return result; } -export function useInitTableState({ selectable }: TableState): TableState { - // TODO: should we do something with onSelect here? - const [innerSelectable, setSelectable] = useState(selectable); - - return useMemo( - () => ({ - selectable: innerSelectable, - setSelectable, - }), - [innerSelectable], +export function useInitTableState({ + selectable, + onSelect, + selected, +}: TableState): TableState { + const [isSelectedAll, setIsSelectedAll] = React.useState(false); + const [uniqueIds, setUniqueIds] = useState>( + new Set(selected || []) ); + + const onSelectedAll = (event: React.ChangeEvent) => { + setIsSelectedAll(event.target.checked || false); + onSelected(event); + }; + const onSelected = (event: React.ChangeEvent) => { + const newSet = new Set(uniqueIds); // Create a copy to avoid mutation + if (event.target.checked) { + newSet.add(event.target.id); + } else { + newSet.delete(event.target.id); + } + setUniqueIds(newSet); + onSelect?.([...newSet]); + }; + return { + selectable, + onSelect, + selected: [...uniqueIds], + onSelected, + onSelectedAll, + isSelectedAll, + }; } diff --git a/packages/react/src/components/Table/index.stories.tsx b/packages/react/src/components/Table/index.stories.tsx index dffdf19d2..45df3e794 100644 --- a/packages/react/src/components/Table/index.stories.tsx +++ b/packages/react/src/components/Table/index.stories.tsx @@ -1,11 +1,15 @@ -import React from 'react'; -import type { Meta, StoryObj } from '@storybook/react'; +import React from "react"; +import type { Meta, StoryObj } from "@storybook/react"; import { - Table, TableHeadCell, TableBody, TableRow, TableHeader, -} from './index'; + Table, + TableHeadCell, + TableBody, + TableRow, + TableHeader, +} from "./index"; const meta: Meta = { - title: 'Table', + title: "Table", component: Table, }; export default meta; @@ -14,31 +18,39 @@ type Story = StoryObj; // default table - presentation only export const Default: Story = { - render: () => ( - - - { }}> - Header 1 - - { }}> - Header 2 - - Header 3 - - - - - - - - - - - - - -
Row 1, Cell 1Row 1, Cell 2Row 1, Cell 3Row 2, Cell 1Row 2, Cell 2Row 2, Cell 3
- ), + render: () => { + const [message, setMessage] = React.useState("0 row(s) selected"); + const handleSelect = (selected?: string[] ) => selected && setMessage(`${ selected.includes('check-all') ? 'All' : selected.length } row(s) selected!`); + + return ( + <> + + + {}}> + Header 1 + + {}}> + Header 2 + + Header 3 + + + + + + + + + + + + + +
Row 1, Cell 1Row 1, Cell 2Row 1, Cell 3Row 2, Cell 1Row 2, Cell 2Row 2, Cell 3
+
{message}
+ + ); + }, }; // selectable table diff --git a/packages/react/src/components/Table/types.ts b/packages/react/src/components/Table/types.ts index cfada83ea..9977f0894 100644 --- a/packages/react/src/components/Table/types.ts +++ b/packages/react/src/components/Table/types.ts @@ -1,8 +1,17 @@ +export interface TableState { + selectable?: boolean; + isSelectedAll?: boolean; + onSelect?: (data?: any) => void; + onSelected?: (event: React.ChangeEvent) => void; + onSelectedAll?: (event: React.ChangeEvent) => void; + selected?: string[]; +} + export interface TableProps { className?: string; selectableClass?: string; selectable?: boolean; - onSelect?: () => void; + onSelect?: (data?: any) => void; } export interface TableRowProps { @@ -17,8 +26,6 @@ export interface TableHeadCellProps { className?: string; sortType?: 'none' | 'ascending' | 'descending' | 'other' | undefined; onSort?: VoidFunction; - // TODO: do we need to include this here? - onSelect?: VoidFunction } export interface TableHeaderProps {