@drivenets/design-system 0.12.1 → 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 +15 -0
- package/dist/index.cjs +1097 -308
- package/dist/index.d.cts +1118 -982
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +1118 -982
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1089 -309
- package/dist/index.js.map +1 -1
- package/dist/index.min.css +1 -1
- package/package.json +31 -31
- 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-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 +1 -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,27 @@
|
|
|
1
|
+
import type { CellContext, RowData } from '@tanstack/react-table';
|
|
2
|
+
import { DsCheckbox } from '../../../../ds-checkbox';
|
|
3
|
+
import { useCellEditor } from '../../../hooks/use-cell-editor';
|
|
4
|
+
|
|
5
|
+
export interface DsTableEditCellCheckboxProps<TData extends RowData> {
|
|
6
|
+
/** TanStack Table cell context from `ColumnDef.editCell`. */
|
|
7
|
+
cellContext: CellContext<TData, boolean>;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Checkbox editor for table cells. Render via `ColumnDef.editCell`. */
|
|
12
|
+
export const DsTableEditCellCheckbox = <TData extends RowData>({
|
|
13
|
+
cellContext,
|
|
14
|
+
disabled,
|
|
15
|
+
}: DsTableEditCellCheckboxProps<TData>) => {
|
|
16
|
+
const { value, setValue } = useCellEditor<TData, boolean>({
|
|
17
|
+
cellContext,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<DsCheckbox
|
|
22
|
+
checked={value}
|
|
23
|
+
disabled={disabled}
|
|
24
|
+
onCheckedChange={(checked) => setValue(checked === true)}
|
|
25
|
+
/>
|
|
26
|
+
);
|
|
27
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { CellContext, RowData } from '@tanstack/react-table';
|
|
2
|
+
import { DsFormControl } from '../../../../ds-form-control';
|
|
3
|
+
import { DsIcon } from '../../../../ds-icon';
|
|
4
|
+
import { useCellEditor } from '../../../hooks/use-cell-editor';
|
|
5
|
+
import styles from './cell-editors.module.scss';
|
|
6
|
+
import { TableEditFormControl } from './table-edit-form-control';
|
|
7
|
+
|
|
8
|
+
export type DsTableEditCellDateValue = Date | null;
|
|
9
|
+
|
|
10
|
+
export interface DsTableEditCellDateProps<TData extends RowData> {
|
|
11
|
+
/** TanStack Table cell context from `ColumnDef.editCell`. */
|
|
12
|
+
cellContext: CellContext<TData, DsTableEditCellDateValue>;
|
|
13
|
+
min?: Date;
|
|
14
|
+
max?: Date;
|
|
15
|
+
placeholder?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Date picker editor for table cells. Render via `ColumnDef.editCell`. */
|
|
19
|
+
export const DsTableEditCellDate = <TData extends RowData>({
|
|
20
|
+
cellContext,
|
|
21
|
+
min,
|
|
22
|
+
max,
|
|
23
|
+
placeholder,
|
|
24
|
+
}: DsTableEditCellDateProps<TData>) => {
|
|
25
|
+
const { value, setValue, error } = useCellEditor<TData, DsTableEditCellDateValue>({
|
|
26
|
+
cellContext,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<TableEditFormControl cellContext={cellContext} error={error}>
|
|
31
|
+
<DsFormControl.DatePicker
|
|
32
|
+
className={styles.fullWidth}
|
|
33
|
+
value={value}
|
|
34
|
+
min={min}
|
|
35
|
+
max={max}
|
|
36
|
+
placeholder={placeholder}
|
|
37
|
+
onChange={setValue}
|
|
38
|
+
slotProps={
|
|
39
|
+
error
|
|
40
|
+
? {
|
|
41
|
+
input: {
|
|
42
|
+
slots: { endAdornment: <DsIcon icon="error" size="tiny" /> },
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
: undefined
|
|
46
|
+
}
|
|
47
|
+
/>
|
|
48
|
+
</TableEditFormControl>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
import type { CellContext, RowData } from '@tanstack/react-table';
|
|
3
|
+
import { DsFormControl } from '../../../../ds-form-control';
|
|
4
|
+
import { useCellEditor } from '../../../hooks/use-cell-editor';
|
|
5
|
+
import styles from './cell-editors.module.scss';
|
|
6
|
+
import { TableEditFormControl } from './table-edit-form-control';
|
|
7
|
+
|
|
8
|
+
export interface DsTableEditCellNumberProps<TData extends RowData> {
|
|
9
|
+
/** TanStack Table cell context from `ColumnDef.editCell`. */
|
|
10
|
+
cellContext: CellContext<TData, number>;
|
|
11
|
+
min?: number;
|
|
12
|
+
max?: number;
|
|
13
|
+
step?: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Number input editor for table cells. Render via `ColumnDef.editCell`. */
|
|
17
|
+
export const DsTableEditCellNumber = <TData extends RowData>({
|
|
18
|
+
cellContext,
|
|
19
|
+
min,
|
|
20
|
+
max,
|
|
21
|
+
step,
|
|
22
|
+
}: DsTableEditCellNumberProps<TData>) => {
|
|
23
|
+
const { value, setValue, error } = useCellEditor<TData, number>({
|
|
24
|
+
cellContext,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
const input = inputRef.current;
|
|
31
|
+
if (!input) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
input.focus();
|
|
35
|
+
input.select();
|
|
36
|
+
}, []);
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<TableEditFormControl cellContext={cellContext} error={error}>
|
|
40
|
+
<DsFormControl.NumberInput
|
|
41
|
+
ref={inputRef}
|
|
42
|
+
className={styles.fullWidth}
|
|
43
|
+
value={value}
|
|
44
|
+
min={min}
|
|
45
|
+
max={max}
|
|
46
|
+
step={step}
|
|
47
|
+
onValueChange={setValue}
|
|
48
|
+
/>
|
|
49
|
+
</TableEditFormControl>
|
|
50
|
+
);
|
|
51
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { CellContext, RowData } from '@tanstack/react-table';
|
|
2
|
+
import { DsFormControl } from '../../../../ds-form-control';
|
|
3
|
+
import { type DsSelectOption } from '../../../../ds-select';
|
|
4
|
+
import { useCellEditor } from '../../../hooks/use-cell-editor';
|
|
5
|
+
import styles from './cell-editors.module.scss';
|
|
6
|
+
import { TableEditFormControl } from './table-edit-form-control';
|
|
7
|
+
|
|
8
|
+
export interface DsTableEditCellSelectProps<TData extends RowData> {
|
|
9
|
+
/** TanStack Table cell context from `ColumnDef.editCell`. */
|
|
10
|
+
cellContext: CellContext<TData, string>;
|
|
11
|
+
options: DsSelectOption[];
|
|
12
|
+
placeholder?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Select editor for table cells. Render via `ColumnDef.editCell`. */
|
|
16
|
+
export const DsTableEditCellSelect = <TData extends RowData>({
|
|
17
|
+
cellContext,
|
|
18
|
+
options,
|
|
19
|
+
placeholder,
|
|
20
|
+
}: DsTableEditCellSelectProps<TData>) => {
|
|
21
|
+
const { value, setValue, error } = useCellEditor<TData, string>({
|
|
22
|
+
cellContext,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<TableEditFormControl cellContext={cellContext} error={error}>
|
|
27
|
+
<DsFormControl.Select
|
|
28
|
+
className={styles.fullWidth}
|
|
29
|
+
value={value}
|
|
30
|
+
options={options}
|
|
31
|
+
placeholder={placeholder}
|
|
32
|
+
onValueChange={setValue}
|
|
33
|
+
/>
|
|
34
|
+
</TableEditFormControl>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
import type { CellContext, RowData } from '@tanstack/react-table';
|
|
3
|
+
import { DsFormControl } from '../../../../ds-form-control';
|
|
4
|
+
import { DsIcon } from '../../../../ds-icon';
|
|
5
|
+
import { useCellEditor } from '../../../hooks/use-cell-editor';
|
|
6
|
+
import styles from './cell-editors.module.scss';
|
|
7
|
+
import { TableEditFormControl } from './table-edit-form-control';
|
|
8
|
+
|
|
9
|
+
export interface DsTableEditCellTextProps<TData extends RowData> {
|
|
10
|
+
/** TanStack Table cell context from `ColumnDef.editCell`. */
|
|
11
|
+
cellContext: CellContext<TData, string>;
|
|
12
|
+
placeholder?: string;
|
|
13
|
+
maxLength?: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Text input editor for table cells. Render via `ColumnDef.editCell`. */
|
|
17
|
+
export const DsTableEditCellText = <TData extends RowData>({
|
|
18
|
+
cellContext,
|
|
19
|
+
placeholder,
|
|
20
|
+
maxLength,
|
|
21
|
+
}: DsTableEditCellTextProps<TData>) => {
|
|
22
|
+
const { value, setValue, error } = useCellEditor<TData, string>({
|
|
23
|
+
cellContext,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
const input = inputRef.current;
|
|
30
|
+
if (!input) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
input.focus();
|
|
34
|
+
input.select();
|
|
35
|
+
}, []);
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<TableEditFormControl cellContext={cellContext} error={error}>
|
|
39
|
+
<DsFormControl.TextInput
|
|
40
|
+
ref={inputRef}
|
|
41
|
+
className={styles.fullWidth}
|
|
42
|
+
value={value}
|
|
43
|
+
placeholder={placeholder}
|
|
44
|
+
maxLength={maxLength}
|
|
45
|
+
onValueChange={setValue}
|
|
46
|
+
slots={error ? { endAdornment: <DsIcon icon="error" size="tiny" /> } : undefined}
|
|
47
|
+
/>
|
|
48
|
+
</TableEditFormControl>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { DsTableEditCellText } from './ds-table-edit-cell-text';
|
|
2
|
+
export type { DsTableEditCellTextProps } from './ds-table-edit-cell-text';
|
|
3
|
+
|
|
4
|
+
export { DsTableEditCellNumber } from './ds-table-edit-cell-number';
|
|
5
|
+
export type { DsTableEditCellNumberProps } from './ds-table-edit-cell-number';
|
|
6
|
+
|
|
7
|
+
export { DsTableEditCellSelect } from './ds-table-edit-cell-select';
|
|
8
|
+
export type { DsTableEditCellSelectProps } from './ds-table-edit-cell-select';
|
|
9
|
+
|
|
10
|
+
export { DsTableEditCellDate } from './ds-table-edit-cell-date';
|
|
11
|
+
export type { DsTableEditCellDateProps, DsTableEditCellDateValue } from './ds-table-edit-cell-date';
|
|
12
|
+
|
|
13
|
+
export { DsTableEditCellCheckbox } from './ds-table-edit-cell-checkbox';
|
|
14
|
+
export type { DsTableEditCellCheckboxProps } from './ds-table-edit-cell-checkbox';
|
|
@@ -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
|
+
}
|