diff --git a/.eslintrc b/.eslintrc index 2fb31aab6..a1d9e0450 100644 --- a/.eslintrc +++ b/.eslintrc @@ -2,6 +2,15 @@ "parser": "@typescript-eslint/parser", "extends": ["@nulogy/nulogy"], "rules": { + "@typescript-eslint/ban-types": [ + "error", + { + "extendDefaults": true, + "types": { + "{}": false + } + } + ], "jsx-a11y/label-has-associated-control": [ 2, { diff --git a/src/SortingTable/SortingTable.tsx b/src/SortingTable/SortingTable.tsx index edeb7ee60..5bf9a005d 100644 --- a/src/SortingTable/SortingTable.tsx +++ b/src/SortingTable/SortingTable.tsx @@ -3,11 +3,11 @@ import { Table } from "../Table"; import type { TableProps } from "../Table"; import type { RowType, ColumnType } from "../Table/Table.types"; -type SortingTableProps = TableProps & { +type SortingTableProps = TableProps & { initialSortColumn: string; }; -type SortableColumnType = ColumnType & { numeric?: boolean }; +type SortableColumnType = ColumnType & { numeric?: boolean }; type SortState = { ascending: boolean; @@ -17,26 +17,31 @@ type SortState = { const numericAlphabeticalSort = (a, b, numeric) => String(a).localeCompare(b, undefined, { numeric, sensitivity: "base" }); -const applySort = (rows: RowType[], sortColumn: string, columns: SortableColumnType[]) => - [...rows].sort((a, b) => { +function applySort(rows: RowType[], sortColumn: string, columns: SortableColumnType[]) { + return [...rows].sort((a, b) => { const column = columns.find((col) => col.dataKey === sortColumn); const { numeric } = column; return numericAlphabeticalSort(a[sortColumn], b[sortColumn], numeric); }); +} -const sortRows = (rows: RowType[], columns: SortableColumnType[], sortState: SortState) => { +function sortRows( + rows: RowType[], + columns: SortableColumnType[], + sortState: SortState +) { const sortedRows = applySort(rows, sortState.sortColumn, columns); return sortState.ascending ? sortedRows : sortedRows.reverse(); -}; +} -const SortingTable: React.FC = ({ +function SortingTable({ columns: incomingColumns, rows: incomingRows, initialSortColumn, ...props -}) => { +}: SortingTableProps) { const [sortState, setSortState] = useState({ ascending: true, sortColumn: initialSortColumn, @@ -77,6 +82,6 @@ const SortingTable: React.FC = ({ const columns = incomingColumns.map((column) => transformColumn(column)); return ; -}; +} export default SortingTable; diff --git a/src/Table/BaseTable.story.tsx b/src/Table/BaseTable.story.tsx index aa824c025..2f6e96731 100644 --- a/src/Table/BaseTable.story.tsx +++ b/src/Table/BaseTable.story.tsx @@ -1,9 +1,8 @@ -/* eslint-disable react/prop-types */ import React from "react"; import styled from "styled-components"; import { boolean, text } from "@storybook/addon-knobs"; import { action } from "@storybook/addon-actions"; -import { Box, DropdownButton, DropdownMenu, Button, Text } from ".."; +import { Box, DropdownButton, DropdownMenu, Button, Text, Flex } from ".."; import { getMockRows, mockColumns } from "./Table.mock-utils"; import { Columns } from "./Table.types"; import { Table } from "."; @@ -12,8 +11,6 @@ const dateToString = ({ cellData }) => { return new Date(cellData).toUTCString().split(" ").splice(0, 4).join(" "); }; -const buttonRenderer = ({ label }) => ; - const sectionRow = ({ cellData }) => ( @@ -31,7 +28,7 @@ const dropdownCellRenderer = ({ cellData }) => ( ); -const columns: Columns = [ +const columns: Columns<{}> = [ { label: "Date", dataKey: "date" }, { label: "Expected Quantity", dataKey: "expectedQuantity" }, { label: "Actual Quantity", dataKey: "actualQuantity", align: "right" }, @@ -43,6 +40,7 @@ const columnsWithWidths = [ { label: "Actual Quantity", dataKey: "actualQuantity" }, { label: "Note", dataKey: "note", width: "50%" }, ]; + const rowData = [ { date: "2019-10-01", @@ -185,7 +183,7 @@ const columnsWithFormatter = [ { label: "Actual Quantity", dataKey: "actualQuantity" }, ]; -const columnsWithAlignment: Columns = [ +const columnsWithAlignment: Columns<{}> = [ { label: "Date", dataKey: "date" }, { label: "Expected Eaches", dataKey: "expectedQuantity" }, { label: "Actual Eaches", dataKey: "actualQuantity", align: "right" }, @@ -197,13 +195,6 @@ const getColumnsWithCellRenderer = (cellRenderer) => [ { label: "", dataKey: "actualQuantity", cellRenderer }, ]; -const getColumnsWithHeaderFormatter = (headerFormatter) => [ - { label: "Date", dataKey: "date" }, - { label: "Expected Quantity", dataKey: "expectedQuantity" }, - { label: "Actual Quantity", dataKey: "actualQuantity" }, - { label: "Add record", dataKey: "c4", headerFormatter }, -]; - const footerRowData = [ { date: "Total", @@ -280,10 +271,6 @@ export const WithData = () => ( /> ); -WithData.story = { - name: "with data", -}; - export const WithNoData = () => (
( /> ); -WithNoData.story = { - name: "with no data", -}; - export const WithStickyHeader = () => (
( ); -WithStickyHeader.story = { - name: "with sticky header", -}; - export const WithLotsOfRowsAndColumns = () => (
( /> ); -WithLotsOfRowsAndColumns.story = { - name: "with lots of rows and columns", -}; - export const WithCustomColumnWidths = () =>
; -WithCustomColumnWidths.story = { - name: "with custom column widths", -}; - export const WithACustomCellComponent = () => (
); -WithACustomCellComponent.story = { - name: "with a custom cell component", -}; - export const WithCellAlignment = () =>
; -WithCellAlignment.story = { - name: "with cell alignment", -}; - export const WithACellFormatter = () =>
; -WithACellFormatter.story = { - name: "with a cell formatter", -}; - export const WithACustomColumnLabelComponent = () => ( -
+
, + }, + ]} + rows={rowData} + /> ); -WithACustomColumnLabelComponent.story = { - name: "with a custom column label component", -}; +export const WithMetadata = () => ( +
( + + {label} + + {metadata.helpText} + + + ), + }, + ]} + rows={rowData} + /> +); export const WithFullWidthSection = () =>
; -WithFullWidthSection.story = { - name: "with full width section", -}; - export const WithAFooter = () => ( <>
( ); -WithAFooter.story = { - name: "with a footer", -}; -/* eslint-enable react/prop-types */ - const TableWithBorderedRows = styled(Table)` border-collapse: collapse; diff --git a/src/Table/BaseTable.tsx b/src/Table/BaseTable.tsx index 00f95ebe6..e1cf47638 100644 --- a/src/Table/BaseTable.tsx +++ b/src/Table/BaseTable.tsx @@ -8,8 +8,8 @@ import TableBody from "./TableBody"; import TableFoot from "./TableFoot"; import { rowsPropType, RowType, Columns } from "./Table.types"; -export type BaseTableProps = { - columns: Columns; +export type BaseTableProps = { + columns: Columns; rows: RowType[]; noRowsContent?: string; keyField?: string; @@ -33,7 +33,7 @@ const StyledTable = styled.table(space, { position: "relative", }); -const BaseTable: React.FC = ({ +function BaseTable({ columns, rows, noRowsContent = "No records have been created for this table.", @@ -48,25 +48,27 @@ const BaseTable: React.FC = ({ onRowMouseEnter = () => {}, onRowMouseLeave = () => {}, ...props -}) => ( - - - - {footerRows && ( - - )} - -); +}: BaseTableProps) { + return ( + + + + {footerRows && ( + + )} + + ); +} BaseTable.propTypes = { ...propTypes.space, diff --git a/src/Table/StatefulTable.tsx b/src/Table/StatefulTable.tsx index 937c700e8..97bdb2e67 100644 --- a/src/Table/StatefulTable.tsx +++ b/src/Table/StatefulTable.tsx @@ -5,7 +5,7 @@ import BaseTable, { BaseTableProps } from "./BaseTable"; import { addExpandableControl } from "./addExpandableControl"; import { addSelectableControl } from "./addSelectableControl"; -export type StatefulTableProps = BaseTableProps & { +export type StatefulTableProps = BaseTableProps & { selectedRows?: string[]; onRowSelectionChange?: (...args: any[]) => any; onRowExpansionChange?: (...args: any[]) => any; @@ -36,7 +36,7 @@ type StatefulTableState = { currentPage: number; paginatedRows: any; }; -class StatefulTable extends Component { +class StatefulTable extends Component, StatefulTableState> { static defaultProps = { ...BaseTable.defaultProps, hasSelectableRows: false, @@ -208,7 +208,7 @@ class StatefulTable extends Component { onRowExpansionChange: this.onExpandRow, expandedRows, }; - const props: StatefulTableProps = { + const props: StatefulTableProps = { ...this.props, rows: this.rowsByPageSelector(currentPage) || [], ...(hasSelectableRows && selectionConfig), diff --git a/src/Table/Table.tsx b/src/Table/Table.tsx index 97e42e886..4f276202d 100644 --- a/src/Table/Table.tsx +++ b/src/Table/Table.tsx @@ -5,12 +5,12 @@ import SortingColumnHeader from "./SortingColumnHeader"; import { StatefulTableProps } from "./StatefulTable"; import { ColumnType, RowType, CellInfoType } from "./Table.types"; -export type TableProps = StatefulTableProps; -export type TableColumnType = ColumnType; +export type TableProps = StatefulTableProps; +export type TableColumnType = ColumnType; export type TableRowType = RowType; -export type TableCellInfoType = CellInfoType; +export type TableCellInfoType = CellInfoType; -const Table = ({ +function Table({ hasSelectableRows, rowsPerPage, hasExpandableRows, @@ -23,9 +23,9 @@ const Table = ({ paginationCss, paginationProps, ...props -}: TableProps) => - hasSelectableRows || rowsPerPage || hasExpandableRows ? ( - ) { + return hasSelectableRows || rowsPerPage || hasExpandableRows ? ( + hasExpandableRows={hasExpandableRows} hasSelectableRows={hasSelectableRows} onRowExpansionChange={onRowExpansionChange} @@ -42,6 +42,7 @@ const Table = ({ ) : ( ); +} Table.SortingHeader = SortingColumnHeader; export default Table; diff --git a/src/Table/Table.types.ts b/src/Table/Table.types.ts index 10397e1c3..2477823c9 100644 --- a/src/Table/Table.types.ts +++ b/src/Table/Table.types.ts @@ -3,32 +3,26 @@ import PropTypes from "prop-types"; export type RowType = unknown; -export interface CellInfoType { +export interface CellInfoType { cellData: unknown; - column: ColumnType; + column: ColumnType; row: RowType; } -interface ColumnInfoType { - align?: ColumnAlignment; - label: string; - dataKey?: Key; - width?: string | number; -} - type ColumnAlignment = "left" | "right" | "center"; -export type ColumnType = { +export type ColumnType = { align?: ColumnAlignment; label?: string; - cellFormatter?: (cell: CellInfoType) => React.ReactNode; - cellRenderer?: (cell: CellInfoType) => React.ReactNode; - headerRenderer?: (column: ColumnInfoType) => React.ReactNode; - headerFormatter?: (column: ColumnInfoType) => React.ReactNode; + cellFormatter?: (cell: CellInfoType) => React.ReactNode; + cellRenderer?: (cell: CellInfoType) => React.ReactNode; + headerRenderer?: (column: ColumnType) => React.ReactNode; + headerFormatter?: (column: ColumnType) => React.ReactNode; width?: string | number; + metadata?: T; } & ({ key: Key; dataKey?: never | undefined } | { dataKey: Key; key?: never | undefined }); -export type Columns = ColumnType[]; +export type Columns = ColumnType[]; export const columnPropType = PropTypes.shape({ align: PropTypes.oneOf(["right", "left", "center"]), diff --git a/src/Table/TableFoot.tsx b/src/Table/TableFoot.tsx index b0d1cd3e9..88d6d47c9 100644 --- a/src/Table/TableFoot.tsx +++ b/src/Table/TableFoot.tsx @@ -55,19 +55,21 @@ TableFooterRow.propTypes = { compact: PropTypes.bool.isRequired, }; -const TableFoot = ({ +function TableFoot({ columns, rows, keyField, loading, compact, }: { - columns: Columns; + columns: Columns; rows: RowType[]; keyField?: string; loading?: boolean; compact?: boolean; -}) => {renderRows(rows, columns, keyField, loading, compact)}; +}) { + return {renderRows(rows, columns, keyField, loading, compact)}; +} TableFoot.propTypes = { columns: columnsPropType.isRequired, diff --git a/src/Table/TableHead.tsx b/src/Table/TableHead.tsx index 02e743efc..1e3afc859 100644 --- a/src/Table/TableHead.tsx +++ b/src/Table/TableHead.tsx @@ -3,8 +3,8 @@ import styled from "styled-components"; import StyledTh from "./StyledTh"; import type { ColumnType, Columns } from "./Table.types"; -interface TableHeadProps { - columns: Columns; +interface TableHeadProps { + columns: Columns; compact?: boolean; sticky?: boolean; } @@ -14,18 +14,22 @@ const StyledHeaderRow = styled.tr(({ theme }) => ({ borderBottom: `1px solid ${theme.colors.lightGrey}`, })); -const defaultheaderFormatter: ColumnType["headerFormatter"] = ({ label }) => label; - -const renderHeaderCellContent = ({ - headerFormatter = defaultheaderFormatter, +function renderHeaderCellContent({ + headerFormatter = ({ label }) => label, align, label, - dataKey, width, -}: ColumnType) => headerFormatter({ align, label, dataKey, width }); + metadata, + dataKey, + key, +}: ColumnType) { + return key + ? headerFormatter({ align, label, width, metadata, key }) + : headerFormatter({ align, label, width, metadata, dataKey }); +} -const TableHead = ({ columns, compact, sticky }: TableHeadProps) => { - const renderColumns = (allColumns: Columns) => +function TableHead({ columns, compact, sticky }: TableHeadProps) { + const renderColumns = (allColumns: Columns) => allColumns.map((column, index) => ( { {renderColumns(columns)} ); -}; +} export default TableHead;