From c5050fbede1311641916c41cc9d452d4bf9055ac Mon Sep 17 00:00:00 2001 From: Sven van de Scheur Date: Thu, 29 Aug 2024 09:45:58 +0200 Subject: [PATCH] :recycle: - refactor: refactor DataGrid components --- src/components/data/datagrid/datagrid.scss | 55 +-- src/components/data/datagrid/datagrid.tsx | 417 +++++++++++---------- src/components/toolbar/toolbar.tsx | 10 +- 3 files changed, 242 insertions(+), 240 deletions(-) diff --git a/src/components/data/datagrid/datagrid.scss b/src/components/data/datagrid/datagrid.scss index 4afd5fa9..af790f20 100644 --- a/src/components/data/datagrid/datagrid.scss +++ b/src/components/data/datagrid/datagrid.scss @@ -5,40 +5,24 @@ border-radius: var(--border-radius-l); width: 100%; - &__table { - border-spacing: 0; - width: 100%; - } - - &__caption { + &__header { padding-block: var(--spacing-v); padding-inline: var(--spacing-h); - text-align: start; - - &:after { - clear: both; - content: ""; - display: block; - } - } - - &__caption .mykn-toolbar { - float: inline-start; - min-height: calc(var(--spacing-h) + var(--typography-line-height-body-xs)); } - &__caption .mykn-toolbar:not(:first-child):last-child { - float: inline-end; + &__table { + border-spacing: 0; + width: 100%; } - &__head { + &__table &__thead { background-color: var(--typography-color-background); position: sticky; top: 0; z-index: 1; } - &__head &__row--filter { + &__thead &__row--filter { background-color: var(--typography-color-background-alt); .mykn-form-control { @@ -51,7 +35,7 @@ } } - &__head &__cell { + &__thead &__cell { text-align: start; } @@ -75,7 +59,7 @@ } } - &__body &__row:last-child &__cell { + &__tbody &__row:last-child &__cell { border-block-end: none; } @@ -123,6 +107,7 @@ padding-block: 0; } + // FIXME? base &__cell--editable:not(#{&}__cell--type-boolean):not(#{&}__cell--link) { padding-inline: 0; } @@ -131,12 +116,12 @@ width: calc(100% - 1em - 3 * var(--spacing-h)); } - &__foot { + &__footer { position: sticky; bottom: 0; } - &__foot &__cell { + &__footer &__cell { border-block-start: 1px solid var(--typography-color-border); border-block-end: none; padding: 0; @@ -154,7 +139,7 @@ table-layout: fixed; } - &__caption { + &__toolbar { background-color: var(--typography-color-background); border-radius: var(--border-radius-l); display: block; @@ -164,15 +149,15 @@ } } - &__head { + &__thead { position: static; } - &__head &__row--header { + &__thead &__row--header { display: none; } - &__body { + &__tbody { display: block; } @@ -228,15 +213,15 @@ width: 100%; } - &__foot { + &__footer { display: flex; } - &__foot &__row { + &__footer &__row { width: 100%; } - &__foot &__cell { + &__footer &__cell { &:before { display: none; } @@ -252,11 +237,11 @@ } // FIXME: Improve this... - .mykn-toolbar--sticky-top + & &__head { + .mykn-toolbar--sticky-top + & &__thead { top: calc(var(--typography-line-height-h1) + 2 * var(--spacing-v)); } - .mykn-toolbar--sticky-top:has(.mykn-form) + & &__head { + .mykn-toolbar--sticky-top:has(.mykn-form) + & &__thead { top: calc(42px + 2 * var(--spacing-v)); } } diff --git a/src/components/data/datagrid/datagrid.tsx b/src/components/data/datagrid/datagrid.tsx index 52c5a16d..f6232766 100644 --- a/src/components/data/datagrid/datagrid.tsx +++ b/src/components/data/datagrid/datagrid.tsx @@ -35,7 +35,7 @@ import { Button, ButtonProps } from "../../button"; import { Checkbox, Form, FormControl } from "../../form"; import { Outline } from "../../icon"; import { Modal } from "../../modal"; -import { Toolbar } from "../../toolbar"; +import { Toolbar, ToolbarItem } from "../../toolbar"; import { AProps, Body, H2, H3, P, PProps } from "../../typography"; import { Paginator, PaginatorProps } from "../paginator"; import { Value } from "../value"; @@ -534,7 +534,6 @@ export const DataGrid: React.FC = (props) => { "Either `pageSize` or `paginatorProps.pageSize` should be set when `showPaginator` is `true`.", ); } - return ( = (props) => { }} >
+ {title && } + {(selectable || fieldsSelectable) && } + = (props) => { role="grid" aria-labelledby={titleId} > - {/* Caption */} - {(title || selectable || fieldsSelectable) && } - - {/* Heading */} - - - - {/* Paginator */} - {showPaginator && } + +
+ + {showPaginator && } {filterable &&
}
); }; -export const DataGridCaption: React.FC = () => { +/** + * DataGrid header, shows title as either string or JSX. + */ +export const DataGridHeader: React.FC = () => { + const { title, titleId } = useContext(DataGridContext); + + return ( +
+ {typeof title === "string" ? ( +

{title}

+ ) : ( + title + )} +
+ ); +}; + +/** + * DataGrid toolbar, shows selection actions and/or allows the user to select fields (columns). + */ +export const DataGridToolbar: React.FC = () => { + const intl = useIntl(); const [selectFieldsModalState, setSelectFieldsModalState] = useState(false); const [selectFieldsActiveState, setSelectFieldsActiveState] = useState< Record >({}); - const intl = useIntl(); const { + allowSelectAll, + allowSelectAllPages, fields, fieldsSelectable, labelSaveFieldSelection, labelSelectFields, selectable, - allowSelectAll, - allowSelectAllPages, - selectionActions, selectedRows, - title, - titleId, + selectionActions, onFieldsChange, } = useContext(DataGridContext); @@ -644,102 +659,105 @@ export const DataGridCaption: React.FC = () => { ? formatMessage(labelSaveFieldSelection, context) : intl.formatMessage(TRANSLATIONS.LABEL_SAVE_FIELD_SELECTION, context); + const toolbarItems: ToolbarItem[] = [ + selectable && allowSelectAll ? ( + + ) : null, + + selectable && allowSelectAllPages ? ( + + ) : null, + + ...(selectionActions || []).map((buttonProps, index) => ( + + + ) : null, + ]; + return ( - - - {title && - (typeof title === "string" ? ( -

{title}

- ) : ( - title - ))} - {selectable && allowSelectAll && ( - - )} - {selectable && allowSelectAllPages && ( - - )} - {selectionActions?.map((buttonProps, index) => ( - - {ucFirst(_labelSelectFields)}} - onClose={() => setSelectFieldsModalState(false)} - > - - ({ - label: field2Title(f.name, { lowerCase: false }), - value: f.name, - selected: Boolean(selectFieldsActiveState[f.name]), - })), - type: "checkbox", - onChange: (e: React.ChangeEvent) => { - const name = e.target.value; - setSelectFieldsActiveState({ - ...selectFieldsActiveState, - [name]: !selectFieldsActiveState[name], - }); - }, - }, - ]} - labelSubmit={ucFirst(_labelSaveFieldSelection)} - onSubmit={(e) => { - const form = e.target as HTMLFormElement; - const data = serializeForm(form); - const selectedFields = (data.fields || []) as string[]; - const newTypedFieldsState = fields.map((f) => ({ - ...f, - active: selectedFields.includes(f.name), - })); - onFieldsChange?.(newTypedFieldsState); - setSelectFieldsModalState(false); - }} - /> - - -
- )} - + + + ); }; /** - * DataGrid heading + * DataGrid table head, encapsulates a set of table rows, indicating that they + * comprise the head of a table with information about the table's columns. */ -export const DataGridHeading: React.FC = () => { +export const DataGridTHead: React.FC = () => { const intl = useIntl(); const onFilterTimeoutRef = useRef(); const [filterState, setFilterState] = useState(); @@ -768,7 +786,8 @@ export const DataGridHeading: React.FC = () => { }, [filterState]); return ( - + + {/* Captions */} {selectable && ( @@ -782,6 +801,8 @@ export const DataGridHeading: React.FC = () => { ))} + + {/* Filters */} {filterable && ( { ); }; -/** - * DataGrid body - */ -export const DataGridBody: React.FC = () => { - const { - dataGridId, - page, - renderableFields, - renderableRows, - selectable, - selectedRows, - equalityChecker = (item1: AttributeData, item2: AttributeData) => - item1 === item2, - sortDirection, - sortField, - } = useContext(DataGridContext); - - return ( - - {renderableRows.map((rowData, index) => ( - - equalityChecker(element, rowData), - ), - })} - > - {selectable && ( - - - - )} - {renderableFields.map((field) => ( - - ))} - - ))} - - ); -}; - -/** - * DataGrid footer - */ -export const DataGridFooter: React.FC = () => { - const { - count, - loading, - onPageChange, - onPageSizeChange, - page, - pageSize, - pageSizeOptions, - renderableFields, - selectable, - paginatorProps, - } = useContext(DataGridContext); - - return ( - - - - - - - - - - ); -}; - export type DataGridHeadingCellProps = React.PropsWithChildren<{ field: TypedField; }>; - /** * DataGrid (heading) cell */ + export const DataGridHeadingCell: React.FC = ({ children, field, @@ -999,6 +925,58 @@ export const DataGridHeadingCell: React.FC = ({ ); }; +/** + * DataGrid table body, encapsulates a set of table rows indicating that they + * comprise the body of a table's (main) data. + */ +export const DataGridTBody: React.FC = () => { + const { + dataGridId, + page, + renderableFields, + renderableRows, + selectable, + selectedRows, + equalityChecker = (item1: AttributeData, item2: AttributeData) => + item1 === item2, + sortDirection, + sortField, + } = useContext(DataGridContext); + + return ( + + {renderableRows.map((rowData, index) => ( + + equalityChecker(element, rowData), + ), + })} + > + {selectable && ( + + + + )} + {renderableFields.map((field) => ( + + ))} + + ))} + + ); +}; + export type DataGridContentCellProps = { field: TypedField; rowData: AttributeData; @@ -1286,3 +1264,36 @@ export const DataGridSelectionCheckbox: React.FC< ); }; + +/** + * DataGrid footer, shows paginator. + */ +export const DataGridFooter: React.FC = () => { + const { + count, + loading, + onPageChange, + onPageSizeChange, + page, + pageSize, + pageSizeOptions, + paginatorProps, + } = useContext(DataGridContext); + + return ( +
+ + + +
+ ); +}; diff --git a/src/components/toolbar/toolbar.tsx b/src/components/toolbar/toolbar.tsx index 56233674..4af37039 100644 --- a/src/components/toolbar/toolbar.tsx +++ b/src/components/toolbar/toolbar.tsx @@ -11,7 +11,8 @@ export type ToolbarItem = | ButtonProps | ButtonLinkProps | DropdownProps - | "spacer"; + | "spacer" + | null; export type ToolbarProps = React.PropsWithChildren< React.HTMLAttributes & { @@ -21,6 +22,9 @@ export type ToolbarProps = React.PropsWithChildren< /** The position of `children` compared to `items`. */ childrenPosition?: "before" | "after"; + /** Can be used to extend the className. */ + className?: string; + /** Whether the toolbar shows items horizontally or vertically, mobile devices always use vertical. */ direction?: "horizontal" | "vertical"; @@ -63,6 +67,7 @@ export type ToolbarProps = React.PropsWithChildren< export const Toolbar: React.FC = ({ children, childrenPosition = "after", + className, align = "start", compact = false, direction = "horizontal", @@ -145,12 +150,13 @@ export const Toolbar: React.FC = ({ [`mykn-toolbar--size-${size}`]: size, [`mykn-toolbar--sticky-${sticky}`]: sticky, }, + className, )} role="toolbar" {...props} > {childrenPosition === "before" && children} - {items.map(renderItem)} + {items.filter((v) => v !== null).map(renderItem)} {childrenPosition === "after" && children} );