@nocobase/client-v2 2.1.0-beta.33 → 2.1.0-beta.34

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.
Files changed (61) hide show
  1. package/es/APIClient.d.ts +16 -0
  2. package/es/Application.d.ts +2 -1
  3. package/es/authRedirect.d.ts +9 -16
  4. package/es/components/form/EnvVariableInput.d.ts +8 -6
  5. package/es/components/form/VariableInput.d.ts +73 -0
  6. package/es/components/form/index.d.ts +1 -0
  7. package/es/components/form/table/RowOverlayPreview.d.ts +27 -0
  8. package/es/components/form/table/SelectionCell.d.ts +36 -0
  9. package/es/components/form/table/Table.d.ts +82 -0
  10. package/es/components/form/table/constants.d.ts +15 -0
  11. package/es/components/form/table/dnd/SortableRow.d.ts +40 -0
  12. package/es/components/form/table/dnd/index.d.ts +9 -0
  13. package/es/components/form/table/index.d.ts +9 -0
  14. package/es/components/form/table/styles.d.ts +41 -0
  15. package/es/components/form/table/utils.d.ts +44 -0
  16. package/es/components/index.d.ts +2 -0
  17. package/es/flow/components/TextAreaWithContextSelector.d.ts +15 -0
  18. package/es/flow/models/blocks/table/dragSort/dragSortComponents.d.ts +1 -6
  19. package/es/flow/models/blocks/table/dragSort/dragSortHooks.d.ts +5 -1
  20. package/es/flow-compat/passwordUtils.d.ts +1 -1
  21. package/es/index.d.ts +1 -0
  22. package/es/index.mjs +145 -78
  23. package/es/theme/globalStyles.d.ts +9 -0
  24. package/es/theme/index.d.ts +1 -0
  25. package/lib/index.js +161 -94
  26. package/package.json +8 -6
  27. package/src/APIClient.ts +68 -0
  28. package/src/Application.tsx +6 -2
  29. package/src/__tests__/authRedirect.test.ts +170 -64
  30. package/src/__tests__/globalDeps.test.ts +2 -0
  31. package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +6 -6
  32. package/src/authRedirect.ts +23 -84
  33. package/src/components/form/EnvVariableInput.tsx +11 -46
  34. package/src/components/form/VariableInput.tsx +177 -0
  35. package/src/components/form/__tests__/EnvVariableInput.test.tsx +175 -0
  36. package/src/components/form/index.tsx +1 -0
  37. package/src/components/form/table/RowOverlayPreview.tsx +51 -0
  38. package/src/components/form/table/SelectionCell.tsx +72 -0
  39. package/src/components/form/table/Table.tsx +279 -0
  40. package/src/components/form/table/__tests__/Table.pagination.test.tsx +80 -0
  41. package/src/components/form/table/constants.ts +16 -0
  42. package/src/components/form/table/dnd/SortableRow.tsx +106 -0
  43. package/src/components/form/table/dnd/index.ts +10 -0
  44. package/src/components/form/table/index.tsx +13 -0
  45. package/src/components/form/table/styles.ts +110 -0
  46. package/src/components/form/table/utils.ts +75 -0
  47. package/src/components/index.ts +2 -0
  48. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +2 -0
  49. package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.test.ts +111 -0
  50. package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.ts +2 -1
  51. package/src/flow/components/TextAreaWithContextSelector.tsx +30 -6
  52. package/src/flow/components/code-editor/__tests__/useCodeRunner.test.tsx +81 -0
  53. package/src/flow/components/code-editor/hooks/useCodeRunner.ts +34 -2
  54. package/src/flow/models/blocks/table/dragSort/dragSortComponents.tsx +1 -81
  55. package/src/flow/models/fields/JSEditableFieldModel.tsx +107 -7
  56. package/src/flow/models/fields/__tests__/JSEditableFieldModel.test.tsx +97 -0
  57. package/src/index.ts +1 -0
  58. package/src/nocobase-buildin-plugin/index.tsx +4 -4
  59. package/src/theme/globalStyles.ts +21 -0
  60. package/src/theme/index.tsx +1 -0
  61. package/src/utils/globalDeps.ts +5 -1
