From 52057abef5008ef71fa6393a47c04b429383867a Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Mon, 2 Dec 2019 13:58:56 +0100 Subject: [PATCH] refactor table --- scm-ui/ui-components/src/table/Column.tsx | 12 +++- scm-ui/ui-components/src/table/SortIcon.tsx | 19 ++++++ .../ui-components/src/table/Table.stories.tsx | 20 +++++- scm-ui/ui-components/src/table/Table.tsx | 62 +++++++++++-------- scm-ui/ui-components/src/table/TextColumn.tsx | 9 +-- scm-ui/ui-components/src/table/types.ts | 11 ++-- 6 files changed, 92 insertions(+), 41 deletions(-) create mode 100644 scm-ui/ui-components/src/table/SortIcon.tsx diff --git a/scm-ui/ui-components/src/table/Column.tsx b/scm-ui/ui-components/src/table/Column.tsx index e1e7a33156..7f95535c36 100644 --- a/scm-ui/ui-components/src/table/Column.tsx +++ b/scm-ui/ui-components/src/table/Column.tsx @@ -2,11 +2,17 @@ import React, { FC, ReactNode } from "react"; import { ColumnProps } from "./types"; type Props = ColumnProps & { - children: (row: any) => ReactNode; + children: (row: any, columnIndex: number) => ReactNode; }; -const Column: FC = ({ row, children }) => { - return <>{children(row)}; +const Column: FC = ({ row, columnIndex, children }) => { + if (row === undefined) { + throw new Error("missing row, use column only as child of Table"); + } + if (columnIndex === undefined) { + throw new Error("missing row, use column only as child of Table"); + } + return <>{children(row, columnIndex)}; }; export default Column; diff --git a/scm-ui/ui-components/src/table/SortIcon.tsx b/scm-ui/ui-components/src/table/SortIcon.tsx new file mode 100644 index 0000000000..6f823d3d95 --- /dev/null +++ b/scm-ui/ui-components/src/table/SortIcon.tsx @@ -0,0 +1,19 @@ +import React, { FC } from "react"; +import styled from "styled-components"; +import Icon from "../Icon"; + +type Props = { + name: string; + isHidden: boolean; +}; + +const IconWithMarginLeft = styled(Icon)` + visibility: ${(props: Props) => (props.isHidden ? "hidden" : "visible")}; + margin-left: 0.25em; +`; + +const SortIcon: FC = (props: Props) => { + return ; +}; + +export default SortIcon; diff --git a/scm-ui/ui-components/src/table/Table.stories.tsx b/scm-ui/ui-components/src/table/Table.stories.tsx index dafc60f5b6..84b80f001f 100644 --- a/scm-ui/ui-components/src/table/Table.stories.tsx +++ b/scm-ui/ui-components/src/table/Table.stories.tsx @@ -3,12 +3,28 @@ import { storiesOf } from "@storybook/react"; import Table from "./Table"; import Column from "./Column"; import TextColumn from "./TextColumn"; +import { ColumnProps } from "./types"; storiesOf("Table|Table", module) .add("Default", () => ( - +
{(row: any) =>

{row.first}

}
- {(row: any) =>

{row.second}

}
+ { + return (a: any, b: any) => { + if (a.second > b.second) { + return -1; + } else if (a.second < b.second) { + return 1; + } else { + return 0; + } + }; + }} + > + {(row: any) =>

{row.second}

} +
)) .add("TextColumn", () => ( diff --git a/scm-ui/ui-components/src/table/Table.tsx b/scm-ui/ui-components/src/table/Table.tsx index 847fe05d05..a36481005e 100644 --- a/scm-ui/ui-components/src/table/Table.tsx +++ b/scm-ui/ui-components/src/table/Table.tsx @@ -1,38 +1,44 @@ import React, { FC, useState } from "react"; import styled from "styled-components"; -import { SortTypes } from "./types"; -import Icon from "../Icon"; +import { Comparator } from "./types"; +import SortIcon from "./SortIcon"; const StyledTable = styled.table.attrs(() => ({ className: "table content is-hoverable" }))``; -const IconWithMarginLeft = styled(Icon)` - margin-left: 0.25em; -`; - -type SortableTableProps = { +type Props = { data: any[]; + sortable?: boolean; }; // @ts-ignore -const Table: FC = ({ data, children }) => { +const Table: FC = ({ data, sortable, children }) => { const [tableData, setTableData] = useState(data); const [ascending, setAscending] = useState(false); - const [lastSortBy, setlastSortBy] = useState(0); + const [lastSortBy, setlastSortBy] = useState(); + const [hoveredIndex, setHoveredIndex] = useState(); - // @ts-ignore - const sortFunctions = React.Children.map(children, child => - // @ts-ignore - child.props.createComparator ? child.props.createComparator(child.props) : undefined - ); + const isSortable = (child: any) => { + return sortable && child.props.createComparator; + }; + + const sortFunctions: Comparator | undefined[] = []; + React.Children.forEach(children, (child, index) => { + if (isSortable(child)) { + // @ts-ignore + sortFunctions.push(child.props.createComparator(child.props, index)); + } else { + sortFunctions.push(undefined); + } + }); const mapDataToColumns = (row: any) => { return ( - {React.Children.map(children, child => { + {React.Children.map(children, (child, columnIndex) => { // @ts-ignore - return {React.cloneElement(child, { ...child.props, row })}; + return {React.cloneElement(child, { ...child.props, columnIndex, row })}; })} ); @@ -51,6 +57,7 @@ const Table: FC = ({ data, children }) => { setAscending(true); sortOrder = true; } + // @ts-ignore const sortFunction = sortOrder ? sortFunctions[index] : sortDescending(sortFunctions[index]); sortableData.sort(sortFunction); setTableData(sortableData); @@ -58,20 +65,21 @@ const Table: FC = ({ data, children }) => { setlastSortBy(index); }; + // @ts-ignore return ( tableData.length > 0 && ( {React.Children.map(children, (child, index) => ( - // @ts-ignore tableSort(index) : undefined} + className={isSortable(child) && "has-cursor-pointer"} + onClick={isSortable(child) ? () => tableSort(index) : undefined} + onMouseEnter={() => setHoveredIndex(index)} + onMouseLeave={() => setHoveredIndex(undefined)} > {child.props.header} - - {child.props.createComparator && renderSortIcon(child.props.sortType, ascending)} + {isSortable(child) && renderSortIcon(child, ascending, index === lastSortBy || index === hoveredIndex)} ))} @@ -82,11 +90,15 @@ const Table: FC = ({ data, children }) => { ); }; -const renderSortIcon = (contentType: string, ascending: boolean) => { - if (contentType === SortTypes.Text) { - return ; +Table.defaultProps = { + sortable: true +}; + +const renderSortIcon = (child: any, ascending: boolean, showIcon: boolean) => { + if (child.props.ascendingIcon && child.props.descendingIcon) { + return ; } else { - return ; + return ; } }; diff --git a/scm-ui/ui-components/src/table/TextColumn.tsx b/scm-ui/ui-components/src/table/TextColumn.tsx index 0ccc69be81..c3184741da 100644 --- a/scm-ui/ui-components/src/table/TextColumn.tsx +++ b/scm-ui/ui-components/src/table/TextColumn.tsx @@ -1,5 +1,5 @@ -import React, {FC} from "react"; -import {ColumnProps, SortTypes} from "./types"; +import React, { FC } from "react"; +import { ColumnProps } from "./types"; type Props = ColumnProps & { dataKey: string; @@ -10,7 +10,7 @@ const TextColumn: FC = ({ row, dataKey }) => { }; TextColumn.defaultProps = { - createComparator: (props: Props) => { + createComparator: (props: Props, columnIndex) => { return (a: any, b: any) => { if (a[props.dataKey] < b[props.dataKey]) { return -1; @@ -21,7 +21,8 @@ TextColumn.defaultProps = { } }; }, - sortType: SortTypes.Text + ascendingIcon: "sort-alpha-down-alt", + descendingIcon: "sort-alpha-down" }; export default TextColumn; diff --git a/scm-ui/ui-components/src/table/types.ts b/scm-ui/ui-components/src/table/types.ts index 683566da6d..9af3e872cb 100644 --- a/scm-ui/ui-components/src/table/types.ts +++ b/scm-ui/ui-components/src/table/types.ts @@ -5,11 +5,8 @@ export type Comparator = (a: any, b: any) => number; export type ColumnProps = { header: ReactNode; row?: any; - createComparator?: (props: any) => Comparator; - sortType: SortTypes; + columnIndex?: number; + createComparator?: (props: any, columnIndex: number) => Comparator; + ascendingIcon?: string; + descendingIcon?: string; }; - -export enum SortTypes { - Text = "text", - Other = "other" -}