@nocobase/client-v2 3.0.0-alpha.5 → 3.0.0-alpha.7
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/es/flow/models/actions/UpdateRecordActionUtils.d.ts +8 -2
- package/es/flow/models/blocks/table/JSColumnModel.d.ts +2 -0
- package/es/index.mjs +87 -78
- package/lib/index.js +93 -84
- package/package.json +7 -7
- package/src/__tests__/app.test.tsx +110 -0
- package/src/__tests__/browserChecker.test.ts +37 -0
- package/src/__tests__/settings-shell.test.tsx +27 -1
- package/src/components/AppComponents.tsx +16 -7
- package/src/components/form/table/Table.tsx +81 -6
- package/src/components/form/table/__tests__/Table.columnWidth.test.tsx +433 -0
- package/src/flow/models/actions/UpdateRecordActionModel.tsx +18 -1
- package/src/flow/models/actions/UpdateRecordActionUtils.ts +18 -6
- package/src/flow/models/actions/__tests__/UpdateRecordActionModel.test.ts +76 -4
- package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +8 -2
- package/src/flow/models/blocks/table/JSColumnModel.tsx +10 -1
- package/src/flow/models/blocks/table/__tests__/JSColumnModel.test.tsx +52 -5
- package/src/flow/models/fields/AssociationFieldModel/__tests__/RecordPickerFieldModel.itemContext.test.ts +46 -0
- package/src/settings-app/SettingsShell.tsx +19 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/client-v2",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.7",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"module": "es/index.mjs",
|
|
@@ -27,11 +27,11 @@
|
|
|
27
27
|
"@formily/antd-v5": "1.2.3",
|
|
28
28
|
"@formily/react": "^2.2.27",
|
|
29
29
|
"@formily/shared": "^2.2.27",
|
|
30
|
-
"@nocobase/evaluators": "3.0.0-alpha.
|
|
31
|
-
"@nocobase/flow-engine": "3.0.0-alpha.
|
|
32
|
-
"@nocobase/sdk": "3.0.0-alpha.
|
|
33
|
-
"@nocobase/shared": "3.0.0-alpha.
|
|
34
|
-
"@nocobase/utils": "3.0.0-alpha.
|
|
30
|
+
"@nocobase/evaluators": "3.0.0-alpha.7",
|
|
31
|
+
"@nocobase/flow-engine": "3.0.0-alpha.7",
|
|
32
|
+
"@nocobase/sdk": "3.0.0-alpha.7",
|
|
33
|
+
"@nocobase/shared": "3.0.0-alpha.7",
|
|
34
|
+
"@nocobase/utils": "3.0.0-alpha.7",
|
|
35
35
|
"ahooks": "^3.7.2",
|
|
36
36
|
"antd": "5.24.2",
|
|
37
37
|
"antd-style": "3.7.1",
|
|
@@ -48,5 +48,5 @@
|
|
|
48
48
|
"react-i18next": "^11.15.1",
|
|
49
49
|
"react-router-dom": "^6.30.1"
|
|
50
50
|
},
|
|
51
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "19cd28c710f3b3c614ebe2fd7d5a415d527ee559"
|
|
52
52
|
}
|
|
@@ -406,6 +406,116 @@ describe('app', () => {
|
|
|
406
406
|
expect(screen.getByText('Enabling plugin')).toBeInTheDocument();
|
|
407
407
|
});
|
|
408
408
|
|
|
409
|
+
it('should keep current content without a dialog while app upgrading', async () => {
|
|
410
|
+
const CurrentPage = () => <div>Current page</div>;
|
|
411
|
+
|
|
412
|
+
class PluginHelloClient extends Plugin {
|
|
413
|
+
async load() {
|
|
414
|
+
this.router.add('root', { path: '/', Component: CurrentPage });
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const app = createMockClient({ plugins: [PluginHelloClient] });
|
|
419
|
+
await renderApp(app);
|
|
420
|
+
|
|
421
|
+
act(() => {
|
|
422
|
+
app.maintained = true;
|
|
423
|
+
app.maintaining = true;
|
|
424
|
+
app.error = Object.assign(new Error('Loading data sources...'), {
|
|
425
|
+
code: 'APP_COMMANDING',
|
|
426
|
+
command: { name: 'upgrade' },
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
expect(screen.getByText('Current page')).toBeInTheDocument();
|
|
431
|
+
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
it('should not show Try again for an app upgrade error state', async () => {
|
|
435
|
+
class PluginHelloClient extends Plugin {}
|
|
436
|
+
const app = createMockClient({ plugins: [PluginHelloClient] });
|
|
437
|
+
app.error = Object.assign(new Error('Loading data sources...'), {
|
|
438
|
+
code: 'APP_COMMANDING',
|
|
439
|
+
command: { name: 'upgrade' },
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
await renderApp(app);
|
|
443
|
+
|
|
444
|
+
expect(screen.getByText('App upgrading')).toBeInTheDocument();
|
|
445
|
+
expect(screen.queryByRole('button', { name: 'Try again' })).not.toBeInTheDocument();
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
it('should clear upgrade feedback after an existing page error', async () => {
|
|
449
|
+
const BrokenPage = () => {
|
|
450
|
+
throw new Error('page error');
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
class PluginHelloClient extends Plugin {
|
|
454
|
+
async load() {
|
|
455
|
+
this.router.add('root', { path: '/', Component: BrokenPage });
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const app = createMockClient({
|
|
460
|
+
plugins: [PluginHelloClient],
|
|
461
|
+
ws: { url: 'ws://localhost:3000/ws' },
|
|
462
|
+
});
|
|
463
|
+
app.maintained = true;
|
|
464
|
+
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
465
|
+
try {
|
|
466
|
+
await renderApp(app);
|
|
467
|
+
expect(screen.getByRole('button', { name: 'Try again' })).toBeInTheDocument();
|
|
468
|
+
|
|
469
|
+
act(() => {
|
|
470
|
+
app.ws.emit('message', {
|
|
471
|
+
type: 'maintaining',
|
|
472
|
+
payload: {
|
|
473
|
+
code: 'APP_COMMANDING',
|
|
474
|
+
command: { name: 'upgrade' },
|
|
475
|
+
message: 'Loading data sources...',
|
|
476
|
+
},
|
|
477
|
+
});
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
expect(screen.getByText('App upgrading')).toBeInTheDocument();
|
|
481
|
+
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
|
482
|
+
expect(screen.queryByRole('button', { name: 'Try again' })).not.toBeInTheDocument();
|
|
483
|
+
} finally {
|
|
484
|
+
errorSpy.mockRestore();
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
it('should keep a custom maintaining dialog while app upgrading', async () => {
|
|
489
|
+
class PluginHelloClient extends Plugin {}
|
|
490
|
+
const app = createMockClient({ plugins: [PluginHelloClient] });
|
|
491
|
+
app.addComponents({
|
|
492
|
+
CustomUpgradeStatus: () => <div>Custom upgrade status</div>,
|
|
493
|
+
});
|
|
494
|
+
app.maintained = true;
|
|
495
|
+
app.maintaining = true;
|
|
496
|
+
app.error = Object.assign(new Error('Loading data sources...'), {
|
|
497
|
+
code: 'APP_COMMANDING',
|
|
498
|
+
command: {
|
|
499
|
+
name: 'upgrade',
|
|
500
|
+
components: { maintainingDialog: 'CustomUpgradeStatus' },
|
|
501
|
+
},
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
await renderApp(app);
|
|
505
|
+
|
|
506
|
+
expect(screen.getByText('Custom upgrade status')).toBeInTheDocument();
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
it('should keep Try again for recoverable app errors', async () => {
|
|
510
|
+
class PluginHelloClient extends Plugin {}
|
|
511
|
+
const app = createMockClient({ plugins: [PluginHelloClient] });
|
|
512
|
+
app.error = Object.assign(new Error('load error'), { code: 'LOAD_ERROR' });
|
|
513
|
+
|
|
514
|
+
await renderApp(app);
|
|
515
|
+
|
|
516
|
+
expect(screen.getByRole('button', { name: 'Try again' })).toBeInTheDocument();
|
|
517
|
+
});
|
|
518
|
+
|
|
409
519
|
it('should handle long loading state gracefully', async () => {
|
|
410
520
|
class PluginHelloClient extends Plugin {
|
|
411
521
|
async load() {
|
|
@@ -147,6 +147,19 @@ describe.each(browserCheckerCases)('$label', ({ scriptPath }) => {
|
|
|
147
147
|
expect(replace).toHaveBeenCalledWith('http://c.local.nocobase.com/v/');
|
|
148
148
|
});
|
|
149
149
|
|
|
150
|
+
it.each(['/', '/index.html'])('redirects app root entry %s to Settings for settings-default', (pathname) => {
|
|
151
|
+
const replace = executeBrowserChecker(scriptPath, {
|
|
152
|
+
pathname,
|
|
153
|
+
publicPath: '/',
|
|
154
|
+
modernClientPrefix: 'v',
|
|
155
|
+
appClientEntryMode: 'settings-default',
|
|
156
|
+
search: '?from=entry',
|
|
157
|
+
hash: '#panel',
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
expect(replace).toHaveBeenCalledWith('http://c.local.nocobase.com/settings/?from=entry#panel');
|
|
161
|
+
});
|
|
162
|
+
|
|
150
163
|
it('does not redirect legacy deep links for modern-default', () => {
|
|
151
164
|
const replace = executeBrowserChecker(scriptPath, {
|
|
152
165
|
pathname: '/admin',
|
|
@@ -158,6 +171,17 @@ describe.each(browserCheckerCases)('$label', ({ scriptPath }) => {
|
|
|
158
171
|
expect(replace).not.toHaveBeenCalled();
|
|
159
172
|
});
|
|
160
173
|
|
|
174
|
+
it('does not redirect legacy deep links for settings-default', () => {
|
|
175
|
+
const replace = executeBrowserChecker(scriptPath, {
|
|
176
|
+
pathname: '/admin',
|
|
177
|
+
publicPath: '/',
|
|
178
|
+
modernClientPrefix: 'v',
|
|
179
|
+
appClientEntryMode: 'settings-default',
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
expect(replace).not.toHaveBeenCalled();
|
|
183
|
+
});
|
|
184
|
+
|
|
161
185
|
it('rewrites legacy document paths for modern-only', () => {
|
|
162
186
|
const replace = executeBrowserChecker(scriptPath, {
|
|
163
187
|
pathname: '/admin/settings/workflow',
|
|
@@ -180,6 +204,19 @@ describe.each(browserCheckerCases)('$label', ({ scriptPath }) => {
|
|
|
180
204
|
expect(replace).toHaveBeenCalledWith('http://c.local.nocobase.com/nocobase/v/');
|
|
181
205
|
});
|
|
182
206
|
|
|
207
|
+
it('redirects a sub-path app root directly to Settings for settings-default', () => {
|
|
208
|
+
const replace = executeBrowserChecker(scriptPath, {
|
|
209
|
+
pathname: '/nocobase/',
|
|
210
|
+
publicPath: '/nocobase/',
|
|
211
|
+
modernClientPrefix: 'v',
|
|
212
|
+
appClientEntryMode: 'settings-default',
|
|
213
|
+
search: '?from=entry',
|
|
214
|
+
hash: '#panel',
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
expect(replace).toHaveBeenCalledWith('http://c.local.nocobase.com/nocobase/settings/?from=entry#panel');
|
|
218
|
+
});
|
|
219
|
+
|
|
183
220
|
it('rewrites sub-app legacy deep links for modern-only without collapsing the sub-app segment', () => {
|
|
184
221
|
const replace = executeBrowserChecker(scriptPath, {
|
|
185
222
|
pathname: '/nocobase/apps/a_31itq60q4kg/admin/',
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { render, screen } from '@testing-library/react';
|
|
11
|
-
import { ConfigProvider } from 'antd';
|
|
11
|
+
import { ConfigProvider, theme as antdTheme } from 'antd';
|
|
12
12
|
import React from 'react';
|
|
13
13
|
import { MemoryRouter } from 'react-router-dom';
|
|
14
14
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
@@ -19,6 +19,7 @@ import type { ThemeConfig } from '../theme';
|
|
|
19
19
|
const userCenterModel = { uid: 'settings-user-center' };
|
|
20
20
|
const createModel = vi.fn(() => userCenterModel);
|
|
21
21
|
const matchRoutes = vi.fn(() => [{ route: { id: 'settings' } }]);
|
|
22
|
+
let settingsThemeConfig: ThemeConfig | null = null;
|
|
22
23
|
const mockApp = {
|
|
23
24
|
flowEngine: {
|
|
24
25
|
createModel,
|
|
@@ -46,6 +47,10 @@ vi.mock('../settings-app/SettingsSearch', () => ({
|
|
|
46
47
|
SettingsSearch: () => <div data-testid="settings-search">search</div>,
|
|
47
48
|
}));
|
|
48
49
|
|
|
50
|
+
vi.mock('../settings-app/useSettingsThemeConfig', () => ({
|
|
51
|
+
useSettingsThemeConfig: () => settingsThemeConfig,
|
|
52
|
+
}));
|
|
53
|
+
|
|
49
54
|
vi.mock('../flow/admin-shell/admin-layout/HelpLite', () => ({
|
|
50
55
|
HelpLite: () => <div data-testid="settings-help">help</div>,
|
|
51
56
|
}));
|
|
@@ -60,8 +65,15 @@ vi.mock('@nocobase/flow-engine', async (importOriginal) => {
|
|
|
60
65
|
};
|
|
61
66
|
});
|
|
62
67
|
|
|
68
|
+
const TokenProbe = () => {
|
|
69
|
+
const { token } = antdTheme.useToken();
|
|
70
|
+
|
|
71
|
+
return <div data-testid="settings-theme-token">{`${token.marginBlock}:${token.sizeLG}`}</div>;
|
|
72
|
+
};
|
|
73
|
+
|
|
63
74
|
describe('SettingsShell', () => {
|
|
64
75
|
beforeEach(() => {
|
|
76
|
+
settingsThemeConfig = null;
|
|
65
77
|
createModel.mockClear();
|
|
66
78
|
matchRoutes.mockReset();
|
|
67
79
|
matchRoutes.mockReturnValue([{ route: { id: 'settings' } }]);
|
|
@@ -111,6 +123,20 @@ describe('SettingsShell', () => {
|
|
|
111
123
|
expect(screen.getByRole('banner')).toHaveStyle({ background: '#ffffff' });
|
|
112
124
|
});
|
|
113
125
|
|
|
126
|
+
it('preserves NocoBase custom tokens in the stored compact theme', () => {
|
|
127
|
+
settingsThemeConfig = { algorithm: antdTheme.compactAlgorithm };
|
|
128
|
+
|
|
129
|
+
render(
|
|
130
|
+
<MemoryRouter initialEntries={['/settings/system-settings']}>
|
|
131
|
+
<SettingsShell>
|
|
132
|
+
<TokenProbe />
|
|
133
|
+
</SettingsShell>
|
|
134
|
+
</MemoryRouter>,
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
expect(screen.getByTestId('settings-theme-token')).toHaveTextContent('16:16');
|
|
138
|
+
});
|
|
139
|
+
|
|
114
140
|
it('places the settings content and embed container side by side below the header', () => {
|
|
115
141
|
const { container } = render(
|
|
116
142
|
<MemoryRouter initialEntries={['/settings/theme-editor']}>
|
|
@@ -28,9 +28,11 @@ interface AppErrorPayload {
|
|
|
28
28
|
maintainingDialog?: string;
|
|
29
29
|
};
|
|
30
30
|
};
|
|
31
|
-
[key: string]: any;
|
|
32
31
|
}
|
|
33
32
|
|
|
33
|
+
const isAppUpgrading = (error?: AppErrorPayload) =>
|
|
34
|
+
error?.code === 'APP_COMMANDING' && error.command?.name === 'upgrade';
|
|
35
|
+
|
|
34
36
|
export const AppSpin = () => {
|
|
35
37
|
return (
|
|
36
38
|
<Spin style={{ position: 'fixed', top: '50%', left: '50%', fontSize: 72, transform: 'translate(-50%, -50%)' }} />
|
|
@@ -168,6 +170,7 @@ const getProps = (app: Application) => {
|
|
|
168
170
|
export const AppError: FC<{ error: Error & { title?: string }; app: Application }> = observer(
|
|
169
171
|
({ app, error }) => {
|
|
170
172
|
const props = getProps(app);
|
|
173
|
+
const upgrading = isAppUpgrading(app.error as AppErrorPayload | undefined);
|
|
171
174
|
return (
|
|
172
175
|
<div>
|
|
173
176
|
<Result
|
|
@@ -180,11 +183,13 @@ export const AppError: FC<{ error: Error & { title?: string }; app: Application
|
|
|
180
183
|
status="error"
|
|
181
184
|
title={error?.title || app.i18n.t('App error', { ns: 'client' })}
|
|
182
185
|
subTitle={app.i18n.t(error?.message)}
|
|
183
|
-
extra={
|
|
184
|
-
|
|
185
|
-
{
|
|
186
|
-
|
|
187
|
-
|
|
186
|
+
extra={
|
|
187
|
+
upgrading ? null : (
|
|
188
|
+
<Button type="primary" key="try" onClick={() => window.location.reload()}>
|
|
189
|
+
{app.i18n.t('Try again')}
|
|
190
|
+
</Button>
|
|
191
|
+
)
|
|
192
|
+
}
|
|
188
193
|
{...props}
|
|
189
194
|
/>
|
|
190
195
|
</div>
|
|
@@ -223,10 +228,14 @@ export const AppMaintaining: FC<{ app: Application; error: Error }> = observer(
|
|
|
223
228
|
|
|
224
229
|
export const AppMaintainingDialog: FC<{ app: Application; error: Error }> = observer(
|
|
225
230
|
({ app, error }) => {
|
|
226
|
-
const
|
|
231
|
+
const payload = error as AppErrorPayload | undefined;
|
|
232
|
+
const component = payload?.command?.components?.maintainingDialog;
|
|
227
233
|
if (component) {
|
|
228
234
|
return app.renderComponent(component, { app, error });
|
|
229
235
|
}
|
|
236
|
+
if (isAppUpgrading(payload)) {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
230
239
|
|
|
231
240
|
const { icon, status, title, subTitle } = getProps(app);
|
|
232
241
|
return (
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
|
|
10
10
|
import { DragOverlay, type DragEndEvent, type DragStartEvent } from '@dnd-kit/core';
|
|
11
11
|
import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
|
|
12
|
-
import { cx } from '@emotion/css';
|
|
12
|
+
import { css, cx, injectGlobal } from '@emotion/css';
|
|
13
13
|
import { DndProvider } from '@nocobase/flow-engine';
|
|
14
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';
|
|
15
|
+
import { Table as AntdTable, theme, type TableProps as AntdTableProps } from 'antd';
|
|
16
|
+
import type { ColumnsType, ColumnGroupType, ColumnType, GetRowKey } from 'antd/es/table/interface';
|
|
17
17
|
import type { RenderedCell } from 'rc-table/lib/interface';
|
|
18
18
|
import React, { useMemo, useState } from 'react';
|
|
19
19
|
import { SortableRow, SortHandle } from './dnd/SortableRow';
|
|
@@ -24,6 +24,20 @@ import { readRowKey, snapshotSourceRow, type RowKey, type RowSnapshot } from './
|
|
|
24
24
|
|
|
25
25
|
type RowSelectionRenderCellResult<RecordType> = React.ReactNode | RenderedCell<RecordType>;
|
|
26
26
|
|
|
27
|
+
const DEFAULT_COLUMN_CONTENT_CLASS_NAME = 'nb-table-default-column-content';
|
|
28
|
+
const DEFAULT_COLUMN_CONTENT_MAX_WIDTH_PROPERTY = '--nb-table-default-column-content-max-width';
|
|
29
|
+
|
|
30
|
+
// Zero specificity lets caller classes remain authoritative regardless of stylesheet insertion order. The max-width
|
|
31
|
+
// value itself comes from a token-derived private CSS variable set by each Table instance.
|
|
32
|
+
injectGlobal`
|
|
33
|
+
:where(.${DEFAULT_COLUMN_CONTENT_CLASS_NAME}) {
|
|
34
|
+
max-width: var(${DEFAULT_COLUMN_CONTENT_MAX_WIDTH_PROPERTY});
|
|
35
|
+
white-space: normal;
|
|
36
|
+
overflow-wrap: anywhere;
|
|
37
|
+
word-break: break-word;
|
|
38
|
+
}
|
|
39
|
+
`;
|
|
40
|
+
|
|
27
41
|
/**
|
|
28
42
|
* Default initial page size for `Table`. Exposed so consumers can seed their
|
|
29
43
|
* controlled `pageSize` state with the same value the component would use if
|
|
@@ -50,6 +64,54 @@ function isRenderedCell<RecordType>(value: unknown): value is RenderedCell<Recor
|
|
|
50
64
|
return typeof value === 'object' && value !== null && !React.isValidElement(value) && 'children' in value;
|
|
51
65
|
}
|
|
52
66
|
|
|
67
|
+
function isColumnGroup<RecordType>(
|
|
68
|
+
column: ColumnGroupType<RecordType> | ColumnType<RecordType>,
|
|
69
|
+
): column is ColumnGroupType<RecordType> {
|
|
70
|
+
return 'children' in column && Array.isArray(column.children) && column.children.length > 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function addDefaultColumnContentClassName<RecordType>(
|
|
74
|
+
columns: ColumnsType<RecordType>,
|
|
75
|
+
defaultClassName: string,
|
|
76
|
+
): ColumnsType<RecordType> {
|
|
77
|
+
return columns.map((column) => {
|
|
78
|
+
if (column === AntdTable.EXPAND_COLUMN || column === AntdTable.SELECTION_COLUMN) {
|
|
79
|
+
return column;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (isColumnGroup(column)) {
|
|
83
|
+
return {
|
|
84
|
+
...column,
|
|
85
|
+
children: addDefaultColumnContentClassName(column.children, defaultClassName),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (column.width !== undefined || column.ellipsis || column.fixed) {
|
|
90
|
+
return column;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const originalOnCell = column.onCell;
|
|
94
|
+
const originalOnHeaderCell = column.onHeaderCell;
|
|
95
|
+
return {
|
|
96
|
+
...column,
|
|
97
|
+
onCell: (record, index) => {
|
|
98
|
+
const cellProps = originalOnCell?.(record, index) ?? {};
|
|
99
|
+
return {
|
|
100
|
+
...cellProps,
|
|
101
|
+
className: cx(defaultClassName, cellProps.className),
|
|
102
|
+
};
|
|
103
|
+
},
|
|
104
|
+
onHeaderCell: (headerColumn) => {
|
|
105
|
+
const cellProps = originalOnHeaderCell?.(headerColumn) ?? {};
|
|
106
|
+
return {
|
|
107
|
+
...cellProps,
|
|
108
|
+
className: cx(defaultClassName, cellProps.className),
|
|
109
|
+
};
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
53
115
|
export interface TableProps<RecordType extends object = any> extends AntdTableProps<RecordType> {
|
|
54
116
|
/**
|
|
55
117
|
* Required so drag-sort, hover-swap and row identity work. Accepts the same
|
|
@@ -108,6 +170,7 @@ export interface TableProps<RecordType extends object = any> extends AntdTablePr
|
|
|
108
170
|
* plus the index swap, so it is safe as the default table on any page.
|
|
109
171
|
*/
|
|
110
172
|
export function Table<RecordType extends object = any>(props: TableProps<RecordType>) {
|
|
173
|
+
const { token } = theme.useToken();
|
|
111
174
|
const {
|
|
112
175
|
rowKey,
|
|
113
176
|
showIndex = true,
|
|
@@ -143,6 +206,17 @@ export function Table<RecordType extends object = any>(props: TableProps<RecordT
|
|
|
143
206
|
const showHandleInSelection = isDraggable && showSortHandle && !!rowSelection;
|
|
144
207
|
const showStandaloneHandleColumn = isDraggable && showSortHandle && !rowSelection;
|
|
145
208
|
|
|
209
|
+
const defaultColumnContentClassName = useMemo(
|
|
210
|
+
() =>
|
|
211
|
+
cx(
|
|
212
|
+
DEFAULT_COLUMN_CONTENT_CLASS_NAME,
|
|
213
|
+
css`
|
|
214
|
+
${DEFAULT_COLUMN_CONTENT_MAX_WIDTH_PROPERTY}: ${token.screenXS - token.paddingXL * 3 + token.padding * 2}px;
|
|
215
|
+
`,
|
|
216
|
+
),
|
|
217
|
+
[token.padding, token.paddingXL, token.screenXS],
|
|
218
|
+
);
|
|
219
|
+
|
|
146
220
|
const itemKeys = useMemo<string[]>(() => {
|
|
147
221
|
if (!isDraggable || !dataSource) return [];
|
|
148
222
|
return dataSource
|
|
@@ -191,15 +265,16 @@ export function Table<RecordType extends object = any>(props: TableProps<RecordT
|
|
|
191
265
|
// selection cell — see `augmentedRowSelection` below.
|
|
192
266
|
const augmentedColumns = useMemo<ColumnsType<RecordType>>(() => {
|
|
193
267
|
const baseColumns: ColumnsType<RecordType> = columns ?? [];
|
|
194
|
-
|
|
268
|
+
const styledColumns = addDefaultColumnContentClassName(baseColumns, defaultColumnContentClassName);
|
|
269
|
+
if (!showStandaloneHandleColumn) return styledColumns;
|
|
195
270
|
const handleColumn: ColumnType<RecordType> = {
|
|
196
271
|
key: '__sort__',
|
|
197
272
|
width: sortHandleColumnWidth,
|
|
198
273
|
align: 'center',
|
|
199
274
|
render: () => <SortHandle />,
|
|
200
275
|
};
|
|
201
|
-
return [handleColumn, ...
|
|
202
|
-
}, [columns, showStandaloneHandleColumn, sortHandleColumnWidth]);
|
|
276
|
+
return [handleColumn, ...styledColumns];
|
|
277
|
+
}, [columns, defaultColumnContentClassName, showStandaloneHandleColumn, sortHandleColumnWidth]);
|
|
203
278
|
|
|
204
279
|
const augmentedRowSelection = useMemo(() => {
|
|
205
280
|
if (!rowSelection) return rowSelection;
|