From e01f3a661fbbb4df1970413c69143df6180acbdb Mon Sep 17 00:00:00 2001 From: Salihu Abdullahi Date: Mon, 20 Jul 2020 17:58:44 +0800 Subject: [PATCH 1/9] feat(table): table theming --- src/Table/Table.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Table/Table.tsx b/src/Table/Table.tsx index 027643102..24aeeb8f8 100644 --- a/src/Table/Table.tsx +++ b/src/Table/Table.tsx @@ -38,7 +38,7 @@ const timesIcon: JSX.Element = ( export type DataItem = T & TableRow; type RowTypes = "row" | "subRow"; export type EditMode = "save" | "cancel" | "edit"; - +export type TableTheme = "light" | "dark"; export interface Column { label: string | React.ReactNode; accessor: string; @@ -436,6 +436,8 @@ interface TableUIProps { useShowActionColumn: boolean; showFilterRow?: boolean; filterProps: FilterProps; + theadTheme: TableTheme; + theme?: TableTheme; } const TableUI: React.FunctionComponent = React.memo( @@ -445,8 +447,8 @@ const TableUI: React.FunctionComponent = React.memo( return (
- - +
+ {props.useRowSelection ? ( - {props.useRowSelection ? ( - - ) : ( - (props.row.subRows.length > 0 || props.row.rowContentDetail) && - props.rowsAreCollapsable && ( - - ) - )} - {props.row.cells.map((cell: Cell, cellIndex: number) => { - return !cell.hidden ? ( - - ) : null; - })} - {props.useShowActionColumn && ( - - )} - - {props.type === "subRow" && ( - - - - )} - - ); -}; - -interface TableUIProps { - actionLinks?: Array; - allRowsAreSelected?: boolean; - className: string; - columns: Array; - footer: React.ReactNode; - loading: boolean; - onActionDropped: (event: React.MouseEvent, row: TableRow, rowIndex?: number) => void; - onAllItemsSelected?: (e: React.ChangeEvent) => void; - onItemSelected?: (e: React.ChangeEvent, row: TableRow, type: RowTypes, rowIndex?: number) => void; - onRowExpanded?: (e: React.MouseEvent, row: TableRow) => void; - onSort?: (accessor: string, sortDirection: sortDirectionTypes) => void; - onSubRowExpanded?: (e: React.MouseEvent, row: TableRow, rowIndex: number) => void; - onChange?: (e: React.ChangeEvent, row: TableRow, rowIndex?: number) => void; - primaryActionButton?: PrimaryActionButton; - rows: Array; - rowsAreCollapsable?: boolean; - sortable: boolean; - useRowCollapse: boolean; - useRowSelection: boolean; - useShowActionColumn: boolean; - showFilterRow?: boolean; - filterProps: FilterProps; - theadTheme: TableTheme; - theme?: TableTheme; -} - -const TableUI: React.FunctionComponent = React.memo( - (props: TableUIProps): React.ReactElement => { - const [checkAllRandomIds] = React.useState(randomId("chk-all")); - const tableRef: React.RefObject = React.createRef(); - - return ( -
-
@@ -609,6 +611,8 @@ export interface TableProps { searchProps?: SearchProps; sortProps?: SortProps; editProps?: EditProps; + theme?: TableTheme; + theadTheme?: TableTheme; } const Table: React.FunctionComponent = React.memo( @@ -1232,6 +1236,8 @@ const Table: React.FunctionComponent = React.memo( showFilterRow={showFilterRow()} filterProps={props.filterProps} onChange={onTextChange} + theme={props.theme} + theadTheme={props.theadTheme} /> ); From 4f8801711b82bb2e8062b4e791e94fc341a284d3 Mon Sep 17 00:00:00 2001 From: Nuru Date: Sun, 26 Jul 2020 19:24:24 +0800 Subject: [PATCH 2/9] refactor(table): remove unwanted styles --- src/Table/Table.tsx | 8 +-- src/Table/table-style.scss | 101 +++++-------------------------------- src/index.json | 10 ++-- 3 files changed, 20 insertions(+), 99 deletions(-) diff --git a/src/Table/Table.tsx b/src/Table/Table.tsx index 24aeeb8f8..d210497dc 100644 --- a/src/Table/Table.tsx +++ b/src/Table/Table.tsx @@ -187,12 +187,8 @@ interface TextboxGroupProps { const TextboxGroup: React.FunctionComponent = (props: TextboxGroupProps) => { return ( -
-
-
- -
-
+
+
); }; diff --git a/src/Table/table-style.scss b/src/Table/table-style.scss index b5d4415cd..657f2cb77 100644 --- a/src/Table/table-style.scss +++ b/src/Table/table-style.scss @@ -8,63 +8,6 @@ $row-height: 40px; $transition-time: 200ms; $filter-gray: #e6e6e6; -@mixin setInputGroupStyles() { - .form-group.input-box-group { - position: relative; - margin-bottom: 0rem; - > .input-group { - position: relative; - > .input-box-group-wrapper { - transition: border $transition-time / 2; - position: relative; - width: 100%; - height: auto; - border: 1px solid $gray-500; - border-radius: 4px; - overflow: hidden; - display: flex; - align-items: center; - &:hover { - border-color: $blue; - background-color: $white; - } - &:focus-within { - border-color: $blue-darker; - box-shadow: none; - background-color: $white; - &:active { - border-color: $blue-darker; - box-shadow: none; - background-color: $white; - } - } - > input.form-control { - display: inline-flex; - z-index: 1; - border: none; - border-radius: 0; - height: auto; - min-height: 10px; - padding: 5px 7px; - &:focus { - box-shadow: none; - } - &::-webkit-inner-spin-button, - &::-webkit-textfield-decoration-container { - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - } - &:nth-child(2):not(:last-child), - &:first-child:not(:last-child) { - padding-right: 0; - } - } - } - } - } -} - table { &.table { thead { @@ -72,38 +15,11 @@ table { > th { &.sortable { cursor: pointer; + &:hover { color: $icon-active-color; } } - &:first-child { - border-bottom: none; - } - - .custom-control { - position: relative; - display: block; - min-height: 1.5rem; - - &.custom-checkbox { - input[type="checkbox"] { - box-sizing: border-box; - padding: 0; - } - - .custom-control-label { - position: relative; - margin-bottom: 0; - vertical-align: top; - } - - .custom-control-input { - position: absolute; - z-index: -1; - opacity: 0; - } - } - } .icon-holder { vertical-align: middle; @@ -112,7 +28,6 @@ table { height: $icon-size; width: $icon-size; display: inline-block; - vertical-align: baseline; > svg { width: 100%; @@ -143,9 +58,13 @@ table { tbody { tr { - td { - @include setInputGroupStyles(); + &:first-child { + td { + border-top: none; + } + } + td { .icon-holder { cursor: pointer; height: $icon-size; @@ -177,6 +96,7 @@ table { button { min-height: $icon-size; + & + .ellipsis-dropdown-holder { margin-left: 20px; } @@ -194,6 +114,7 @@ table { fill: $white; } } + svg { fill: $icon-active-color; height: calc(#{$icon-size} * 1.5); @@ -225,12 +146,14 @@ table { margin-bottom: 0.125rem; } } + > a { color: inherit; padding: 8px 16px; text-decoration: none; display: block; cursor: pointer; + &:hover { background-color: $icon-active-color; color: $white; @@ -246,6 +169,7 @@ table { align-items: center; } } + &.sub-row { td { > div { @@ -255,6 +179,7 @@ table { } } } + &.sub-description-row { td { .description { diff --git a/src/index.json b/src/index.json index 5a68b98c3..e96d321f7 100644 --- a/src/index.json +++ b/src/index.json @@ -23,8 +23,8 @@ "RadioGroup": "./src/RadioGroup/index.ts", "Rating": "./src/Rating/index.ts", "Slider": "./src/Slider/index.ts", - "Stepper": "./src/Stepper/index.ts", "StepTracker": "./src/StepTracker/index.ts", + "Stepper": "./src/Stepper/index.ts", "Table": "./src/Table/index.ts", "Tabs": "./src/Tabs/index.ts", "TextArea": "./src/TextArea/index.ts", @@ -131,14 +131,14 @@ "from": "./src/Slider/index.ts", "to": "./Slider/index.js" }, - { - "from": "./src/Stepper/index.ts", - "to": "./Stepper/index.js" - }, { "from": "./src/StepTracker/index.ts", "to": "./StepTracker/index.js" }, + { + "from": "./src/Stepper/index.ts", + "to": "./Stepper/index.js" + }, { "from": "./src/Table/index.ts", "to": "./Table/index.js" From 41dc34f0d81b58928bb5a87113e115fd7aa3a1f2 Mon Sep 17 00:00:00 2001 From: Nuru Date: Mon, 27 Jul 2020 10:48:22 +0800 Subject: [PATCH 3/9] refactor(table): refactor table, move components to individual files" --- src/Table/Table.test.tsx | 2 +- src/Table/Table.tsx | 527 +------------------------- src/Table/readme.md | 3 - src/Table/sections/ActionColumnUI.tsx | 98 +++++ src/Table/sections/RowUI.tsx | 146 +++++++ src/Table/sections/TableUI.tsx | 174 +++++++++ src/Table/sections/TextboxGroup.tsx | 18 + src/Table/sections/helperFunctions.ts | 97 +++++ 8 files changed, 540 insertions(+), 525 deletions(-) create mode 100644 src/Table/sections/ActionColumnUI.tsx create mode 100644 src/Table/sections/RowUI.tsx create mode 100644 src/Table/sections/TableUI.tsx create mode 100644 src/Table/sections/TextboxGroup.tsx create mode 100644 src/Table/sections/helperFunctions.ts diff --git a/src/Table/Table.test.tsx b/src/Table/Table.test.tsx index d187fd862..bd17337e3 100644 --- a/src/Table/Table.test.tsx +++ b/src/Table/Table.test.tsx @@ -276,7 +276,7 @@ describe("Component: Table", () => { mode: null, }; - const selector: string = "tbody tr.parent-row td .form-group.input-box-group"; + const selector: string = "tbody tr.parent-row td .form-group"; const updatedSelectedRows: Array = smallData?.slice(0, 2).map((row: TableRow) => ({ ...row, selected: true })); diff --git a/src/Table/Table.tsx b/src/Table/Table.tsx index d210497dc..3a664dcbb 100644 --- a/src/Table/Table.tsx +++ b/src/Table/Table.tsx @@ -1,77 +1,42 @@ -import * as React from "react"; -import { randomId } from "@sebgroup/frontend-tools/dist/randomId"; +import React from "react"; import "./table-style.scss"; -const angleDown: JSX.Element = ( - - - -); -const angleRightIcon: JSX.Element = ( - - - -); -const ellipsis: JSX.Element = ( - - - -); -const defaultSort: JSX.Element = ( - - - - -); -const timesIcon: JSX.Element = ( - - - -); +import { TableUI } from "./sections/TableUI"; +import { sortArray, searchTextInArray, filterArray } from "./sections/helperFunctions"; export type DataItem = T & TableRow; -type RowTypes = "row" | "subRow"; +export type RowTypes = "row" | "subRow"; export type EditMode = "save" | "cancel" | "edit"; export type TableTheme = "light" | "dark"; + export interface Column { label: string | React.ReactNode; accessor: string; canSort?: boolean; isHidden?: boolean; } - export interface ActionLinkItem { label: string; onClick: (event: React.MouseEvent, selectedRow: TableRow) => void; } - export interface PrimaryActionButton { label: string; buttonTheme?: "link" | "outline-primary" | "secondary" | "ghost-dark" | "ghost-light" | "danger" | "primary"; buttonSize?: "lg" | "md" | "sm"; onClick: (event: React.MouseEvent, selectedRow: TableRow) => void; } - export interface TableHeader extends Column { isSorted?: boolean; isSortedDesc?: boolean; filters?: Array; } - -interface Cell { +export interface Cell { id: string | number; accessor: string; value: string | number | boolean; canEdit?: boolean; hidden?: boolean; } - export interface TableRow { rowIndex: number; cells: Array; @@ -82,489 +47,10 @@ export interface TableRow { rowContentDetail?: React.ReactNode; isEditMode?: boolean; } - export const enum sortDirectionTypes { Ascending = "ASC", Descending = "DESC", } - -/** - * sum the total of columns or cols in a row - * @param colsLength the length of the columns - * @param useSelection add a column for selection checkboxes - * @param useShowActionColumn add another column for action columns - * @param useGroupBy add another columns for groupby - */ -function sumCols(colsLength: number, useSelection?: boolean, useShowActionColumn?: boolean, useGroupBy?: boolean): number { - let sum = colsLength; - - if (useSelection) { - sum = sum + 1; - } - - if (useGroupBy) { - sum = sum + 1; - } - if (useShowActionColumn) { - sum = sum + 1; - } - - return sum; -} - -/** - * sort array of tabke rows - * @param items table rows array - * @param columnName the target column name - * @param sortDirection the sort direction - */ -function sortArray(items: Array = [], columnName: string, sortDirection: sortDirectionTypes): Array { - const languages: Readonly> = window.navigator?.languages || ["sw", "en"]; - - const sortedItems: Array = [...items].sort((firstItem: TableRow, secondItem: TableRow) => { - let result: number = 0; - if (sortDirection === sortDirectionTypes.Ascending) { - if (isNaN(secondItem[columnName]) && isNaN(firstItem[columnName])) { - result = String(firstItem[columnName]).localeCompare(String(secondItem[columnName]), languages as Array, { sensitivity: "base", ignorePunctuation: true }); - } else { - result = firstItem[columnName] - secondItem[columnName]; - } - } else { - if (isNaN(secondItem[columnName]) && isNaN(firstItem[columnName])) { - result = String(secondItem[columnName]).localeCompare(String(firstItem[columnName]), languages as Array, { sensitivity: "base", ignorePunctuation: true }); - } else { - result = secondItem[columnName] - firstItem[columnName]; - } - } - return result; - }); - return sortedItems; -} - -function filterArray(items: Array, columns: Array): Array { - return [...items].filter((row: TableRow) => { - return columns.some((column: TableHeader) => { - return column.filters?.some((filterValue: string) => { - const currentColumn: Cell = row?.cells.find((cell: Cell) => cell?.accessor === column?.accessor); - return currentColumn.value === filterValue; - }); - }); - }); -} - -/** - * search text in array of table row - * @param items the array of table rows - * @param keyword The keyword to search in the array - * @param searchFields the target field to search - */ -function searchTextInArray(items: Array, keyword: string, searchFields: Array): Array { - return [...items].filter((row: TableRow) => { - const searchText: string = String(keyword); - - return searchFields.some((searchColumn: string) => { - let result: boolean = false; - const searchField: string = searchColumn; - const regEx: RegExp = new RegExp(searchText, "gi"); - if (row[searchField] === null || row[searchField] === undefined) { - result = false; - } else if (typeof row[searchField] === "string") { - result = row[searchField].search(regEx) > -1; - } else if (typeof row[searchField] === "number") { - result = String(row[searchField]).search(regEx) !== -1; - } - return result; - }); - }); -} - -interface TextboxGroupProps { - type: string; - onChange: (event: React.ChangeEvent) => void; - name: string; - value: string | number; -} - -const TextboxGroup: React.FunctionComponent = (props: TextboxGroupProps) => { - return ( -
- -
- ); -}; - -interface ActionColumnProps { - actionLinks?: Array; - onActionDropped?: (event: React.MouseEvent) => void; - primaryActionButton?: PrimaryActionButton; - selectedRow: TableRow; - tableRef: React.RefObject; -} - -const ActionColumn: React.FunctionComponent = (props: ActionColumnProps) => { - const [btnPrimaryRandomIds] = React.useState(randomId("btn")); - const [dropup, setDropup] = React.useState(false); - const actionRef: React.RefObject = React.createRef(); - const [actionColumnClass, setActionColumnClass] = React.useState(""); - - React.useEffect(() => { - let className: string = "dropdown-content"; - if (props.selectedRow?.actionsDropdownDropped) { - className += " active"; - } - - if (dropup) { - className += " dropup"; - } - - setActionColumnClass(className); - }, [props.selectedRow, dropup]); - - return ( -
- {props.primaryActionButton && ( - - )} - {props.actionLinks && props.actionLinks?.length ? ( -
) => { - const tableSize: DOMRect = props.tableRef?.current?.getBoundingClientRect(); - const actionColumnHeight: number = actionRef.current?.scrollHeight; - const actionColumSize: DOMRect = actionRef.current?.getBoundingClientRect(); - - if (tableSize?.height > actionColumnHeight) { - const lengthOffset: number = tableSize?.bottom - actionColumSize?.bottom; - if (lengthOffset < actionColumnHeight) { - setDropup(true); - } else { - setDropup(false); - } - } else { - setDropup(false); - } - e.preventDefault(); - props.onActionDropped(e); - }} - > -
- {ellipsis} -
- {props.selectedRow.actionsDropdownDropped ? ( -
- {props.actionLinks.map((link: ActionLinkItem, index: number) => ( - ) => { - e.preventDefault(); - link.onClick(e, props.selectedRow); - }} - > - {link.label} - - ))} -
- ) : null} -
- ) : null} -
- ); -}; - -interface RowUIProps { - actionLinks?: Array; - columns: Array; - onActionDropped: (event: React.MouseEvent, row: TableRow, rowIndex?: number) => void; - onItemSelected?: (e: React.ChangeEvent, row: TableRow, type: RowTypes, rowIndex?: number) => void; - onRowExpanded?: (e: React.MouseEvent, row: TableRow) => void; - onSubRowExpanded?: (e: React.MouseEvent, row: TableRow, rowIndex: number) => void; - onChange?: (e: React.ChangeEvent, row: TableRow) => void; - parentRowIndex?: number; - parentRowIsExpanded?: boolean; - primaryActionButton?: PrimaryActionButton; - row: TableRow; - rowsAreCollapsable: boolean; - tableRef: React.RefObject; - type: RowTypes; - useRowCollapse: boolean; - useRowSelection: boolean; - useShowActionColumn: boolean; -} - -const RowUI: React.FunctionComponent = (props: RowUIProps) => { - const [checkRowRandomIds] = React.useState(randomId("chk-")); - - return ( - -
-
- ) => { - if (props.type === "row") { - props.onItemSelected && props.onItemSelected(e, props.row, props.type); - } else { - props.onItemSelected(e, props.row, "subRow", props.parentRowIndex); - } - }} - name={`chk` + (props.type === "subRow" ? `${props.parentRowIndex}-${props.row.rowIndex}` : props.row.rowIndex)} - /> -
- {(props.row.subRows.length > 0 || props.row.rowContentDetail) && props.rowsAreCollapsable && ( -
) => { - if (props.type === "row") { - props.onRowExpanded(e, props.row); - } else { - props.onSubRowExpanded(e, props.row, props.parentRowIndex); - } - }} - > - {props.row.expanded ? angleDown : angleRightIcon} -
- )} -
-
) => { - if (props.type === "row") { - props.onRowExpanded && props.onRowExpanded(e, props.row); - } else { - props.onSubRowExpanded(e, props.row, props.parentRowIndex); - } - }} - > - {props.row.expanded ? angleDown : angleRightIcon} -
-
- {props.row?.isEditMode && cell.canEdit ? ( - ) => { - props.onChange(e, props.row); - }} - name={cell.id?.toString()} - type={"text"} - value={String(cell.value)} - /> - ) : ( - cell.value - )} - - ) => { - props.onActionDropped && props.onActionDropped(event, props.row, props.type === "subRow" ? props.parentRowIndex : null); - }} - /> -
-
{props.row.rowContentDetail}
-
- - - {props.useRowSelection ? ( - - ) : ( - props.rowsAreCollapsable && - ) : null; - })} - {props.useShowActionColumn && - - - {props.rows?.map((row: TableRow, i: number) => { - return ( - - , updatedRow: TableRow) => { - props.onChange(e, updatedRow); - }} - /> - {row.subRows?.map((subRow: TableRow) => { - return ( - - , updatedSubRow: TableRow) => { - props.onChange(e, updatedSubRow, row.rowIndex); - }} - /> - - ); - })} - - - - - - ); - })} - {props.rows?.length === 0 && ( - - - - )} - - - {props.footer && ( - - - - )} - -
-
- -
-
- )} - {props.columns?.map((header: TableHeader, index: number) => { - return !header.isHidden ? ( - ) => { - if (props.sortable && header.canSort) { - props.onSort(header?.accessor, header.isSortedDesc ? sortDirectionTypes.Ascending : sortDirectionTypes.Descending); - } else { - e.preventDefault(); - } - }} - > - {header.label} - {props.sortable && header.canSort && ( - - {defaultSort} - - )} - } -
-
{row.rowContentDetail}
-
Record empty
{props.footer}
-
- ); - } -); - export interface SearchProps { onSearch?: (rows: Array) => void; searchInColumns?: Array; @@ -585,7 +71,6 @@ export interface FilterProps { filterItems: Array; onAfterFilter: (rows: Array) => void; } - export interface EditProps { mode: EditMode; onAfterEdit: (rows: Array) => void; diff --git a/src/Table/readme.md b/src/Table/readme.md index 25424e270..a744ea75f 100644 --- a/src/Table/readme.md +++ b/src/Table/readme.md @@ -34,9 +34,6 @@ These are the current available properties: | columns | `Array` | Array of the table columns | | data | `Array` | Array of the objects that make of the rows | | searchProps? | `SearcProps` | The search properties, use this if you wanht to enable searching. See below | -| offset? | `number` | number of rows in a page, this is used in pagination | -| currentpage? | `number` | The current page, also use in pagination | -| usePagination? | `boolean` | This property along with the above two constitutes the pagination | | primaryActionButton? | `PrimaryActionButton` | The primary button under action column | | actionLinks? | `Array` | The array list of the clickable links to be made available under action | | sortProps? | `SortProps` | The sorting props, see the props below | diff --git a/src/Table/sections/ActionColumnUI.tsx b/src/Table/sections/ActionColumnUI.tsx new file mode 100644 index 000000000..92690b7c3 --- /dev/null +++ b/src/Table/sections/ActionColumnUI.tsx @@ -0,0 +1,98 @@ +import React from "react"; +import { ActionLinkItem, PrimaryActionButton, TableRow } from "../Table"; +import { randomId } from "@sebgroup/frontend-tools/dist/randomId"; + +const ellipsis: JSX.Element = ( + + + +); + +interface ActionColumnProps { + actionLinks?: Array; + onActionDropped?: (event: React.MouseEvent) => void; + primaryActionButton?: PrimaryActionButton; + selectedRow: TableRow; + tableRef: React.RefObject; +} + +export const ActionColumnUI: React.FunctionComponent = (props: ActionColumnProps) => { + const [btnPrimaryRandomIds] = React.useState(randomId("btn")); + const [dropup, setDropup] = React.useState(false); + const actionRef: React.RefObject = React.createRef(); + const [actionColumnClass, setActionColumnClass] = React.useState(""); + + React.useEffect(() => { + let className: string = "dropdown-content"; + if (props.selectedRow?.actionsDropdownDropped) { + className += " active"; + } + + if (dropup) { + className += " dropup"; + } + + setActionColumnClass(className); + }, [props.selectedRow, dropup]); + + return ( +
+ {props.primaryActionButton && ( + + )} + {props.actionLinks && props.actionLinks?.length ? ( +
) => { + const tableSize: DOMRect = props.tableRef?.current?.getBoundingClientRect(); + const actionColumnHeight: number = actionRef.current?.scrollHeight; + const actionColumSize: DOMRect = actionRef.current?.getBoundingClientRect(); + + if (tableSize?.height > actionColumnHeight) { + const lengthOffset: number = tableSize?.bottom - actionColumSize?.bottom; + if (lengthOffset < actionColumnHeight) { + setDropup(true); + } else { + setDropup(false); + } + } else { + setDropup(false); + } + e.preventDefault(); + props.onActionDropped(e); + }} + > +
+ {ellipsis} +
+ {props.selectedRow.actionsDropdownDropped ? ( +
+ {props.actionLinks.map((link: ActionLinkItem, index: number) => ( + ) => { + e.preventDefault(); + link.onClick(e, props.selectedRow); + }} + > + {link.label} + + ))} +
+ ) : null} +
+ ) : null} +
+ ); +}; diff --git a/src/Table/sections/RowUI.tsx b/src/Table/sections/RowUI.tsx new file mode 100644 index 000000000..72f0651a4 --- /dev/null +++ b/src/Table/sections/RowUI.tsx @@ -0,0 +1,146 @@ +import React from "react"; +import { TableRow, RowTypes, ActionLinkItem, TableHeader, PrimaryActionButton, Cell } from "../Table"; +import { randomId } from "@sebgroup/frontend-tools/dist/randomId"; + +// components +import TextboxGroup from "./TextboxGroup"; +import { ActionColumnUI } from "./ActionColumnUI"; +import { sumCols } from "./helperFunctions"; + +const angleDown: JSX.Element = ( + + + +); +const angleRightIcon: JSX.Element = ( + + + +); + +interface RowUIProps { + actionLinks?: Array; + columns: Array; + onActionDropped: (event: React.MouseEvent, row: TableRow, rowIndex?: number) => void; + onItemSelected?: (e: React.ChangeEvent, row: TableRow, type: RowTypes, rowIndex?: number) => void; + onRowExpanded?: (e: React.MouseEvent, row: TableRow) => void; + onSubRowExpanded?: (e: React.MouseEvent, row: TableRow, rowIndex: number) => void; + onChange?: (e: React.ChangeEvent, row: TableRow) => void; + parentRowIndex?: number; + parentRowIsExpanded?: boolean; + primaryActionButton?: PrimaryActionButton; + row: TableRow; + rowsAreCollapsable: boolean; + tableRef: React.RefObject; + type: RowTypes; + useRowCollapse: boolean; + useRowSelection: boolean; + useShowActionColumn: boolean; +} + +export const RowUI: React.FunctionComponent = (props: RowUIProps) => { + const [checkRowRandomIds] = React.useState(randomId("chk-")); + + return ( + + + {props.useRowSelection ? ( + +
+ ) => { + if (props.type === "row") { + props.onItemSelected && props.onItemSelected(e, props.row, props.type); + } else { + props.onItemSelected(e, props.row, "subRow", props.parentRowIndex); + } + }} + name={`chk` + (props.type === "subRow" ? `${props.parentRowIndex}-${props.row.rowIndex}` : props.row.rowIndex)} + /> +
+ {(props.row.subRows.length > 0 || props.row.rowContentDetail) && props.rowsAreCollapsable && ( +
) => { + if (props.type === "row") { + props.onRowExpanded(e, props.row); + } else { + props.onSubRowExpanded(e, props.row, props.parentRowIndex); + } + }} + > + {props.row.expanded ? angleDown : angleRightIcon} +
+ )} + + ) : ( + (props.row.subRows.length > 0 || props.row.rowContentDetail) && + props.rowsAreCollapsable && ( + +
) => { + if (props.type === "row") { + props.onRowExpanded && props.onRowExpanded(e, props.row); + } else { + props.onSubRowExpanded(e, props.row, props.parentRowIndex); + } + }} + > + {props.row.expanded ? angleDown : angleRightIcon} +
+ + ) + )} + {props.row.cells.map((cell: Cell, cellIndex: number) => { + return !cell.hidden ? ( + + {props.row?.isEditMode && cell.canEdit ? ( + ) => { + props.onChange(e, props.row); + }} + name={cell.id?.toString()} + type={"text"} + value={String(cell.value)} + /> + ) : ( + cell.value + )} + + ) : null; + })} + {props.useShowActionColumn && ( + + ) => { + props.onActionDropped && props.onActionDropped(event, props.row, props.type === "subRow" ? props.parentRowIndex : null); + }} + /> + + )} + + {props.type === "subRow" && ( + + +
{props.row.rowContentDetail}
+ + + )} +
+ ); +}; diff --git a/src/Table/sections/TableUI.tsx b/src/Table/sections/TableUI.tsx new file mode 100644 index 000000000..78d4449c0 --- /dev/null +++ b/src/Table/sections/TableUI.tsx @@ -0,0 +1,174 @@ +import React from "react"; +import { ActionLinkItem, TableHeader, sortDirectionTypes, TableRow, RowTypes, PrimaryActionButton, FilterProps, TableTheme } from "../Table"; +import { randomId } from "@sebgroup/frontend-tools/dist/randomId"; +import { RowUI } from "./RowUI"; +import { sumCols } from "./helperFunctions"; + +const defaultSort: JSX.Element = ( + + + + +); + +interface TableUIProps { + actionLinks?: Array; + allRowsAreSelected?: boolean; + className: string; + columns: Array; + footer: React.ReactNode; + loading: boolean; + onActionDropped: (event: React.MouseEvent, row: TableRow, rowIndex?: number) => void; + onAllItemsSelected?: (e: React.ChangeEvent) => void; + onItemSelected?: (e: React.ChangeEvent, row: TableRow, type: RowTypes, rowIndex?: number) => void; + onRowExpanded?: (e: React.MouseEvent, row: TableRow) => void; + onSort?: (accessor: string, sortDirection: sortDirectionTypes) => void; + onSubRowExpanded?: (e: React.MouseEvent, row: TableRow, rowIndex: number) => void; + onChange?: (e: React.ChangeEvent, row: TableRow, rowIndex?: number) => void; + primaryActionButton?: PrimaryActionButton; + rows: Array; + rowsAreCollapsable?: boolean; + sortable: boolean; + useRowCollapse: boolean; + useRowSelection: boolean; + useShowActionColumn: boolean; + showFilterRow?: boolean; + filterProps: FilterProps; + theadTheme: TableTheme; + theme?: TableTheme; +} + +export const TableUI: React.FunctionComponent = React.memo( + (props: TableUIProps): React.ReactElement => { + const [checkAllRandomIds] = React.useState(randomId("chk-all")); + const tableRef: React.RefObject = React.createRef(); + + return ( +
+ + + + {props.useRowSelection ? ( + + ) : ( + props.rowsAreCollapsable && + ) : null; + })} + {props.useShowActionColumn && + + + {props.rows?.map((row: TableRow, i: number) => { + return ( + + , updatedRow: TableRow) => { + props.onChange(e, updatedRow); + }} + /> + {row.subRows?.map((subRow: TableRow) => { + return ( + + , updatedSubRow: TableRow) => { + props.onChange(e, updatedSubRow, row.rowIndex); + }} + /> + + ); + })} + + + + + + ); + })} + {props.rows?.length === 0 && ( + + + + )} + + + {props.footer && ( + + + + )} + +
+
+ +
+
+ )} + {props.columns?.map((header: TableHeader, index: number) => { + return !header.isHidden ? ( + ) => { + if (props.sortable && header.canSort) { + props.onSort(header?.accessor, header.isSortedDesc ? sortDirectionTypes.Ascending : sortDirectionTypes.Descending); + } else { + e.preventDefault(); + } + }} + > + {header.label} + {props.sortable && header.canSort && ( + + {defaultSort} + + )} + } +
+
{row.rowContentDetail}
+
Record empty
{props.footer}
+
+ ); + } +); diff --git a/src/Table/sections/TextboxGroup.tsx b/src/Table/sections/TextboxGroup.tsx new file mode 100644 index 000000000..7a3c3deab --- /dev/null +++ b/src/Table/sections/TextboxGroup.tsx @@ -0,0 +1,18 @@ +import React from "react"; + +interface TextboxGroupProps { + type: string; + onChange: (event: React.ChangeEvent) => void; + name: string; + value: string | number; +} + +const TextboxGroup: React.FunctionComponent = (props: TextboxGroupProps) => { + return ( +
+ +
+ ); +}; + +export default TextboxGroup; diff --git a/src/Table/sections/helperFunctions.ts b/src/Table/sections/helperFunctions.ts new file mode 100644 index 000000000..a94249160 --- /dev/null +++ b/src/Table/sections/helperFunctions.ts @@ -0,0 +1,97 @@ +import { TableRow, sortDirectionTypes, TableHeader, Cell } from "../Table"; + +/** + * sum the total of columns or cols in a row + * @param colsLength the length of the columns + * @param useSelection add a column for selection checkboxes + * @param useShowActionColumn add another column for action columns + * @param useGroupBy add another columns for groupby + */ +export function sumCols(colsLength: number, useSelection?: boolean, useShowActionColumn?: boolean, useGroupBy?: boolean): number { + let sum = colsLength; + + if (useSelection) { + sum = sum + 1; + } + + if (useGroupBy) { + sum = sum + 1; + } + if (useShowActionColumn) { + sum = sum + 1; + } + + return sum; +} + +/** + * sort array of tabke rows + * @param items table rows array + * @param columnName the target column name + * @param sortDirection the sort direction + * @return Array of tableRow + */ +export function sortArray(items: Array = [], columnName: string, sortDirection: sortDirectionTypes): Array { + const languages: Readonly> = window.navigator?.languages || ["sw", "en"]; + + const sortedItems: Array = [...items].sort((firstItem: TableRow, secondItem: TableRow) => { + let result: number = 0; + if (sortDirection === sortDirectionTypes.Ascending) { + if (isNaN(secondItem[columnName]) && isNaN(firstItem[columnName])) { + result = String(firstItem[columnName]).localeCompare(String(secondItem[columnName]), languages as Array, { sensitivity: "base", ignorePunctuation: true }); + } else { + result = firstItem[columnName] - secondItem[columnName]; + } + } else { + if (isNaN(secondItem[columnName]) && isNaN(firstItem[columnName])) { + result = String(secondItem[columnName]).localeCompare(String(firstItem[columnName]), languages as Array, { sensitivity: "base", ignorePunctuation: true }); + } else { + result = secondItem[columnName] - firstItem[columnName]; + } + } + return result; + }); + return sortedItems; +} + +/** + * + * @param items the table rows array + * @param columns the rable columns array + */ +export function filterArray(items: Array, columns: Array): Array { + return [...items].filter((row: TableRow) => { + return columns.some((column: TableHeader) => { + return column.filters?.some((filterValue: string) => { + const currentColumn: Cell = row?.cells.find((cell: Cell) => cell?.accessor === column?.accessor); + return currentColumn.value === filterValue; + }); + }); + }); +} + +/** + * search text in array of table row + * @param items the array of table rows + * @param keyword The keyword to search in the array + * @param searchFields the target field to search + */ +export function searchTextInArray(items: Array, keyword: string, searchFields: Array): Array { + return [...items].filter((row: TableRow) => { + const searchText: string = String(keyword); + + return searchFields.some((searchColumn: string) => { + let result: boolean = false; + const searchField: string = searchColumn; + const regEx: RegExp = new RegExp(searchText, "gi"); + if (row[searchField] === null || row[searchField] === undefined) { + result = false; + } else if (typeof row[searchField] === "string") { + result = row[searchField].search(regEx) > -1; + } else if (typeof row[searchField] === "number") { + result = String(row[searchField]).search(regEx) !== -1; + } + return result; + }); + }); +} From dfe11378a6884b4e545ba6717360c4615c7ac771 Mon Sep 17 00:00:00 2001 From: Nuru Date: Mon, 27 Jul 2020 12:41:20 +0800 Subject: [PATCH 4/9] refactor(table): fix export terms --- src/Table/sections/RowUI.tsx | 2 +- src/Table/sections/TextboxGroup.tsx | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Table/sections/RowUI.tsx b/src/Table/sections/RowUI.tsx index 72f0651a4..7ecfe5fd5 100644 --- a/src/Table/sections/RowUI.tsx +++ b/src/Table/sections/RowUI.tsx @@ -3,7 +3,7 @@ import { TableRow, RowTypes, ActionLinkItem, TableHeader, PrimaryActionButton, C import { randomId } from "@sebgroup/frontend-tools/dist/randomId"; // components -import TextboxGroup from "./TextboxGroup"; +import { TextboxGroup } from "./TextboxGroup"; import { ActionColumnUI } from "./ActionColumnUI"; import { sumCols } from "./helperFunctions"; diff --git a/src/Table/sections/TextboxGroup.tsx b/src/Table/sections/TextboxGroup.tsx index 7a3c3deab..c6db26707 100644 --- a/src/Table/sections/TextboxGroup.tsx +++ b/src/Table/sections/TextboxGroup.tsx @@ -7,12 +7,10 @@ interface TextboxGroupProps { value: string | number; } -const TextboxGroup: React.FunctionComponent = (props: TextboxGroupProps) => { +export const TextboxGroup: React.FunctionComponent = (props: TextboxGroupProps) => { return (
); }; - -export default TextboxGroup; From 9f3f433dd5307ab9bb71ec65cf43b74efbe2f03d Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 29 Jul 2020 21:35:26 +0000 Subject: [PATCH 5/9] chore(deps): [security] bump elliptic from 6.5.2 to 6.5.3 Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.2 to 6.5.3. **This update includes a security fix.** - [Release notes](https://github.com/indutny/elliptic/releases) - [Commits](https://github.com/indutny/elliptic/compare/v6.5.2...v6.5.3) Signed-off-by: dependabot-preview[bot] --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index c56447b65..654d80223 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9776,9 +9776,9 @@ "dev": true }, "elliptic": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", - "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==", + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", + "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", "dev": true, "requires": { "bn.js": "^4.4.0", From d71161c4bdf1ac5a813dc31a3883305d9d110150 Mon Sep 17 00:00:00 2001 From: Yousif Al-Raheem Date: Tue, 4 Aug 2020 20:02:09 +0800 Subject: [PATCH 6/9] fix(dropdown): fixes a potential issue causing the dropdown toggle button to cause a form submission --- src/Dropdown/Dropdown.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Dropdown/Dropdown.tsx b/src/Dropdown/Dropdown.tsx index 06eb1021d..d72498c13 100644 --- a/src/Dropdown/Dropdown.tsx +++ b/src/Dropdown/Dropdown.tsx @@ -436,6 +436,7 @@ const Dropdown: React.FunctionComponent = (props: DropdownProps): return ( @@ -110,7 +110,7 @@ export const Pagination: React.FunctionComponent = React.memo( )} {props.value !== 1 && (
  • props.onChange(props.value - 1)}> - @@ -119,7 +119,7 @@ export const Pagination: React.FunctionComponent = React.memo( {list.map((num: number) => { return (
  • props.onChange(num)} value={num}> - @@ -129,7 +129,7 @@ export const Pagination: React.FunctionComponent = React.memo( {props.value !== pagingSize && (
  • props.onChange(props.value + 1)}> - @@ -138,7 +138,7 @@ export const Pagination: React.FunctionComponent = React.memo( {props.value !== pagingSize && props.useFirstAndLast && (
  • props.onChange(pagingSize)}> -