@drivenets/design-system 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/dist/index.cjs +1652 -455
- package/dist/index.d.cts +1226 -989
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +1226 -989
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1643 -456
- package/dist/index.js.map +1 -1
- package/dist/index.min.css +1 -1
- package/package.json +31 -31
- package/src/components/ds-catalog-layout/catalog-layout-empty-illustration.tsx +57 -0
- package/src/components/ds-catalog-layout/ds-catalog-layout.context.ts +19 -0
- package/src/components/ds-catalog-layout/ds-catalog-layout.module.scss +167 -0
- package/src/components/ds-catalog-layout/ds-catalog-layout.tsx +130 -0
- package/src/components/ds-catalog-layout/ds-catalog-layout.types.ts +62 -0
- package/src/components/ds-catalog-layout/index.ts +9 -0
- package/src/components/ds-date-input/ds-date-input.utils.ts +2 -2
- package/src/components/ds-drawer/ds-drawer.tsx +2 -0
- package/src/components/ds-form-control/ds-form-control.tsx +14 -11
- package/src/components/ds-form-control/ds-form-control.types.ts +4 -0
- package/src/components/ds-grid/ds-grid.tsx +1 -2
- package/src/components/ds-modal/ds-modal.tsx +3 -0
- package/src/components/ds-smart-tabs/ds-smart-tabs.module.scss +1 -0
- package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.module.scss +21 -2
- package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.tsx +14 -11
- package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.types.ts +1 -6
- package/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.module.scss +2 -2
- package/src/components/ds-table/components/ds-table-cell/ds-table-cell.module.scss +2 -0
- package/src/components/ds-table/components/ds-table-cell/ds-table-cell.tsx +42 -11
- package/src/components/ds-table/components/ds-table-header/ds-table-header.module.scss +24 -4
- package/src/components/ds-table/components/ds-table-header/ds-table-header.tsx +22 -1
- package/src/components/ds-table/components/ds-table-row/ds-table-row.module.scss +22 -0
- package/src/components/ds-table/components/ds-table-row/ds-table-row.tsx +1 -0
- package/src/components/ds-table/components/ds-table-row-virtualized/ds-table-row-virtualized.module.scss +19 -0
- package/src/components/ds-table/components/ds-table-row-virtualized/ds-table-row-virtualized.tsx +2 -1
- package/src/components/ds-table/components/ds-table-row-virtualized/ds-table-row-virtualized.types.ts +1 -1
- package/src/components/ds-table/components/edit/cell-editors/cell-editors.module.scss +3 -0
- package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-checkbox.tsx +27 -0
- package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-date.tsx +50 -0
- package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-number.tsx +51 -0
- package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-select.tsx +36 -0
- package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-text.tsx +50 -0
- package/src/components/ds-table/components/edit/cell-editors/index.ts +14 -0
- package/src/components/ds-table/components/edit/cell-editors/table-edit-form-control.tsx +31 -0
- package/src/components/ds-table/components/edit/ds-table-editable-cell/ds-table-editable-cell.module.scss +28 -0
- package/src/components/ds-table/components/edit/ds-table-editable-cell/ds-table-editable-cell.tsx +39 -0
- package/src/components/ds-table/components/edit/ds-table-editable-cell/ds-table-editable-cell.types.ts +7 -0
- package/src/components/ds-table/components/edit/ds-table-editable-cell/index.ts +2 -0
- package/src/components/ds-table/components/edit/ds-table-editing-cell/ds-table-editing-cell.module.scss +48 -0
- package/src/components/ds-table/components/edit/ds-table-editing-cell/ds-table-editing-cell.tsx +76 -0
- package/src/components/ds-table/components/edit/ds-table-editing-cell/ds-table-editing-cell.types.ts +5 -0
- package/src/components/ds-table/components/edit/ds-table-editing-cell/index.ts +2 -0
- package/src/components/ds-table/context/ds-table-context.ts +79 -1
- package/src/components/ds-table/ds-table-column.types.ts +12 -0
- package/src/components/ds-table/ds-table.module.scss +100 -10
- package/src/components/ds-table/ds-table.tsx +18 -19
- package/src/components/ds-table/ds-table.types.ts +52 -0
- package/src/components/ds-table/filters/adapters/create-checkbox-filter-adapter.tsx +24 -40
- package/src/components/ds-table/filters/adapters/create-dual-range-filter-adapter.tsx +10 -31
- package/src/components/ds-table/filters/adapters/create-filter-adapter.ts +6 -98
- package/src/components/ds-table/filters/components/checkbox-filter/checkbox-filter.module.scss +36 -0
- package/src/components/ds-table/filters/components/checkbox-filter/checkbox-filter.tsx +92 -13
- package/src/components/ds-table/filters/components/checkbox-filter/index.ts +1 -1
- package/src/components/ds-table/filters/components/column-filter-popover/column-filter-popover.module.scss +42 -0
- package/src/components/ds-table/filters/components/column-filter-popover/column-filter-popover.tsx +89 -0
- package/src/components/ds-table/filters/components/column-filter-popover/column-filter-popover.types.ts +25 -0
- package/src/components/ds-table/filters/components/column-filter-popover/index.ts +5 -0
- package/src/components/ds-table/filters/hooks/use-table-filters.ts +113 -78
- package/src/components/ds-table/filters/index.ts +2 -44
- package/src/components/ds-table/filters/types/column-meta.augmentation.ts +19 -0
- package/src/components/ds-table/filters/types/filter-adapter.types.ts +48 -81
- package/src/components/ds-table/hooks/use-cell-editor.ts +81 -0
- package/src/components/ds-table/index.ts +29 -0
- package/src/components/ds-table/utils/column-size.ts +9 -8
- package/src/components/ds-tag/ds-tag.tsx +2 -2
- package/src/components/ds-workspace-layout/ds-workspace-layout.context.ts +19 -0
- package/src/components/ds-workspace-layout/ds-workspace-layout.module.scss +166 -0
- package/src/components/ds-workspace-layout/ds-workspace-layout.tsx +135 -0
- package/src/components/ds-workspace-layout/ds-workspace-layout.types.ts +52 -0
- package/src/components/ds-workspace-layout/index.ts +11 -0
- package/src/index.ts +2 -1
- package/src/styles/_scrollbars.scss +37 -0
- package/src/utils/restore-document-body-modal-styles.ts +44 -0
- package/src/utils/use-ark-dialog-body-lock-cleanup.ts +36 -0
- package/src/components/ds-workspace/ds-workspace.module.scss +0 -55
- package/src/components/ds-workspace/ds-workspace.tsx +0 -37
- package/src/components/ds-workspace/ds-workspace.types.ts +0 -17
- package/src/components/ds-workspace/index.ts +0 -8
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { CellContext, RowData } from '@tanstack/react-table';
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
|
+
import { DsFormControl } from '../../../../ds-form-control';
|
|
4
|
+
import styles from './cell-editors.module.scss';
|
|
5
|
+
|
|
6
|
+
export interface TableEditFormControlProps<TData extends RowData> {
|
|
7
|
+
cellContext: CellContext<TData, unknown>;
|
|
8
|
+
error: string | null;
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const TableEditFormControl = <TData extends RowData>({
|
|
13
|
+
cellContext,
|
|
14
|
+
error,
|
|
15
|
+
children,
|
|
16
|
+
}: TableEditFormControlProps<TData>) => {
|
|
17
|
+
const label = String(cellContext.column.columnDef.header ?? cellContext.column.id);
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<DsFormControl
|
|
21
|
+
hideLabel
|
|
22
|
+
label={label}
|
|
23
|
+
className={styles.fullWidth}
|
|
24
|
+
status={error ? 'error' : undefined}
|
|
25
|
+
message={error ?? undefined}
|
|
26
|
+
messageIcon="cancel"
|
|
27
|
+
>
|
|
28
|
+
{children}
|
|
29
|
+
</DsFormControl>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
.root {
|
|
2
|
+
display: flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
gap: var(--3xs);
|
|
5
|
+
width: 100%;
|
|
6
|
+
min-width: 0;
|
|
7
|
+
padding: 0 var(--standard);
|
|
8
|
+
height: 100%;
|
|
9
|
+
|
|
10
|
+
&:focus-within {
|
|
11
|
+
outline: none;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.editButton {
|
|
15
|
+
flex-shrink: 0;
|
|
16
|
+
opacity: 0;
|
|
17
|
+
pointer-events: none;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
&:hover,
|
|
21
|
+
&:focus-within,
|
|
22
|
+
&[data-active] {
|
|
23
|
+
.editButton {
|
|
24
|
+
opacity: 1;
|
|
25
|
+
pointer-events: auto;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
package/src/components/ds-table/components/edit/ds-table-editable-cell/ds-table-editable-cell.tsx
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { MouseEvent } from 'react';
|
|
2
|
+
import { DsButtonV3 } from '../../../../ds-button-v3';
|
|
3
|
+
import styles from './ds-table-editable-cell.module.scss';
|
|
4
|
+
import type { DsTableEditableCellProps } from './ds-table-editable-cell.types';
|
|
5
|
+
import { useDsTableContext } from '../../../context/ds-table-context';
|
|
6
|
+
|
|
7
|
+
export const DsTableEditableCell = <TData, TValue>({
|
|
8
|
+
cell,
|
|
9
|
+
children,
|
|
10
|
+
}: DsTableEditableCellProps<TData, TValue>) => {
|
|
11
|
+
const { beginEdit, editing } = useDsTableContext<TData, TValue>();
|
|
12
|
+
const isEditing =
|
|
13
|
+
editing !== null && editing.cell.row.id === cell.row.id && editing.cell.column.id === cell.column.id;
|
|
14
|
+
const handleEdit = (event: MouseEvent<HTMLElement>) => {
|
|
15
|
+
event.stopPropagation();
|
|
16
|
+
beginEdit(cell);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div
|
|
21
|
+
className={styles.root}
|
|
22
|
+
data-active={isEditing ? '' : undefined}
|
|
23
|
+
onDoubleClick={handleEdit}
|
|
24
|
+
tabIndex={0}
|
|
25
|
+
role="button"
|
|
26
|
+
aria-label="Double-click to edit"
|
|
27
|
+
>
|
|
28
|
+
{children}
|
|
29
|
+
<DsButtonV3
|
|
30
|
+
className={styles.editButton}
|
|
31
|
+
variant="tertiary"
|
|
32
|
+
size="small"
|
|
33
|
+
icon="edit"
|
|
34
|
+
aria-label="Edit cell"
|
|
35
|
+
onClick={handleEdit}
|
|
36
|
+
/>
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
@use '../../../../../styles/shared/input' as input;
|
|
2
|
+
|
|
3
|
+
.editAnchor {
|
|
4
|
+
position: relative;
|
|
5
|
+
width: 100%;
|
|
6
|
+
height: 100%;
|
|
7
|
+
min-width: 0;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.overlay {
|
|
11
|
+
position: absolute;
|
|
12
|
+
top: -1px;
|
|
13
|
+
left: -1px;
|
|
14
|
+
z-index: 1;
|
|
15
|
+
display: flex;
|
|
16
|
+
flex-direction: column;
|
|
17
|
+
gap: var(--3xs);
|
|
18
|
+
width: max-content;
|
|
19
|
+
min-width: max(100%, 240px);
|
|
20
|
+
padding: var(--xs) var(--3xs);
|
|
21
|
+
background: var(--background);
|
|
22
|
+
border-top: 1px solid var(--border-action-primary);
|
|
23
|
+
border-bottom-left-radius: var(--3xs);
|
|
24
|
+
border-bottom-right-radius: var(--3xs);
|
|
25
|
+
box-shadow: 0 2px 6px var(--shadow-moderate);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.row {
|
|
29
|
+
display: flex;
|
|
30
|
+
flex-direction: row;
|
|
31
|
+
align-items: flex-start;
|
|
32
|
+
gap: var(--3xs);
|
|
33
|
+
width: 100%;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.slot {
|
|
37
|
+
flex: 1 1 auto;
|
|
38
|
+
min-width: 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.actions {
|
|
42
|
+
display: flex;
|
|
43
|
+
flex-shrink: 0;
|
|
44
|
+
flex-direction: row;
|
|
45
|
+
align-items: center;
|
|
46
|
+
gap: var(--3xs);
|
|
47
|
+
height: input.$input-text-height-default;
|
|
48
|
+
}
|
package/src/components/ds-table/components/edit/ds-table-editing-cell/ds-table-editing-cell.tsx
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { useEffect, useRef, type FormEvent } from 'react';
|
|
2
|
+
import { flexRender } from '@tanstack/react-table';
|
|
3
|
+
import { DsButtonV3 } from '../../../../ds-button-v3';
|
|
4
|
+
import { DsStack } from '../../../../ds-stack';
|
|
5
|
+
import { useDsTableContext } from '../../../context/ds-table-context';
|
|
6
|
+
import styles from './ds-table-editing-cell.module.scss';
|
|
7
|
+
import type { DsTableEditingCellProps } from './ds-table-editing-cell.types';
|
|
8
|
+
|
|
9
|
+
export const DsTableEditingCell = <TData, TValue>({ cell }: DsTableEditingCellProps<TData, TValue>) => {
|
|
10
|
+
const { commit, cancel } = useDsTableContext<TData, TValue>();
|
|
11
|
+
const columnDef = cell.column.columnDef;
|
|
12
|
+
const editAnchorRef = useRef<HTMLDivElement>(null);
|
|
13
|
+
|
|
14
|
+
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
|
|
15
|
+
event.preventDefault();
|
|
16
|
+
event.stopPropagation();
|
|
17
|
+
commit();
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
22
|
+
if (event.key !== 'Escape') {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const anchor = editAnchorRef.current;
|
|
26
|
+
if (!anchor?.contains(document.activeElement)) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
event.preventDefault();
|
|
30
|
+
event.stopPropagation();
|
|
31
|
+
cancel();
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
35
|
+
return () => {
|
|
36
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
37
|
+
};
|
|
38
|
+
}, [cancel]);
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div ref={editAnchorRef} className={styles.editAnchor}>
|
|
42
|
+
<form className={styles.overlay} onSubmit={handleSubmit}>
|
|
43
|
+
<DsStack direction="row" gap="var(--3xs)" alignItems="flex-start" width="100%" className={styles.row}>
|
|
44
|
+
<DsStack flex="1 1 auto" className={styles.slot}>
|
|
45
|
+
{columnDef.editCell ? flexRender(columnDef.editCell, cell.getContext()) : null}
|
|
46
|
+
</DsStack>
|
|
47
|
+
<DsStack direction="row" gap="var(--3xs)" className={styles.actions}>
|
|
48
|
+
<DsButtonV3
|
|
49
|
+
type="button"
|
|
50
|
+
variant="tertiary"
|
|
51
|
+
size="small"
|
|
52
|
+
icon="close"
|
|
53
|
+
aria-label="Cancel edit"
|
|
54
|
+
onClick={(event) => {
|
|
55
|
+
event.stopPropagation();
|
|
56
|
+
cancel();
|
|
57
|
+
}}
|
|
58
|
+
style={{ color: 'var(--icon-error)' }}
|
|
59
|
+
/>
|
|
60
|
+
<DsButtonV3
|
|
61
|
+
type="submit"
|
|
62
|
+
variant="tertiary"
|
|
63
|
+
size="small"
|
|
64
|
+
icon="check"
|
|
65
|
+
aria-label="Confirm edit"
|
|
66
|
+
onClick={(event) => {
|
|
67
|
+
event.stopPropagation();
|
|
68
|
+
}}
|
|
69
|
+
style={{ color: 'var(--icon-success)' }}
|
|
70
|
+
/>
|
|
71
|
+
</DsStack>
|
|
72
|
+
</DsStack>
|
|
73
|
+
</form>
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
};
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
import { createContext, useContext } from 'react';
|
|
1
|
+
import { createContext, useCallback, useContext, useRef, useState } from 'react';
|
|
2
|
+
import type { Cell } from '@tanstack/react-table';
|
|
2
3
|
import type { DsDataTableProps, DsTableRowSize } from '../ds-table.types';
|
|
3
4
|
|
|
5
|
+
export interface EditingState<TData, TValue> {
|
|
6
|
+
cell: Cell<TData, TValue>;
|
|
7
|
+
draftValue: TValue;
|
|
8
|
+
error: string | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
4
11
|
export interface DsTableContextType<TData, TValue> extends Partial<DsDataTableProps<TData, TValue>> {
|
|
5
12
|
/**
|
|
6
13
|
* Whether the table is virtualized
|
|
@@ -16,6 +23,12 @@ export interface DsTableContextType<TData, TValue> extends Partial<DsDataTablePr
|
|
|
16
23
|
* ID of the currently active row
|
|
17
24
|
*/
|
|
18
25
|
activeRowId?: string | null;
|
|
26
|
+
/** The cell currently in edit mode, if any. */
|
|
27
|
+
editing: EditingState<TData, TValue> | null;
|
|
28
|
+
beginEdit: (cell: Cell<TData, TValue>) => void;
|
|
29
|
+
setDraft: (value: TValue) => void;
|
|
30
|
+
commit: (overrideValue?: TValue) => void;
|
|
31
|
+
cancel: () => void;
|
|
19
32
|
}
|
|
20
33
|
|
|
21
34
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -28,3 +41,68 @@ export const useDsTableContext = <TData, TValue>(): DsTableContextType<TData, TV
|
|
|
28
41
|
}
|
|
29
42
|
return context as DsTableContextType<TData, TValue>;
|
|
30
43
|
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Hoisted editing state for single-cell-at-a-time inline editing.
|
|
47
|
+
* Lives on {@link DsTableContext} so cell renderers and editors share one provider.
|
|
48
|
+
*/
|
|
49
|
+
export const useEditingState = <TData, TValue>(
|
|
50
|
+
onCellEdit?: (row: TData, columnId: string, value: TValue) => void,
|
|
51
|
+
onCellValidate?: (row: TData, columnId: string, value: TValue) => string | null,
|
|
52
|
+
) => {
|
|
53
|
+
const [editing, setEditing] = useState<EditingState<TData, TValue> | null>(null);
|
|
54
|
+
|
|
55
|
+
const onCellEditRef = useRef(onCellEdit);
|
|
56
|
+
onCellEditRef.current = onCellEdit;
|
|
57
|
+
|
|
58
|
+
const onCellValidateRef = useRef(onCellValidate);
|
|
59
|
+
onCellValidateRef.current = onCellValidate;
|
|
60
|
+
|
|
61
|
+
const editingRef = useRef(editing);
|
|
62
|
+
editingRef.current = editing;
|
|
63
|
+
|
|
64
|
+
const beginEdit = useCallback((cell: Cell<TData, TValue>) => {
|
|
65
|
+
const current = editingRef.current;
|
|
66
|
+
if (current) {
|
|
67
|
+
const isSameCell = current.cell.row.id === cell.row.id && current.cell.column.id === cell.column.id;
|
|
68
|
+
if (isSameCell) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
setEditing({
|
|
73
|
+
cell,
|
|
74
|
+
draftValue: cell.getValue(),
|
|
75
|
+
error: null,
|
|
76
|
+
});
|
|
77
|
+
}, []);
|
|
78
|
+
|
|
79
|
+
const setDraft = useCallback((value: TValue) => {
|
|
80
|
+
const current = editingRef.current;
|
|
81
|
+
if (!current) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
setEditing({ ...current, draftValue: value, error: null });
|
|
85
|
+
}, []);
|
|
86
|
+
|
|
87
|
+
const commit = useCallback((overrideValue?: TValue) => {
|
|
88
|
+
const current = editingRef.current;
|
|
89
|
+
if (!current) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const valueToCommit = overrideValue !== undefined ? overrideValue : current.draftValue;
|
|
93
|
+
const error =
|
|
94
|
+
onCellValidateRef.current?.(current.cell.row.original, current.cell.column.id, valueToCommit) ?? null;
|
|
95
|
+
if (error !== null) {
|
|
96
|
+
setEditing({ ...current, draftValue: valueToCommit, error });
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
onCellEditRef.current?.(current.cell.row.original, current.cell.column.id, valueToCommit);
|
|
100
|
+
setEditing(null);
|
|
101
|
+
}, []);
|
|
102
|
+
|
|
103
|
+
const cancel = useCallback(() => {
|
|
104
|
+
setEditing(null);
|
|
105
|
+
}, []);
|
|
106
|
+
|
|
107
|
+
return { editing, beginEdit, setDraft, commit, cancel };
|
|
108
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { CellContext, RowData } from '@tanstack/react-table';
|
|
3
|
+
|
|
4
|
+
declare module '@tanstack/react-table' {
|
|
5
|
+
interface ColumnDefBase<TData extends RowData, TValue> {
|
|
6
|
+
/**
|
|
7
|
+
* Renders the cell editor when the cell enters edit mode.
|
|
8
|
+
* When omitted, the column is not editable.
|
|
9
|
+
*/
|
|
10
|
+
editCell?: (context: CellContext<TData, TValue>) => ReactNode;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
@use '../../styles/typography';
|
|
2
|
-
@use '../../styles/
|
|
2
|
+
@use '../../styles/scrollbars' as scrollbars;
|
|
3
3
|
@use './styles/variables' as vars;
|
|
4
4
|
|
|
5
5
|
:root {
|
|
@@ -9,9 +9,10 @@
|
|
|
9
9
|
--ds-table-header-height: 45px;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
$bulk-actions-
|
|
12
|
+
$bulk-actions-reserved-space: 80px;
|
|
13
13
|
|
|
14
14
|
.container {
|
|
15
|
+
position: relative;
|
|
15
16
|
border-radius: vars.$border-radius;
|
|
16
17
|
border-width: vars.$border-width;
|
|
17
18
|
border-style: solid;
|
|
@@ -21,7 +22,51 @@ $bulk-actions-height: 60px;
|
|
|
21
22
|
|
|
22
23
|
.dataTableContainer {
|
|
23
24
|
max-height: 100%;
|
|
24
|
-
overflow: auto;
|
|
25
|
+
overflow-x: auto;
|
|
26
|
+
overflow-y: hidden;
|
|
27
|
+
display: flex;
|
|
28
|
+
flex-direction: column;
|
|
29
|
+
|
|
30
|
+
@include scrollbars.scrollbar-on-hover;
|
|
31
|
+
|
|
32
|
+
> table {
|
|
33
|
+
display: flex;
|
|
34
|
+
flex-direction: column;
|
|
35
|
+
flex: 1;
|
|
36
|
+
min-height: 0;
|
|
37
|
+
width: max-content;
|
|
38
|
+
min-width: 100%;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
thead {
|
|
42
|
+
display: block;
|
|
43
|
+
flex-shrink: 0;
|
|
44
|
+
overflow-y: scroll;
|
|
45
|
+
scrollbar-gutter: stable;
|
|
46
|
+
|
|
47
|
+
@include scrollbars.scrollbar-on-hover;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
tbody {
|
|
51
|
+
display: block;
|
|
52
|
+
overflow-y: auto;
|
|
53
|
+
flex: 1;
|
|
54
|
+
min-height: 0;
|
|
55
|
+
scrollbar-gutter: stable;
|
|
56
|
+
|
|
57
|
+
@include scrollbars.scrollbar-on-hover;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
thead tr,
|
|
61
|
+
tbody tr {
|
|
62
|
+
display: flex;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
thead th,
|
|
66
|
+
tbody td {
|
|
67
|
+
display: flex;
|
|
68
|
+
align-items: center;
|
|
69
|
+
}
|
|
25
70
|
}
|
|
26
71
|
|
|
27
72
|
.table {
|
|
@@ -40,21 +85,66 @@ $bulk-actions-height: 60px;
|
|
|
40
85
|
width: 100%;
|
|
41
86
|
}
|
|
42
87
|
|
|
88
|
+
.bulkActionsVisible {
|
|
89
|
+
tbody {
|
|
90
|
+
padding-bottom: $bulk-actions-reserved-space;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
43
94
|
.emptyState {
|
|
95
|
+
// colSpan in markup isn't honored by flex row layout, so the cell stretches
|
|
96
|
+
// across the row; stretch the inner content so it occupies full table width
|
|
97
|
+
flex: 1;
|
|
98
|
+
min-width: 0;
|
|
44
99
|
height: vars.$empty-state-height;
|
|
100
|
+
justify-content: center;
|
|
45
101
|
text-align: center;
|
|
46
|
-
}
|
|
47
102
|
|
|
48
|
-
|
|
49
|
-
|
|
103
|
+
> * {
|
|
104
|
+
flex: 1;
|
|
105
|
+
}
|
|
50
106
|
}
|
|
51
107
|
|
|
52
108
|
.virtualizedContainer {
|
|
53
109
|
position: relative;
|
|
54
110
|
height: vars.$virtualized-height;
|
|
55
|
-
overflow: auto;
|
|
56
|
-
|
|
111
|
+
overflow-x: auto;
|
|
112
|
+
overflow-y: hidden;
|
|
113
|
+
display: flex;
|
|
114
|
+
flex-direction: column;
|
|
57
115
|
|
|
58
|
-
.
|
|
59
|
-
|
|
116
|
+
@include scrollbars.scrollbar-on-hover;
|
|
117
|
+
|
|
118
|
+
> table {
|
|
119
|
+
display: flex;
|
|
120
|
+
flex-direction: column;
|
|
121
|
+
flex: 1;
|
|
122
|
+
min-height: 0;
|
|
123
|
+
width: max-content;
|
|
124
|
+
min-width: 100%;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
thead {
|
|
128
|
+
display: block;
|
|
129
|
+
flex-shrink: 0;
|
|
130
|
+
overflow-y: scroll;
|
|
131
|
+
scrollbar-gutter: stable;
|
|
132
|
+
|
|
133
|
+
@include scrollbars.scrollbar-on-hover;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
tbody {
|
|
137
|
+
flex: 1;
|
|
138
|
+
min-height: 0;
|
|
139
|
+
scrollbar-gutter: stable;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
thead tr {
|
|
143
|
+
display: flex;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
thead th {
|
|
147
|
+
display: flex;
|
|
148
|
+
align-items: center;
|
|
149
|
+
}
|
|
60
150
|
}
|
|
@@ -23,7 +23,7 @@ import { DsTableRowExpandableCell } from './components/ds-table-row-expandable-c
|
|
|
23
23
|
import { DsTableRowSelectableCell } from './components/ds-table-row-selectable-cell';
|
|
24
24
|
import { DsTableHeaderSelectableCell } from './components/ds-table-header-selectable-cell';
|
|
25
25
|
import { useDragAndDrop } from './hooks/use-drag-and-drop';
|
|
26
|
-
import { type DsTableContextType, DsTableContext } from './context/ds-table-context';
|
|
26
|
+
import { type DsTableContextType, DsTableContext, useEditingState } from './context/ds-table-context';
|
|
27
27
|
import { DsTableBodyVirtualized } from './components/ds-table-body-virtualized';
|
|
28
28
|
import {
|
|
29
29
|
EMPTY_TABLE_STATE_TEXT,
|
|
@@ -77,6 +77,8 @@ const DsTable = <TData extends { id: string }, TValue>({
|
|
|
77
77
|
onColumnVisibilityChange,
|
|
78
78
|
activeRowId,
|
|
79
79
|
infiniteScroll,
|
|
80
|
+
onCellEdit,
|
|
81
|
+
onCellValidate,
|
|
80
82
|
}: DsDataTableProps<TData, TValue>) => {
|
|
81
83
|
const [data, setData] = React.useState(tableData);
|
|
82
84
|
const [sorting, setSorting] = React.useState<SortingState>([]);
|
|
@@ -267,6 +269,8 @@ const DsTable = <TData extends { id: string }, TValue>({
|
|
|
267
269
|
|
|
268
270
|
const isBulkActionsVisible = selectable && actions.length > 0 && selectedRows.length > 0;
|
|
269
271
|
|
|
272
|
+
const editingState = useEditingState<TData, TValue>(onCellEdit, onCellValidate);
|
|
273
|
+
|
|
270
274
|
const contextValue: DsTableContextType<TData, TValue> = {
|
|
271
275
|
stickyHeader,
|
|
272
276
|
bordered,
|
|
@@ -283,6 +287,8 @@ const DsTable = <TData extends { id: string }, TValue>({
|
|
|
283
287
|
renderExpandedRow,
|
|
284
288
|
virtualized,
|
|
285
289
|
activeRowId,
|
|
290
|
+
onCellEdit,
|
|
291
|
+
...editingState,
|
|
286
292
|
};
|
|
287
293
|
|
|
288
294
|
return (
|
|
@@ -298,18 +304,11 @@ const DsTable = <TData extends { id: string }, TValue>({
|
|
|
298
304
|
)}
|
|
299
305
|
>
|
|
300
306
|
<DragWrapper>
|
|
301
|
-
<Table
|
|
302
|
-
className={classnames(
|
|
303
|
-
fullWidth && styles.fullWidth,
|
|
304
|
-
!bordered && styles.tableNoBorder,
|
|
305
|
-
virtualized && styles.virtualized,
|
|
306
|
-
)}
|
|
307
|
-
>
|
|
307
|
+
<Table className={classnames(fullWidth && styles.fullWidth, !bordered && styles.tableNoBorder)}>
|
|
308
308
|
<DsTableHeader table={table} />
|
|
309
309
|
{virtualized ? (
|
|
310
310
|
<DsTableBodyVirtualized
|
|
311
311
|
table={table}
|
|
312
|
-
tableContainerRef={tableContainerRef}
|
|
313
312
|
emptyState={emptyState}
|
|
314
313
|
estimateSize={virtualizedOptions?.estimateSize || ROW_SIZE_HEIGHT_MAP[rowSize]}
|
|
315
314
|
overscan={virtualizedOptions?.overscan}
|
|
@@ -330,17 +329,17 @@ const DsTable = <TData extends { id: string }, TValue>({
|
|
|
330
329
|
)}
|
|
331
330
|
</Table>
|
|
332
331
|
</DragWrapper>
|
|
332
|
+
{selectable && actions.length > 0 && (
|
|
333
|
+
<DsTableBulkActions
|
|
334
|
+
numSelectedRows={selectedRows.length}
|
|
335
|
+
actions={actions.map((action) => ({
|
|
336
|
+
...action,
|
|
337
|
+
onClick: () => action.onClick(selectedRows),
|
|
338
|
+
}))}
|
|
339
|
+
onClearSelection={table.resetRowSelection}
|
|
340
|
+
/>
|
|
341
|
+
)}
|
|
333
342
|
</div>
|
|
334
|
-
{selectable && actions.length > 0 && (
|
|
335
|
-
<DsTableBulkActions
|
|
336
|
-
numSelectedRows={selectedRows.length}
|
|
337
|
-
actions={actions.map((action) => ({
|
|
338
|
-
...action,
|
|
339
|
-
onClick: () => action.onClick(selectedRows),
|
|
340
|
-
}))}
|
|
341
|
-
onClearSelection={table.resetRowSelection}
|
|
342
|
-
/>
|
|
343
|
-
)}
|
|
344
343
|
</DsTableContext.Provider>
|
|
345
344
|
);
|
|
346
345
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
|
+
import './ds-table-column.types';
|
|
2
3
|
import type {
|
|
3
4
|
ColumnDef,
|
|
4
5
|
ColumnFiltersState,
|
|
@@ -440,4 +441,55 @@ export interface DsDataTableProps<TData, TValue> {
|
|
|
440
441
|
* Callback when column visibility changes
|
|
441
442
|
*/
|
|
442
443
|
onColumnVisibilityChange?: (visibility: VisibilityState) => void;
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Callback fired when an editable cell commits a new value via one of the
|
|
447
|
+
* `DsTableEditCell*` wrappers or the `useCellEditor` hook. The parent is
|
|
448
|
+
* expected to update the row data in response.
|
|
449
|
+
*
|
|
450
|
+
* Commits occur on confirm (check button or Enter). Opening another cell
|
|
451
|
+
* discards the previous draft without calling this callback.
|
|
452
|
+
*
|
|
453
|
+
* @param row - The row data of the cell that was edited
|
|
454
|
+
* @param columnId - The id of the column whose cell was edited
|
|
455
|
+
* @param value - The new committed value
|
|
456
|
+
*
|
|
457
|
+
* @example
|
|
458
|
+
* ```tsx
|
|
459
|
+
* const [people, setPeople] = useState(initialPeople);
|
|
460
|
+
*
|
|
461
|
+
* <DsTable
|
|
462
|
+
* data={people}
|
|
463
|
+
* columns={columns}
|
|
464
|
+
* onCellEdit={(row, columnId, value) => {
|
|
465
|
+
* setPeople((rows) =>
|
|
466
|
+
* rows.map((r) => (r.id === row.id ? { ...r, [columnId]: value } : r)),
|
|
467
|
+
* );
|
|
468
|
+
* }}
|
|
469
|
+
* />
|
|
470
|
+
* ```
|
|
471
|
+
*/
|
|
472
|
+
onCellEdit?: (row: TData, columnId: string, value: TValue) => void;
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Validates a cell value before it is committed. Return `null` to allow the
|
|
476
|
+
* commit (which fires `onCellEdit`), or an error message string to reject it
|
|
477
|
+
* and keep the cell in edit mode. Synchronous only.
|
|
478
|
+
*
|
|
479
|
+
*
|
|
480
|
+
* @param row - The row data of the cell being edited
|
|
481
|
+
* @param columnId - The id of the column whose cell is being edited
|
|
482
|
+
* @param value - The candidate value to validate
|
|
483
|
+
*
|
|
484
|
+
* @example
|
|
485
|
+
* ```tsx
|
|
486
|
+
* <DsTable
|
|
487
|
+
* data={people}
|
|
488
|
+
* columns={columns}
|
|
489
|
+
* onCellValidate={(row, columnId, value) => validateField(columnId)(value, row)}
|
|
490
|
+
* onCellEdit={...}
|
|
491
|
+
* />
|
|
492
|
+
* ```
|
|
493
|
+
*/
|
|
494
|
+
onCellValidate?: (row: TData, columnId: string, value: TValue) => string | null;
|
|
443
495
|
}
|