Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement #28 #35

Merged
merged 6 commits into from
Sep 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
42 changes: 42 additions & 0 deletions package/DataTable.props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,15 @@ import type {
MantineTheme,
Sx,
TableProps,
CollapseProps,
} from '@mantine/core';
import type { CSSProperties, ReactNode } from 'react';

export type ExpandedRowCollapseProps = Pick<
CollapseProps,
'animateOpacity' | 'transitionDuration' | 'transitionTimingFunction'
>;

export type DataTableColumnTextAlignment = 'left' | 'center' | 'right';
export type DataTableVerticalAlignment = 'top' | 'center' | 'bottom';

Expand Down Expand Up @@ -382,6 +388,42 @@ export type DataTableProps<T> = {
*/
items: (record: T) => DataTableContextMenuItemProps[];
};

/**
* Defines a custom component to render beneath the related record's row
*/
expandedRow?: {
/**
* Defines when rows should expand; defaults to `click`
* May pass a custom function that receives the current record;
* if true, that row will be expanded
*/
expandRowOn?: 'click' | 'always' | ((record: T) => boolean);

/**
* Defined if multiple rows are allowed to be expanded at the same time; defaults to `false`
*/
expandMultiple?: boolean;

/**
* Defines the record that will be expanded when the table is first loaded;
* defaults to `undefined`;
* does nothing if `expandRowOn === 'always'`
*/
expandFirst?: unknown;

/**
* Pass additional props to the Mantine Collapse component wrapping the custom component;
* does not accept the `children`, `in`, or `onTransitionEnd` properties
*/
collapseProps?: ExpandedRowCollapseProps;

/**
* Custom component to be rendered;
* accepts the current record
*/
item: (record: T) => ReactNode;
};
} & Pick<TableProps, 'striped' | 'highlightOnHover' | 'horizontalSpacing' | 'verticalSpacing' | 'fontSize'> &
Omit<DefaultProps<'root' | 'header' | 'pagination', CSSProperties>, 'unstyled'> &
DataTableOuterBorderProps &
Expand Down
50 changes: 47 additions & 3 deletions package/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ export default function DataTable<T>({
striped,
onRowClick,
rowContextMenu,
expandedRow,
sx,
className,
classNames,
Expand Down Expand Up @@ -187,7 +188,7 @@ export default function DataTable<T>({
};

/**
* React hooks linting rule would reccomend to also include the `useDobouncedState` setters
* React hooks linting rule would recommend to also include the `useDobouncedState` setters
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch ;-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Y'know, I just noticed that it says useDobouncedState instead of useDebouncedState. Didn't catch that one ;-)

* (setScrolledToBottom, setScrolledToLeft, setScrolledToRight, setScrolledToTop) in the effect
* dependecies, but it looks like there's actually no need to.
*/
Expand All @@ -206,6 +207,34 @@ export default function DataTable<T>({
onPageChange!(page);
};

const { expandRowOn = 'click', expandMultiple = false, expandFirst, collapseProps } = expandedRow ?? {};

const initialExpandedRows = expandRowOn === 'always' ? [] : [expandFirst];
const [expandedRowIds, setExpandedRowIds] = useState<unknown[]>(initialExpandedRows);
const changeExpandedRowIds = expandMultiple
? (recordID: unknown, isExpanded: boolean) => {
isExpanded
? setExpandedRowIds(expandedRowIds.filter((currentID) => currentID !== recordID))
: setExpandedRowIds([...(expandedRowIds as unknown[]), recordID]);
}
: (recordID: unknown, isExpanded: boolean) => {
isExpanded ? setExpandedRowIds([]) : setExpandedRowIds([recordID]);
};

useEffect(() => {
if (typeof expandRowOn === 'function' && records) {
const rowIds: unknown[] = [];
for (const record of records) {
const result = expandRowOn(record);
if (result) {
rowIds.push(getValueAtPath(record, idAccessor));
if (!expandMultiple) break;
}
}
setExpandedRowIds(rowIds);
}
}, [records, idAccessor, expandRowOn, expandMultiple]);

const recordsLength = records?.length;
const recordIds = records?.map((record) => getValueAtPath(record, idAccessor));
const selectedRecordIds = selectedRecords?.map((record) => getValueAtPath(record, idAccessor));
Expand Down Expand Up @@ -290,6 +319,7 @@ export default function DataTable<T>({
records.map((record, recordIndex) => {
const recordId = getValueAtPath(record, idAccessor);
const selected = selectedRecordIds?.includes(recordId) || false;
const isExpanded = expandRowOn === 'always' ? true : expandedRowIds.includes(recordId);

let showContextMenuOnClick = false;
let showContextMenuOnRightClick = false;
Expand Down Expand Up @@ -340,8 +370,19 @@ export default function DataTable<T>({
}
onClick={
showContextMenuOnClick
? (e) => {
setRowContextMenuInfo({ top: e.clientY, left: e.clientX, record });
? expandRowOn === 'click'
? (e) => {
setRowContextMenuInfo({ top: e.clientY, left: e.clientX, record });
changeExpandedRowIds(recordId, isExpanded);
onRowClick?.(record);
}
: (e) => {
setRowContextMenuInfo({ top: e.clientY, left: e.clientX, record });
onRowClick?.(record);
}
: expandRowOn === 'click'
? () => {
changeExpandedRowIds(recordId, isExpanded);
onRowClick?.(record);
}
: onRowClick
Expand All @@ -362,6 +403,9 @@ export default function DataTable<T>({
rowContextMenuInfo ? getValueAtPath(rowContextMenuInfo.record, idAccessor) === recordId : false
}
leftShadowVisible={selectionVisibleAndNotScrolledToLeft}
expandedRow={expandedRow?.item}
isExpanded={isExpanded}
collapseProps={collapseProps ?? {}}
/>
);
})
Expand Down
89 changes: 82 additions & 7 deletions package/DataTableRow.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Checkbox, createStyles } from '@mantine/core';
import { Checkbox, createStyles, Collapse } from '@mantine/core';
import { ChangeEventHandler, MouseEventHandler } from 'react';
import { DataTableColumn } from './DataTable.props';
import { DataTableColumn, ExpandedRowCollapseProps } from './DataTable.props';
import DataTableRowCell from './DataTableRowCell';

const useStyles = createStyles((theme) => {
Expand Down Expand Up @@ -65,10 +65,16 @@ const useStyles = createStyles((theme) => {
},
},
},
expandedRow: {
padding: '0 !important',
},
expandedRow__collapsed: {
border: '0 !important',
},
};
});

type DataTableRowProps<T> = {
interface DataTableRowBaseProps<T> {
record: T;
columns: DataTableColumn<T>[];
selectionVisible: boolean;
Expand All @@ -78,9 +84,77 @@ type DataTableRowProps<T> = {
onContextMenu: MouseEventHandler<HTMLTableRowElement> | undefined;
contextMenuVisible: boolean;
leftShadowVisible: boolean;
};
}

interface DataTableRowChildProps<T> extends DataTableRowBaseProps<T> {
styles: ReturnType<typeof useStyles>;
}

interface DataTableRowParentProps<T> extends DataTableRowBaseProps<T> {
expandedRow: ((record: T) => React.ReactNode) | undefined;
isExpanded: boolean;
collapseProps: ExpandedRowCollapseProps;
}

export default function DataTableRowParent<T>({
record,
columns,
selectionVisible,
selectionChecked,
onSelectionChange,
onClick,
onContextMenu,
contextMenuVisible,
leftShadowVisible,
expandedRow,
isExpanded,
collapseProps,
}: DataTableRowParentProps<T>) {
const styles = useStyles();
const { cx, classes } = styles;

const dataTableRow = DataTableRow({
record,
columns,
selectionVisible,
selectionChecked,
onSelectionChange,
onClick,
onContextMenu,
contextMenuVisible,
leftShadowVisible,
styles,
});

if (expandedRow) {
const { animateOpacity, transitionDuration, transitionTimingFunction } = collapseProps;
const columnCount = selectionVisible ? columns.length + 1 : columns.length;
return (
<>
{dataTableRow}
<tr>
<td
colSpan={columnCount}
className={cx(classes.expandedRow, { [classes.expandedRow__collapsed]: !isExpanded })}
>
<Collapse
in={isExpanded}
animateOpacity={animateOpacity}
transitionDuration={transitionDuration}
transitionTimingFunction={transitionTimingFunction}
>
{expandedRow(record)}
</Collapse>
</td>
</tr>
</>
);
} else {
return dataTableRow;
}
}

export default function DataTableRow<T>({
function DataTableRow<T>({
record,
columns,
selectionVisible,
Expand All @@ -90,8 +164,9 @@ export default function DataTableRow<T>({
onContextMenu,
contextMenuVisible,
leftShadowVisible,
}: DataTableRowProps<T>) {
const { cx, classes } = useStyles();
styles,
}: DataTableRowChildProps<T>) {
const { cx, classes } = styles;

return (
<tr
Expand Down