@@ -0,0 +1,279 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ import { DragOverlay, type DragEndEvent, type DragStartEvent } from '@dnd-kit/core';
11
+ import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
12
+ import { cx } from '@emotion/css';
13
+ import { DndProvider } from '@nocobase/flow-engine';
14
+ import { useMemoizedFn } from 'ahooks';
15
+ import { Table as AntdTable, type TableProps as AntdTableProps } from 'antd';
16
+ import type { ColumnsType, ColumnType, GetRowKey } from 'antd/es/table/interface';
17
+ import type { RenderedCell } from 'rc-table/lib/interface';
18
+ import React, { useMemo, useState } from 'react';
19
+ import { SortableRow, SortHandle } from './dnd/SortableRow';
20
+ import { RowOverlayPreview } from './RowOverlayPreview';
21
+ import { SelectionCell } from './SelectionCell';
22
+ import { indexSwapClassName, selectionGutterClassName } from './styles';
23
+ import { readRowKey, snapshotSourceRow, type RowKey, type RowSnapshot } from './utils';
24
+
25
+ type RowSelectionRenderCellResult<RecordType> = React.ReactNode | RenderedCell<RecordType>;
26
+
27
+ /**
28
+ * Default initial page size for `Table`. Exposed so consumers can seed their
29
+ * controlled `pageSize` state with the same value the component would use if
30
+ * they relied purely on the built-in pagination defaults.
31
+ */
32
+ export const DEFAULT_PAGE_SIZE = 50;
33
+
34
+ /**
35
+ * Default `pageSizeOptions` injected into the pagination config. Matches the
36
+ * v1 settings-page table so users see the same choices across versions.
37
+ * Consumers can override by passing their own `pageSizeOptions` in
38
+ * `pagination`.
39
+ */
40
+ export const PAGE_SIZE_OPTIONS: readonly number[] = [5, 10, 20, 50, 100, 200];
41
+
42
+ /**
43
+ * antd's `rowSelection.renderCell` can return either a `ReactNode` or a
44
+ * `RenderedCell` (the `{ children, props }` shape used to drive colSpan /
45
+ * rowSpan). `SelectionCell` only paints inside an existing selection cell —
46
+ * cell-spanning isn't supported here — so this guard unwraps to the inner
47
+ * children when a `RenderedCell` slips through.
48
+ */
49
+ function isRenderedCell<RecordType>(value: unknown): value is RenderedCell<RecordType> {
50
+ return typeof value === 'object' && value !== null && !React.isValidElement(value) && 'children' in value;
51
+ }
52
+
53
+ export interface TableProps<RecordType extends object = any> extends AntdTableProps<RecordType> {
54
+ /**
55
+ * Required so drag-sort, hover-swap and row identity work. Accepts the same
56
+ * shape as antd `Table.rowKey`. When passed as a function it gets the
57
+ * record + index and must return a serializable id.
58
+ */
59
+ rowKey: RowKey<RecordType>;
60
+ /**
61
+ * Show row index (1, 2, 3, …) in the rowSelection column by default; hovering
62
+ * a row or selecting it reveals the checkbox in the same cell. Requires
63
+ * `rowSelection` — the index lives in the selection column. Defaults to
64
+ * `true`.
65
+ */
66
+ showIndex?: boolean;
67
+ /**
68
+ * Enable vertical drag-and-drop row reordering. When true, rows show a drag
69
+ * handle on the left and `onSortEnd` fires after each drop. Defaults to
70
+ * `false`; the rest of the table behaves like a plain antd Table.
71
+ */
72
+ isDraggable?: boolean;
73
+ /**
74
+ * Called after a row is dropped onto another. Only used when `isDraggable`
75
+ * is true. The caller persists the move (e.g. `resource.move(...)`) and
76
+ * refreshes `dataSource` — this component does NOT mutate the data array.
77
+ */
78
+ onSortEnd?: (from: RecordType, to: RecordType) => void | Promise<void>;
79
+ /**
80
+ * Hide the drag handle entirely while still keeping `isDraggable` on. Useful
81
+ * when the caller wants to embed `<SortHandle />` inside a custom column.
82
+ * Defaults to `true`.
83
+ */
84
+ showSortHandle?: boolean;
85
+ /**
86
+ * Override the width of the auto-inserted handle column when `rowSelection`
87
+ * is absent. When `rowSelection` is provided the handle is rendered inside
88
+ * the selection column instead, so this prop is ignored.
89
+ */
90
+ sortHandleColumnWidth?: number;
91
+ }
92
+
93
+ /**
94
+ * Generic v2 settings-page table primitive. Built on antd's `Table` and adds:
95
+ *
96
+ * - Row-index ↔ checkbox swap inside the selection column (`showIndex`,
97
+ * on by default): index visible at rest, checkbox shows on hover or when
98
+ * selected. Both elements are absolutely positioned inside the cell so
99
+ * they share the same center anchor and never compete for layout space.
100
+ * - Optional vertical drag-and-drop reordering (`isDraggable`): handle is
101
+ * absolute-positioned in a 32px left gutter so the checkbox column stays
102
+ * visually centered. The drag overlay renders an `outerHTML` clone of the
103
+ * source `<tr>`; the index is suppressed inside the clone so the row
104
+ * being moved doesn't display a stale ordinal.
105
+ *
106
+ * Use this in place of antd `Table` for any settings-page list. When
107
+ * `isDraggable` is false the component is a thin pass-through to antd Table
108
+ * plus the index swap, so it is safe as the default table on any page.
109
+ */
110
+ export function Table<RecordType extends object = any>(props: TableProps<RecordType>) {
111
+ const {
112
+ rowKey,
113
+ showIndex = true,
114
+ isDraggable = false,
115
+ onSortEnd,
116
+ showSortHandle = true,
117
+ sortHandleColumnWidth = 40,
118
+ components,
119
+ columns,
120
+ dataSource,
121
+ rowSelection,
122
+ className,
123
+ pagination,
124
+ ...rest
125
+ } = props;
126
+
127
+ // Apply opinionated pagination defaults (showSizeChanger + a v1-aligned set
128
+ // of pageSizeOptions). `pagination === false` is preserved verbatim so
129
+ // callers can still disable pagination outright; for any other value (object
130
+ // or undefined) we spread caller-provided keys last so explicit overrides
131
+ // win.
132
+ const mergedPagination = useMemo<AntdTableProps<RecordType>['pagination']>(() => {
133
+ if (pagination === false) {
134
+ return false;
135
+ }
136
+ return {
137
+ showSizeChanger: true,
138
+ pageSizeOptions: [...PAGE_SIZE_OPTIONS],
139
+ ...(pagination ?? {}),
140
+ };
141
+ }, [pagination]);
142
+
143
+ const showHandleInSelection = isDraggable && showSortHandle && !!rowSelection;
144
+ const showStandaloneHandleColumn = isDraggable && showSortHandle && !rowSelection;
145
+
146
+ const itemKeys = useMemo<string[]>(() => {
147
+ if (!isDraggable || !dataSource) return [];
148
+ return dataSource
149
+ .map((record, index) => readRowKey(record, rowKey, index))
150
+ .filter((key): key is React.Key => key != null)
151
+ .map((key) => String(key));
152
+ }, [dataSource, isDraggable, rowKey]);
153
+
154
+ // Snapshot of the source `<tr>` at drag start. Cleared on drag end / cancel.
155
+ const [activeSnapshot, setActiveSnapshot] = useState<RowSnapshot | null>(null);
156
+
157
+ const handleDragStart = useMemoizedFn((event: DragStartEvent) => {
158
+ setActiveSnapshot(snapshotSourceRow(String(event.active.id)));
159
+ });
160
+
161
+ const handleDragCancel = useMemoizedFn(() => {
162
+ setActiveSnapshot(null);
163
+ });
164
+
165
+ const handleDragEnd = useMemoizedFn(async (event: DragEndEvent) => {
166
+ setActiveSnapshot(null);
167
+ const { active, over } = event;
168
+ if (!active || !over || !onSortEnd) return;
169
+ if (String(active.id) === String(over.id)) return;
170
+ if (!dataSource) return;
171
+ const fromIndex = dataSource.findIndex(
172
+ (record, index) => String(readRowKey(record, rowKey, index)) === String(active.id),
173
+ );
174
+ const toIndex = dataSource.findIndex(
175
+ (record, index) => String(readRowKey(record, rowKey, index)) === String(over.id),
176
+ );
177
+ if (fromIndex < 0 || toIndex < 0) return;
178
+ const from = dataSource[fromIndex];
179
+ const to = dataSource[toIndex];
180
+ if (!from || !to) return;
181
+ await onSortEnd(from, to);
182
+ });
183
+
184
+ const tableComponents = useMemo(() => {
185
+ if (!isDraggable) return components;
186
+ return { ...components, body: { ...components?.body, row: SortableRow } };
187
+ }, [components, isDraggable]);
188
+
189
+ // When dragging without rowSelection, prepend a standalone column for the
190
+ // handle. When rowSelection is present, the handle lives inside the
191
+ // selection cell — see `augmentedRowSelection` below.
192
+ const augmentedColumns = useMemo<ColumnsType<RecordType>>(() => {
193
+ const baseColumns: ColumnsType<RecordType> = columns ?? [];
194
+ if (!showStandaloneHandleColumn) return baseColumns;
195
+ const handleColumn: ColumnType<RecordType> = {
196
+ key: '__sort__',
197
+ width: sortHandleColumnWidth,
198
+ align: 'center',
199
+ render: () => <SortHandle />,
200
+ };
201
+ return [handleColumn, ...baseColumns];
202
+ }, [columns, showStandaloneHandleColumn, sortHandleColumnWidth]);
203
+
204
+ const augmentedRowSelection = useMemo(() => {
205
+ if (!rowSelection) return rowSelection;
206
+ if (!showHandleInSelection && !showIndex) return rowSelection;
207
+ const originalRenderCell = rowSelection.renderCell;
208
+ return {
209
+ ...rowSelection,
210
+ renderCell: (checked: boolean, record: RecordType, index: number, originalNode: React.ReactNode) => {
211
+ const result: RowSelectionRenderCellResult<RecordType> = originalRenderCell
212
+ ? originalRenderCell(checked, record, index, originalNode)
213
+ : originalNode;
214
+ const node: React.ReactNode = isRenderedCell<RecordType>(result) ? result.children ?? null : result;
215
+ return (
216
+ <SelectionCell
217
+ checked={checked}
218
+ index={index}
219
+ showHandle={showHandleInSelection}
220
+ showIndex={showIndex}
221
+ originalNode={node}
222
+ />
223
+ );
224
+ },
225
+ };
226
+ }, [rowSelection, showHandleInSelection, showIndex]);
227
+
228
+ const tableClassName = cx(
229
+ className,
230
+ showHandleInSelection && selectionGutterClassName,
231
+ showIndex && rowSelection && indexSwapClassName,
232
+ );
233
+
234
+ // antd's `rowKey` accepts `string | GetRowKey<RecordType>`. Our `RowKey`
235
+ // alias is narrower (keyof RecordType for string keys), but TS doesn't infer
236
+ // that `keyof T & string` is assignable to `string` without help — express
237
+ // the narrowing explicitly at the antd boundary.
238
+ const antdRowKey: string | GetRowKey<RecordType> = typeof rowKey === 'function' ? rowKey : String(rowKey);
239
+
240
+ const tableBody = (
241
+ <AntdTable<RecordType>
242
+ {...rest}
243
+ rowKey={antdRowKey}
244
+ dataSource={dataSource}
245
+ columns={augmentedColumns}
246
+ components={tableComponents}
247
+ rowSelection={augmentedRowSelection}
248
+ className={tableClassName}
249
+ pagination={mergedPagination}
250
+ />
251
+ );
252
+
253
+ if (!isDraggable || !onSortEnd || !itemKeys.length) {
254
+ return tableBody;
255
+ }
256
+
257
+ return (
258
+ <DndProvider
259
+ onDragStart={handleDragStart}
260
+ onDragEnd={handleDragEnd}
261
+ onDragCancel={handleDragCancel}
262
+ showDragOverlay={false}
263
+ >
264
+ <SortableContext items={itemKeys} strategy={verticalListSortingStrategy}>
265
+ {tableBody}
266
+ </SortableContext>
267
+ {/* dropAnimation={null}: skip the default tween that snaps the overlay
268
+ back to the source `<tr>` on drop. Our source row hasn't moved yet
269
+ (the server `move` + refetch is async) so the tween reads as the
270
+ row "bouncing back" to its original position, which contradicts the
271
+ successful drop the user just performed. */}
272
+ <DragOverlay dropAnimation={null}>
273
+ {activeSnapshot ? <RowOverlayPreview snapshot={activeSnapshot} /> : null}
274
+ </DragOverlay>
275
+ </DndProvider>
276
+ );
277
+ }
278
+
279
+ export default Table;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ import { render } from '@testing-library/react';
11
+ import React from 'react';
12
+ import { describe, expect, it } from 'vitest';
13
+ import { DEFAULT_PAGE_SIZE, PAGE_SIZE_OPTIONS, Table } from '../Table';
14
+
15
+ type Row = { id: number; name: string };
16
+
17
+ const columns = [{ title: 'Name', dataIndex: 'name' as const }];
18
+
19
+ function makeRows(count: number): Row[] {
20
+ return Array.from({ length: count }, (_, index) => ({ id: index + 1, name: `row-${index + 1}` }));
21
+ }
22
+
23
+ describe('Table pagination defaults', () => {
24
+ it('exports DEFAULT_PAGE_SIZE=50 and the v1-aligned PAGE_SIZE_OPTIONS list', () => {
25
+ expect(DEFAULT_PAGE_SIZE).toBe(50);
26
+ expect([...PAGE_SIZE_OPTIONS]).toEqual([5, 10, 20, 50, 100, 200]);
27
+ });
28
+
29
+ it('shows the page-size changer by default when pagination is enabled', () => {
30
+ const { container } = render(
31
+ <Table<Row>
32
+ rowKey="id"
33
+ columns={columns}
34
+ dataSource={makeRows(120)}
35
+ pagination={{ current: 1, pageSize: 50, total: 120 }}
36
+ />,
37
+ );
38
+ expect(container.querySelector('.ant-pagination-options-size-changer')).not.toBeNull();
39
+ });
40
+
41
+ it('shows the page-size changer when caller omits pagination entirely', () => {
42
+ const { container } = render(<Table<Row> rowKey="id" columns={columns} dataSource={makeRows(120)} />);
43
+ expect(container.querySelector('.ant-pagination-options-size-changer')).not.toBeNull();
44
+ });
45
+
46
+ it('renders no pagination at all when caller passes pagination={false}', () => {
47
+ const { container } = render(
48
+ <Table<Row> rowKey="id" columns={columns} dataSource={makeRows(120)} pagination={false} />,
49
+ );
50
+ expect(container.querySelector('.ant-pagination')).toBeNull();
51
+ });
52
+
53
+ it('lets caller-provided showSizeChanger=false override the default', () => {
54
+ const { container } = render(
55
+ <Table<Row>
56
+ rowKey="id"
57
+ columns={columns}
58
+ dataSource={makeRows(120)}
59
+ pagination={{ current: 1, pageSize: 50, total: 120, showSizeChanger: false }}
60
+ />,
61
+ );
62
+ expect(container.querySelector('.ant-pagination-options-size-changer')).toBeNull();
63
+ });
64
+
65
+ it('preserves caller-controlled current page when caller passes a pagination object', () => {
66
+ const { container } = render(
67
+ <Table<Row>
68
+ rowKey="id"
69
+ columns={columns}
70
+ dataSource={makeRows(120)}
71
+ pagination={{ current: 2, pageSize: 50, total: 120 }}
72
+ />,
73
+ );
74
+ // antd marks the active page with `.ant-pagination-item-active` and the
75
+ // page number lives inside an <a> child — checking the rendered text is
76
+ // the most stable assertion across antd versions.
77
+ const active = container.querySelector('.ant-pagination-item-active');
78
+ expect(active?.textContent).toBe('2');
79
+ });
80
+ });
@@ -0,0 +1,16 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ /**
11
+ * Pixel reservation on the left side of the rowSelection column for the drag
12
+ * handle. Used by the runtime `selectionGutterClassName` (real table) and by
13
+ * the drag overlay `overlayCellStylesClassName` (floating clone) so both
14
+ * variants put the handle in the same gutter position.
15
+ */
16
+ export const SORT_HANDLE_GUTTER = 32;
@@ -0,0 +1,106 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ import { MenuOutlined } from '@ant-design/icons';
11
+ import { TinyColor } from '@ctrl/tinycolor';
12
+ import { useSortable } from '@dnd-kit/sortable';
13
+ import type { SyntheticListenerMap } from '@dnd-kit/core/dist/hooks/utilities';
14
+ import type { DraggableAttributes } from '@dnd-kit/core';
15
+ import { css } from '@emotion/css';
16
+ import { theme } from 'antd';
17
+ import classNames from 'classnames';
18
+ import React, { useMemo } from 'react';
19
+
20
+ type DragSortRowContextValue = {
21
+ attributes?: DraggableAttributes;
22
+ listeners?: SyntheticListenerMap;
23
+ setActivatorNodeRef?: (node: HTMLElement | null) => void;
24
+ };
25
+
26
+ export const DragSortRowContext = React.createContext<DragSortRowContextValue | null>(null);
27
+
28
+ const sortHandleClass = css`
29
+ display: inline-flex;
30
+ align-items: center;
31
+ justify-content: center;
32
+ cursor: grab;
33
+ `;
34
+
35
+ /**
36
+ * Activator handle that initiates a row drag. Reads `attributes` / `listeners`
37
+ * / `setActivatorNodeRef` from the surrounding `DragSortRowContext` provided
38
+ * by `SortableRow`, so the handle can sit anywhere within the row's cells
39
+ * (typically a dedicated first column).
40
+ */
41
+ export const SortHandle: React.FC<{ id?: string | number; style?: React.CSSProperties }> = (props) => {
42
+ const { id: _id, ...otherProps } = props;
43
+ const dragSortContext = React.useContext(DragSortRowContext);
44
+ return (
45
+ <span
46
+ ref={dragSortContext?.setActivatorNodeRef}
47
+ {...dragSortContext?.attributes}
48
+ {...dragSortContext?.listeners}
49
+ {...otherProps}
50
+ className={classNames(sortHandleClass)}
51
+ >
52
+ <MenuOutlined />
53
+ </span>
54
+ );
55
+ };
56
+
57
+ /**
58
+ * Drop-in replacement for antd Table's `<tr>` body row that wires `useSortable`
59
+ * keyed by the `data-row-key` attribute antd injects. Pass via
60
+ * `Table.components.body.row` and wrap the `<tbody>` with `DndContext` +
61
+ * `SortableContext`. The `DragSortRowContext` it provides lets `SortHandle`
62
+ * be placed anywhere inside the row, not just on the row itself.
63
+ */
64
+ export const SortableRow: React.FC<{
65
+ rowIndex?: number;
66
+ className?: string;
67
+ [key: string]: any;
68
+ }> = (props) => {
69
+ const { token }: any = theme.useToken();
70
+ const id = props['data-row-key']?.toString();
71
+ const { setNodeRef, setActivatorNodeRef, attributes, listeners, active, over } = useSortable({
72
+ id,
73
+ });
74
+ const { rowIndex, ...others } = props;
75
+ const isOver = over?.id === id;
76
+ const classObj = useMemo(() => {
77
+ const borderColor = new TinyColor(token.colorPrimary).setAlpha(0.6).toHex8String();
78
+ return {
79
+ topActiveClass: css`
80
+ & > td {
81
+ border-top: 2px solid ${borderColor} !important;
82
+ }
83
+ `,
84
+ bottomActiveClass: css`
85
+ & > td {
86
+ border-bottom: 2px solid ${borderColor} !important;
87
+ }
88
+ `,
89
+ };
90
+ }, [token.colorPrimary]);
91
+
92
+ const className =
93
+ (active?.data.current?.sortable.index ?? -1) > rowIndex ? classObj.topActiveClass : classObj.bottomActiveClass;
94
+
95
+ return (
96
+ <DragSortRowContext.Provider value={{ listeners, attributes, setActivatorNodeRef }}>
97
+ <tr
98
+ ref={(node) => {
99
+ setNodeRef(node);
100
+ }}
101
+ {...others}
102
+ className={classNames(props.className, { [className]: active && isOver })}
103
+ />
104
+ </DragSortRowContext.Provider>
105
+ );
106
+ };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ export * from './SortableRow';
@@ -0,0 +1,13 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ // Public surface of the v2 table primitive. Internal helpers (utils, styles,
11
+ // SelectionCell, RowOverlayPreview, etc.) stay unexported — they're
12
+ // implementation details of `Table` and are not part of the package API.
13
+ export * from './Table';
@@ -0,0 +1,110 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ import { css } from '@emotion/css';
11
+ import { SORT_HANDLE_GUTTER } from './constants';
12
+
13
+ /**
14
+ * Reserve a `SORT_HANDLE_GUTTER`-wide gap on the left of the rowSelection
15
+ * column so the handle's `left:0` lands inside a `position:relative` cell.
16
+ * Padding is mirrored on both the header `<th>` and body `<td>` so the
17
+ * "select all" checkbox stays vertically aligned with body checkboxes.
18
+ *
19
+ * The class is a module-level constant — emotion's hash is stable across
20
+ * re-renders, so the caller doesn't need a `useMemo` to keep referential
21
+ * equality.
22
+ */
23
+ export const selectionGutterClassName = css`
24
+ .ant-table-thead > tr > th.ant-table-selection-column,
25
+ .ant-table-tbody > tr > td.ant-table-selection-column {
26
+ padding-left: ${SORT_HANDLE_GUTTER}px !important;
27
+ position: relative;
28
+ }
29
+ `;
30
+
31
+ /**
32
+ * Index ↔ checkbox hover swap CSS. Both `.nb-table-index` and the antd
33
+ * checkbox (wrapped in `.nb-origin-node`) are absolutely positioned and
34
+ * centered inside `.nb-row-selection-cell`, so they share the same anchor
35
+ * without one displacing the other. `display: none/flex` flips so they never
36
+ * overlap mid-transition.
37
+ */
38
+ export const indexSwapClassName = css`
39
+ .ant-table-tbody > tr > td.ant-table-selection-column {
40
+ .nb-row-selection-cell {
41
+ position: relative;
42
+ display: inline-block;
43
+ min-width: 22px;
44
+ min-height: 22px;
45
+ vertical-align: middle;
46
+ }
47
+ .nb-row-selection-cell .nb-table-index,
48
+ .nb-row-selection-cell .nb-origin-node {
49
+ position: absolute;
50
+ inset: 0;
51
+ display: flex;
52
+ align-items: center;
53
+ justify-content: center;
54
+ }
55
+ .nb-row-selection-cell .nb-origin-node {
56
+ display: none;
57
+ }
58
+ .nb-row-selection-cell.checked .nb-table-index {
59
+ display: none;
60
+ }
61
+ .nb-row-selection-cell.checked .nb-origin-node {
62
+ display: flex;
63
+ }
64
+ }
65
+ .ant-table-tbody > tr:hover > td.ant-table-selection-column {
66
+ .nb-row-selection-cell .nb-table-index {
67
+ display: none;
68
+ }
69
+ .nb-row-selection-cell .nb-origin-node {
70
+ display: flex;
71
+ }
72
+ }
73
+ `;
74
+
75
+ /**
76
+ * Self-contained styles for the drag-overlay clone. The runtime
77
+ * `selectionGutterClassName` + `indexSwapClassName` are scoped to AntdTable's
78
+ * emotion hash, so they don't reach the cloned `<tr>` injected via
79
+ * `dangerouslySetInnerHTML`. This class replays the minimal subset that the
80
+ * clone needs:
81
+ * - selection column gutter + `position: relative` so the absolute handle
82
+ * lands correctly
83
+ * - hide the row index inside the overlay (drag preview shouldn't carry
84
+ * a stale ordinal)
85
+ * - force the checkbox (`.nb-origin-node`) absolute-centered and visible
86
+ * regardless of hover/checked state, so the selection cell isn't empty
87
+ */
88
+ export const overlayCellStylesClassName = css`
89
+ .ant-table-cell.ant-table-selection-column {
90
+ padding-left: ${SORT_HANDLE_GUTTER}px !important;
91
+ position: relative;
92
+ }
93
+ .nb-table-index {
94
+ display: none !important;
95
+ }
96
+ .nb-row-selection-cell {
97
+ position: relative;
98
+ display: inline-block;
99
+ min-width: 22px;
100
+ min-height: 22px;
101
+ vertical-align: middle;
102
+ }
103
+ .nb-row-selection-cell .nb-origin-node {
104
+ position: absolute;
105
+ inset: 0;
106
+ display: flex !important;
107
+ align-items: center;
108
+ justify-content: center;
109
+ }
110
+ `;