diff --git a/.eslintrc.js b/.eslintrc.js index 102807e..cd043e7 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -12,17 +12,37 @@ module.exports = { ], rules: { "no-console": "off", - "no-unused-vars": ["error", { argsIgnorePattern: "^_" }], + "@typescript-eslint/camelcase": "off", "@typescript-eslint/explicit-function-return-type": ["off"], + "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }], + "react/prop-types": "off", + "react/display-name": "off", + "no-unused-expressions": "off", + "no-useless-concat": "off", + "no-useless-constructor": "off", + "no-unexpected-multiline": "off", + "default-case": "off", "@typescript-eslint/no-use-before-define": "off", - "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }], + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-empty-interface": "off", + "@typescript-eslint/ban-ts-ignore": "off", "@typescript-eslint/no-empty-function": "off", - "react/prop-types": "off", + "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/ban-types": "off", + "@typescript-eslint/ban-ts-comment": "off", + "@typescript-eslint/no-var-requires": "off", + "@typescript-eslint/indent": "off", + "@typescript-eslint/member-delimiter-style": "off", + "@typescript-eslint/type-annotation-spacing": "off", + "@typescript-eslint/no-use-before-define": "off", + "@typescript-eslint/explicit-function-return-type": "off", + "no-extra-semi": "off", + "no-mixed-spaces-and-tabs": "off", + "react-hooks/rules-of-hooks": "error", + "react-hooks/exhaustive-deps": "warn", "no-prototype-builtins": "off", }, - env: { - jest: true, - }, + plugins: ["@typescript-eslint", "react-hooks"], settings: { react: { pragma: "React", diff --git a/package.json b/package.json index 0ac837d..67ec1dd 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "type": "git", "url": "git+https://github.com/eyeseetea/d2-ui-components.git" }, - "version": "2.1.0", + "version": "2.2.0", "main": "index.js", "types": "index.d.ts", "peerDependencies": { @@ -57,7 +57,7 @@ "react": "^16.8.15", "react-dom": "^16.8.15", "ts-jest": "^24.1.0", - "typescript": "^3.6.3" + "typescript": "4.0.2" }, "dependencies": { "@date-io/core": "^1.3.6", diff --git a/src/confirmation-dialog/ConfirmationDialog.tsx b/src/confirmation-dialog/ConfirmationDialog.tsx index 7548555..1e64e94 100644 --- a/src/confirmation-dialog/ConfirmationDialog.tsx +++ b/src/confirmation-dialog/ConfirmationDialog.tsx @@ -11,9 +11,9 @@ import _ from "lodash"; import React, { ReactNode } from "react"; import i18n from "../utils/i18n"; -export interface ConfirmationDialogProps extends Partial { +export interface ConfirmationDialogProps extends Partial> { isOpen?: boolean; - title?: string; + title?: string | ReactNode; description?: string | ReactNode; onSave?: (event: React.MouseEvent) => void; onCancel?: (event: React.MouseEvent) => void; diff --git a/src/data-table/ColumnSelectorDialog.tsx b/src/data-table/ColumnSelectorDialog.tsx index 6ee95b6..f6c749e 100644 --- a/src/data-table/ColumnSelectorDialog.tsx +++ b/src/data-table/ColumnSelectorDialog.tsx @@ -1,4 +1,3 @@ -import i18n from "@dhis2/d2-i18n"; import { Checkbox, DialogContent } from "@material-ui/core"; import Table from "@material-ui/core/Table"; import TableBody from "@material-ui/core/TableBody"; @@ -7,6 +6,7 @@ import TableHead from "@material-ui/core/TableHead"; import TableRow from "@material-ui/core/TableRow"; import React from "react"; import { ConfirmationDialog, ReferenceObject, TableColumn } from ".."; +import i18n from "../utils/i18n"; interface ColumnSelectorDialogProps { columns: TableColumn[]; diff --git a/src/data-table/DataTable.tsx b/src/data-table/DataTable.tsx index 7e1b3e9..91f105f 100644 --- a/src/data-table/DataTable.tsx +++ b/src/data-table/DataTable.tsx @@ -127,7 +127,7 @@ export function DataTable(props: DataTa const [stateSelection, updateSelection] = useState(initialState.selection || []); const [statePagination, updatePagination] = useState(initialState.pagination); - const [visibleColumns, updateVisibleColumns] = useState([]); + const [visibleColumns, updateVisibleColumns] = useState>([]); const [stateSorting, updateSorting] = useState( initialState.sorting || { field: columns[0].name, @@ -164,10 +164,11 @@ export function DataTable(props: DataTa const enableMultipleAction = _.isUndefined(forceSelectionColumn) ? _.some(availableActions, "multiple") : forceSelectionColumn; + const totalRows = pagination.total ? pagination.total : rows.length; const selectionMessages = hideSelectionMessages ? [] - : getSelectionMessages(rowObjects, selection, pagination, ids, childrenKeys); + : getSelectionMessages(rowObjects, selection, totalRows, ids, childrenKeys); // Contextual menu const [contextMenuTarget, setContextMenuTarget] = useState(null); diff --git a/src/data-table/DataTableBody.tsx b/src/data-table/DataTableBody.tsx index 9a394aa..3ba5940 100644 --- a/src/data-table/DataTableBody.tsx +++ b/src/data-table/DataTableBody.tsx @@ -1,4 +1,3 @@ -import i18n from "@dhis2/d2-i18n"; import Checkbox from "@material-ui/core/Checkbox"; import IconButton from "@material-ui/core/IconButton"; import { makeStyles } from "@material-ui/core/styles"; @@ -23,6 +22,7 @@ import { } from "./types"; import { formatRowValue } from "./utils/formatting"; import { isEventCtrlClick, parseActions, updateSelection } from "./utils/selection"; +import i18n from "../utils/i18n"; const defaultMouseActionsMapping: MouseActionsMapping = { left: { type: "primary" }, @@ -78,13 +78,13 @@ export function DataTableBody(props: DataTableBodyPro rowConfig, visibleColumns, sorting, - availableActions, + availableActions = [], selected, onChange = _.noop, openContextualMenu = _.noop, enableMultipleAction, loading, - childrenKeys, + childrenKeys = [], mouseActionsMapping = defaultMouseActionsMapping, } = props; const { field, order } = sorting; @@ -153,8 +153,9 @@ export function DataTableBody(props: DataTableBodyPro }); }; - const { style, cellStyle, disabled, selectable = !disabled } = rowConfig(row); - const selectedItem: Partial = _.find(selected, { id: row.id }); + const config = rowConfig ? rowConfig(row) : {}; + const { style, cellStyle, disabled, selectable = !disabled } = config; + const selectedItem = _.find(selected, { id: row.id }); const { checked = !!selectedItem, indeterminate = false, icon = } = selectedItem || {}; const rowClassName = _.compact([ diff --git a/src/data-table/DataTableHeader.tsx b/src/data-table/DataTableHeader.tsx index 3792735..8910652 100644 --- a/src/data-table/DataTableHeader.tsx +++ b/src/data-table/DataTableHeader.tsx @@ -1,4 +1,3 @@ -import i18n from "@dhis2/d2-i18n"; import Checkbox from "@material-ui/core/Checkbox"; import IconButton from "@material-ui/core/IconButton"; import { makeStyles } from "@material-ui/core/styles"; @@ -7,10 +6,11 @@ import TableHead from "@material-ui/core/TableHead"; import TableRow from "@material-ui/core/TableRow"; import TableSortLabel from "@material-ui/core/TableSortLabel"; import ExpandMoreIcon from "@material-ui/icons/ExpandMore"; -import ViewColumnIcon from "@material-ui/icons/ViewColumn"; import SettingsIcon from "@material-ui/icons/Settings"; +import ViewColumnIcon from "@material-ui/icons/ViewColumn"; import _ from "lodash"; import React, { MouseEvent, useState } from "react"; +import i18n from "../utils/i18n"; import ColumnSelectorDialog from "./ColumnSelectorDialog"; import { ContextualMenu } from "./ContextualMenu"; import { DataTableNotifications } from "./DataTableNotifications"; @@ -67,13 +67,13 @@ export function DataTableHeader(props: DataTableHeade globalActions, ids, visibleColumns, - onVisibleColumnsChange = _.noop, + onVisibleColumnsChange, sorting, - onSortingChange = _.noop, + onSortingChange, allSelected = false, tableNotifications = [], - handleSelectionChange = _.noop, - onSelectAllClick = _.noop, + handleSelectionChange, + onSelectAllClick, enableMultipleAction, hideColumnVisibilityOptions = false, hideSelectAll = false, @@ -86,7 +86,7 @@ export function DataTableHeader(props: DataTableHeade const createSortHandler = (property: keyof T) => (_event: React.MouseEvent) => { const isDesc = field === property && order === "desc"; - onSortingChange({ field: property, order: isDesc ? "asc" : "desc" }); + if (onSortingChange) onSortingChange({ field: property, order: isDesc ? "asc" : "desc" }); }; const tableActions = _.compact([ @@ -115,7 +115,7 @@ export function DataTableHeader(props: DataTableHeade setOpenColumnSettings(false)} /> )} @@ -160,7 +160,7 @@ export function DataTableHeader(props: DataTableHeade diff --git a/src/data-table/DataTablePagination.tsx b/src/data-table/DataTablePagination.tsx index d80c829..34d1638 100644 --- a/src/data-table/DataTablePagination.tsx +++ b/src/data-table/DataTablePagination.tsx @@ -1,10 +1,11 @@ import Pagination from "@material-ui/core/TablePagination"; import _ from "lodash"; import React from "react"; +import { PartialBy } from "../utils/types"; import { PaginationOptions, TablePagination } from "./types"; export interface DataTablePaginationProps { - pagination: TablePagination; + pagination: PartialBy; paginationOptions: Partial; defaultTotal: number; onChange?(newPagination: TablePagination): void; diff --git a/src/data-table/DetailsBox.tsx b/src/data-table/DetailsBox.tsx index e2fb9cc..00b201e 100644 --- a/src/data-table/DetailsBox.tsx +++ b/src/data-table/DetailsBox.tsx @@ -1,10 +1,9 @@ -import React from "react"; -import { makeStyles } from "@material-ui/core/styles"; import Paper from "@material-ui/core/Paper"; +import { makeStyles } from "@material-ui/core/styles"; import CloseIcon from "@material-ui/icons/Close"; -import i18n from "@dhis2/d2-i18n"; - -import { ReferenceObject, ObjectsTableDetailField } from "./types"; +import React from "react"; +import i18n from "../utils/i18n"; +import { ObjectsTableDetailField, ReferenceObject } from "./types"; import { formatRowValue } from "./utils/formatting"; const useStyles = makeStyles({ diff --git a/src/data-table/ObjectsTable.tsx b/src/data-table/ObjectsTable.tsx index 0d74464..882cf63 100644 --- a/src/data-table/ObjectsTable.tsx +++ b/src/data-table/ObjectsTable.tsx @@ -51,7 +51,7 @@ export function ObjectsTable(props: Obj filterComponents: parentFilterComponents, sideComponents: parentSideComponents, resetKey = "", - childrenKeys, + childrenKeys = [], ...rest } = props; const classes = useStyles(); diff --git a/src/data-table/utils/selection.tsx b/src/data-table/utils/selection.tsx index 177ad12..19ee836 100644 --- a/src/data-table/utils/selection.tsx +++ b/src/data-table/utils/selection.tsx @@ -1,13 +1,7 @@ -import i18n from "@dhis2/d2-i18n"; import _ from "lodash"; import { MouseEvent } from "react"; -import { - ReferenceObject, - TableAction, - TableNotification, - TablePagination, - TableSelection, -} from "../types"; +import i18n from "../../utils/i18n"; +import { ReferenceObject, TableAction, TableNotification, TableSelection } from "../types"; export function updateSelection(selected: TableSelection[], row: T) { const selectedIndex = _.findIndex(selected, { id: row.id }); @@ -59,7 +53,7 @@ export function parseActions( export function getSelectionMessages( rows: T[], tableSelection: TableSelection[], - pagination: TablePagination, + total: number, ids: string[], childrenKeys: string[] ): TableNotification[] { @@ -77,11 +71,11 @@ export function getSelectionMessages( const selection = _.differenceBy(tableSelection, childrenIds, "id"); - const allSelected = selection.length === pagination.total; + const allSelected = selection.length === total; const selectionInOtherPages = _.differenceBy(selection, rows, "id"); const allSelectedInPage = _.differenceBy(rows, selection, "id").length === 0; - const multiplePagesAvailable = pagination.total > rows.length; - const selectAllImplemented = ids.length === pagination.total; + const multiplePagesAvailable = total > rows.length; + const selectAllImplemented = ids.length === total; return _.compact([ allSelected @@ -108,9 +102,7 @@ export function getSelectionMessages( message: i18n.t("All {{total}} items on this page are selected.", { total: rows.length, }), - link: i18n.t("Select all {{total}} items in all pages", { - total: pagination.total, - }), + link: i18n.t("Select all {{total}} items in all pages", { total }), newSelection: ids.map(id => ({ id })), } : null, diff --git a/src/data-table/utils/sorting.tsx b/src/data-table/utils/sorting.tsx index 0329a1b..198617e 100644 --- a/src/data-table/utils/sorting.tsx +++ b/src/data-table/utils/sorting.tsx @@ -6,7 +6,7 @@ import { ReactNode } from "react"; export function sortObjects( rows: T[], columns: TableColumn[], - tablePagination: TablePagination, + tablePagination: Pick, tableSorting: TableSorting ) { const { field, order } = tableSorting; diff --git a/src/date-picker/DatePicker.tsx b/src/date-picker/DatePicker.tsx index 84a4d52..01cd79b 100644 --- a/src/date-picker/DatePicker.tsx +++ b/src/date-picker/DatePicker.tsx @@ -54,7 +54,7 @@ const getMaterialTheme = (isFilter: boolean, colors: Dictionary) => }); export const DatePicker: React.FC = ({ - isFilter, + isFilter = false, errorStyle, errorText, ...datePickerProps diff --git a/src/loading/LoadingConsumer.tsx b/src/loading/LoadingConsumer.tsx index d4713d9..be71904 100644 --- a/src/loading/LoadingConsumer.tsx +++ b/src/loading/LoadingConsumer.tsx @@ -1,13 +1,11 @@ -import React from "react"; -import PropTypes from "prop-types"; - -import { withStyles } from "@material-ui/core/styles"; -import LoadingContext from "./context"; import CircularProgress from "@material-ui/core/CircularProgress"; -import Typography from "@material-ui/core/Typography"; import Divider from "@material-ui/core/Divider"; +import { makeStyles } from "@material-ui/core/styles"; +import Typography from "@material-ui/core/Typography"; +import React from "react"; +import LoadingContext from "./context"; -const styles = _theme => ({ +const useStyles = makeStyles({ loadingMask: { height: "100%", width: "100%", @@ -33,12 +31,15 @@ const styles = _theme => ({ }, }); -const LoadingConsumer = props => { - const { classes } = props; +const LoadingConsumer = () => { + const classes = useStyles(); return ( - {({ isLoading, message, progress }) => { + {state => { + if (!state) throw new Error("Loading context has not been defined"); + const { isLoading, message, progress = 0 } = state; + const hideMessage = !message || !message.trim(); return (