@kne/react-modal 0.1.1 → 0.1.4
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/README.md +763 -334
- package/dist/index.css +582 -0
- package/dist/index.css.map +1 -1
- package/dist/index.js +864 -62
- package/dist/index.js.map +1 -1
- package/dist/index.modern.js +858 -71
- package/dist/index.modern.js.map +1 -1
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
### 描述
|
|
4
4
|
|
|
5
|
-
基于 antd 的 React
|
|
5
|
+
基于 antd 的 React 弹层组件,提供 Modal/Drawer、useModal/useDrawer/useConfirmModal、共用 TabsLayout/ScrollRegion 布局与 createModalRender/createDrawerRender,支持 SimpleBar 与移动端适配。
|
|
6
6
|
|
|
7
7
|
### 关键词
|
|
8
8
|
|
|
9
|
-
react, modal, antd, dialog, simplebar, use-modal, footer-buttons, tabs-layout, mobile, form, component, i18n
|
|
9
|
+
react, modal, drawer, antd, dialog, simplebar, use-modal, use-drawer, use-confirm-modal, footer-buttons, tabs-layout, mobile, form, component, i18n
|
|
10
10
|
|
|
11
11
|
### 安装
|
|
12
12
|
|
|
@@ -18,20 +18,21 @@ npm i --save @kne/react-modal
|
|
|
18
18
|
|
|
19
19
|
#### 概述
|
|
20
20
|
|
|
21
|
-
`@kne/react-modal` 是基于 Ant Design
|
|
21
|
+
`@kne/react-modal` 是基于 Ant Design 的弹层组件库,提供声明式 / 命令式 **Modal** 与 **Drawer**,兼容 `footerButtons` 体系,内置 SimpleBar 滚动与移动端适配。
|
|
22
22
|
|
|
23
23
|
#### 主要特性
|
|
24
24
|
|
|
25
|
-
- 声明式 / 命令式同一套 props 与 UI
|
|
26
|
-
- `footer` + `footerButtons`(`ButtonComponent` / `display` / `autoClose
|
|
25
|
+
- 声明式 / 命令式同一套 props 与 UI(Modal `useModal`;Drawer `useDrawer` + `DrawerContextHolder`;确认框 `useConfirmModal`)
|
|
26
|
+
- `footer` + `footerButtons`(`ButtonComponent` / `display` / `autoClose`)
|
|
27
27
|
- title / footer 固定在滚动外,body 默认 SimpleBar
|
|
28
|
-
- `--kne-modal-*`
|
|
29
|
-
-
|
|
30
|
-
-
|
|
28
|
+
- `--kne-modal-*` / `--kne-drawer-*` CSS 变量管理高度链
|
|
29
|
+
- Modal 移动端全屏;Drawer 移动端侧滑全宽
|
|
30
|
+
- **示例**:各场景示例顶部提供 **Modal / Drawer** 切换,同一套 props 与内容对比两种弹层
|
|
31
|
+
- **共用布局**:`TabsLayout`、`ColumnsLayout`、`ScrollRegion`;`createModalRender` / `createDrawerRender` 对接 renderModal 宿主
|
|
31
32
|
|
|
32
33
|
#### 使用场景
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
列表快览、表单确认、侧滑详情、Tabs / 分栏复杂内容区均可通过布局组件组合,无需业务侧手写 SCSS。
|
|
35
36
|
|
|
36
37
|
|
|
37
38
|
### 示例
|
|
@@ -225,65 +226,99 @@ npm i --save @kne/react-modal
|
|
|
225
226
|
|
|
226
227
|
#### 示例代码
|
|
227
228
|
|
|
228
|
-
-
|
|
229
|
-
-
|
|
229
|
+
- 基础弹层
|
|
230
|
+
- Modal / Drawer 切换:受控打开与异步 onConfirm(loading / 成功提示)
|
|
230
231
|
- _ReactModal(@kne/current-lib_react-modal)[import * as _ReactModal from "@kne/react-modal"],(@kne/current-lib_react-modal/dist/index.css),antd(antd)
|
|
231
232
|
|
|
232
233
|
```jsx
|
|
233
|
-
const { default: Modal } = _ReactModal;
|
|
234
|
-
const { Button, Space, message,
|
|
235
|
-
const { useState } = React;
|
|
234
|
+
const { default: Modal, Drawer, DrawerContextHolder } = _ReactModal;
|
|
235
|
+
const { Button, Space, message, Typography, Radio, App } = antd;
|
|
236
|
+
const { useState, useEffect } = React;
|
|
236
237
|
|
|
237
238
|
const { Text, Paragraph } = Typography;
|
|
238
239
|
|
|
239
240
|
const BasicExample = () => {
|
|
240
241
|
const [open, setOpen] = useState(false);
|
|
242
|
+
const [mode, setMode] = useState('modal');
|
|
243
|
+
const isDrawer = mode === 'drawer';
|
|
244
|
+
|
|
245
|
+
useEffect(() => {
|
|
246
|
+
if (open) {
|
|
247
|
+
setOpen(false);
|
|
248
|
+
}
|
|
249
|
+
}, [mode]);
|
|
250
|
+
|
|
251
|
+
const overlayProps = {
|
|
252
|
+
title: '保存评估备注',
|
|
253
|
+
open,
|
|
254
|
+
onClose: () => setOpen(false),
|
|
255
|
+
onConfirm: async () => {
|
|
256
|
+
await new Promise(resolve => setTimeout(resolve, 800));
|
|
257
|
+
message.success('备注已保存至候选人档案');
|
|
258
|
+
},
|
|
259
|
+
confirmText: '保存'
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const content = (
|
|
263
|
+
<>
|
|
264
|
+
<Paragraph style={{ marginBottom: 8 }}>
|
|
265
|
+
将把当前页面的筛选条件与评估摘要一并写入 <Text strong>陈思远</Text> 的档案备注。
|
|
266
|
+
</Paragraph>
|
|
267
|
+
<Paragraph type="secondary" style={{ marginBottom: 0 }}>
|
|
268
|
+
保存后可在「候选人详情 → 操作记录」中查看历史版本。
|
|
269
|
+
</Paragraph>
|
|
270
|
+
</>
|
|
271
|
+
);
|
|
241
272
|
|
|
242
273
|
return (
|
|
243
274
|
<Space direction="vertical" style={{ width: '100%' }} size="middle">
|
|
275
|
+
<Space align="center" wrap>
|
|
276
|
+
<Text type="secondary">打开方式</Text>
|
|
277
|
+
<Radio.Group
|
|
278
|
+
value={mode}
|
|
279
|
+
optionType="button"
|
|
280
|
+
size="small"
|
|
281
|
+
options={[
|
|
282
|
+
{ label: 'Modal', value: 'modal' },
|
|
283
|
+
{ label: 'Drawer', value: 'drawer' }
|
|
284
|
+
]}
|
|
285
|
+
onChange={e => setMode(e.target.value)}
|
|
286
|
+
/>
|
|
287
|
+
</Space>
|
|
244
288
|
<Button type="primary" onClick={() => setOpen(true)}>
|
|
245
289
|
保存评估备注
|
|
246
290
|
</Button>
|
|
247
|
-
<Text type="secondary"
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}}
|
|
256
|
-
|
|
257
|
-
>
|
|
258
|
-
<Paragraph style={{ marginBottom: 8 }}>
|
|
259
|
-
将把当前页面的筛选条件与评估摘要一并写入 <Text strong>陈思远</Text> 的档案备注。
|
|
260
|
-
</Paragraph>
|
|
261
|
-
<Paragraph type="secondary" style={{ marginBottom: 0 }}>
|
|
262
|
-
保存后可在「候选人详情 → 操作记录」中查看历史版本。
|
|
263
|
-
</Paragraph>
|
|
264
|
-
</Modal>
|
|
291
|
+
<Text type="secondary">
|
|
292
|
+
最简受控弹层:切换 Modal / Drawer 对比同一套 props;异步 onConfirm 带 loading。
|
|
293
|
+
</Text>
|
|
294
|
+
{isDrawer ? (
|
|
295
|
+
<Drawer {...overlayProps} size="default">
|
|
296
|
+
{content}
|
|
297
|
+
</Drawer>
|
|
298
|
+
) : (
|
|
299
|
+
<Modal {...overlayProps}>{content}</Modal>
|
|
300
|
+
)}
|
|
265
301
|
</Space>
|
|
266
302
|
);
|
|
267
303
|
};
|
|
268
304
|
|
|
269
|
-
|
|
305
|
+
render(
|
|
270
306
|
<App>
|
|
307
|
+
<DrawerContextHolder />
|
|
271
308
|
<BasicExample />
|
|
272
309
|
</App>
|
|
273
310
|
);
|
|
274
311
|
|
|
275
|
-
render(<BaseExample />);
|
|
276
|
-
|
|
277
312
|
```
|
|
278
313
|
|
|
279
314
|
- footerButtons 与尺寸
|
|
280
|
-
-
|
|
281
|
-
- _ReactModal(@kne/current-lib_react-modal)[import * as _ReactModal from "@kne/react-modal"],(@kne/current-lib_react-modal/dist/index.css),antd(antd)
|
|
315
|
+
- Modal / Drawer 切换:size / footerButtons / 左侧 footer 插槽 / noPadding 预览
|
|
316
|
+
- _ReactModal(@kne/current-lib_react-modal)[import * as _ReactModal from "@kne/react-modal"],(@kne/current-lib_react-modal/dist/index.css),(@kne/current-lib_react-modal/doc/style.scss),antd(antd)
|
|
282
317
|
|
|
283
318
|
```jsx
|
|
284
|
-
const { default: Modal } = _ReactModal;
|
|
285
|
-
const { Button, Space, Radio, Tag, message,
|
|
286
|
-
const { useState } = React;
|
|
319
|
+
const { default: Modal, Drawer, DrawerContextHolder } = _ReactModal;
|
|
320
|
+
const { Button, Space, Radio, Tag, message, Typography, App } = antd;
|
|
321
|
+
const { useState, useEffect } = React;
|
|
287
322
|
|
|
288
323
|
const { Text, Title, Paragraph } = Typography;
|
|
289
324
|
|
|
@@ -291,11 +326,85 @@ const FooterButtonsExample = () => {
|
|
|
291
326
|
const [open, setOpen] = useState(false);
|
|
292
327
|
const [size, setSize] = useState('default');
|
|
293
328
|
const [noPadding, setNoPadding] = useState(false);
|
|
329
|
+
const [mode, setMode] = useState('modal');
|
|
330
|
+
const isDrawer = mode === 'drawer';
|
|
331
|
+
|
|
332
|
+
useEffect(() => {
|
|
333
|
+
if (open) {
|
|
334
|
+
setOpen(false);
|
|
335
|
+
}
|
|
336
|
+
}, [mode]);
|
|
337
|
+
|
|
338
|
+
const overlayProps = {
|
|
339
|
+
title: '确认发布岗位',
|
|
340
|
+
size,
|
|
341
|
+
noPadding,
|
|
342
|
+
open,
|
|
343
|
+
onClose: () => setOpen(false),
|
|
344
|
+
footer: <Text type="secondary">发布后将同步至招聘官网与内推渠道</Text>,
|
|
345
|
+
footerButtons: [
|
|
346
|
+
{
|
|
347
|
+
children: '存草稿',
|
|
348
|
+
onClick: () => message.info('已保存草稿')
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
children: '预览',
|
|
352
|
+
display: () => size !== 'small',
|
|
353
|
+
onClick: () => message.info('打开预览页')
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
type: 'primary',
|
|
357
|
+
children: '立即发布',
|
|
358
|
+
onClick: async () => {
|
|
359
|
+
await new Promise(resolve => setTimeout(resolve, 600));
|
|
360
|
+
message.success('岗位已发布');
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
]
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
const content = (
|
|
367
|
+
<div className="demo-job-preview">
|
|
368
|
+
<div className="demo-job-preview-header">
|
|
369
|
+
<Title level={5} style={{ margin: 0 }}>
|
|
370
|
+
高级前端工程师
|
|
371
|
+
</Title>
|
|
372
|
+
<Space size={8} style={{ marginTop: 8 }}>
|
|
373
|
+
<Tag color="blue">上海</Tag>
|
|
374
|
+
<Tag>25K–40K · 15 薪</Tag>
|
|
375
|
+
<Tag color="green">急招</Tag>
|
|
376
|
+
</Space>
|
|
377
|
+
</div>
|
|
378
|
+
<div className="demo-job-preview-body">
|
|
379
|
+
<Paragraph style={{ marginTop: 0 }}>
|
|
380
|
+
负责招聘中台、候选人评估等 B 端产品的前端交付;要求熟悉 React、工程化与组件库协作。
|
|
381
|
+
</Paragraph>
|
|
382
|
+
<Paragraph style={{ marginBottom: 0 }}>
|
|
383
|
+
{noPadding
|
|
384
|
+
? 'noPadding=true:预览卡片应贴齐内容区边缘。'
|
|
385
|
+
: 'noPadding=false:预览卡片四周保留默认内边距。'}
|
|
386
|
+
</Paragraph>
|
|
387
|
+
</div>
|
|
388
|
+
</div>
|
|
389
|
+
);
|
|
294
390
|
|
|
295
391
|
return (
|
|
296
392
|
<Space direction="vertical" style={{ width: '100%' }} size="large">
|
|
393
|
+
<Space align="center" wrap>
|
|
394
|
+
<Text type="secondary">打开方式</Text>
|
|
395
|
+
<Radio.Group
|
|
396
|
+
value={mode}
|
|
397
|
+
optionType="button"
|
|
398
|
+
size="small"
|
|
399
|
+
options={[
|
|
400
|
+
{ label: 'Modal', value: 'modal' },
|
|
401
|
+
{ label: 'Drawer', value: 'drawer' }
|
|
402
|
+
]}
|
|
403
|
+
onChange={e => setMode(e.target.value)}
|
|
404
|
+
/>
|
|
405
|
+
</Space>
|
|
297
406
|
<div>
|
|
298
|
-
<Text type="secondary"
|
|
407
|
+
<Text type="secondary">切换 Modal / Drawer、尺寸与 noPadding,观察预览卡片与 footer 按钮区布局。</Text>
|
|
299
408
|
</div>
|
|
300
409
|
<Radio.Group
|
|
301
410
|
value={size}
|
|
@@ -315,86 +424,45 @@ const FooterButtonsExample = () => {
|
|
|
315
424
|
noPadding={String(noPadding)}
|
|
316
425
|
</Button>
|
|
317
426
|
</Space>
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
onClose={() => setOpen(false)}
|
|
324
|
-
footer={<Text type="secondary">发布后将同步至招聘官网与内推渠道</Text>}
|
|
325
|
-
footerButtons={[
|
|
326
|
-
{
|
|
327
|
-
children: '存草稿',
|
|
328
|
-
onClick: () => message.info('已保存草稿')
|
|
329
|
-
},
|
|
330
|
-
{
|
|
331
|
-
children: '预览',
|
|
332
|
-
display: () => size !== 'small',
|
|
333
|
-
onClick: () => message.info('打开预览页')
|
|
334
|
-
},
|
|
335
|
-
{
|
|
336
|
-
type: 'primary',
|
|
337
|
-
children: '立即发布',
|
|
338
|
-
onClick: async () => {
|
|
339
|
-
await new Promise(resolve => setTimeout(resolve, 600));
|
|
340
|
-
message.success('岗位已发布');
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
]}
|
|
344
|
-
>
|
|
345
|
-
<div className="demo-job-preview">
|
|
346
|
-
<div className="demo-job-preview-header">
|
|
347
|
-
<Title level={5} style={{ margin: 0 }}>
|
|
348
|
-
高级前端工程师
|
|
349
|
-
</Title>
|
|
350
|
-
<Space size={8} style={{ marginTop: 8 }}>
|
|
351
|
-
<Tag color="blue">上海</Tag>
|
|
352
|
-
<Tag>25K–40K · 15 薪</Tag>
|
|
353
|
-
<Tag color="green">急招</Tag>
|
|
354
|
-
</Space>
|
|
355
|
-
</div>
|
|
356
|
-
<div className="demo-job-preview-body">
|
|
357
|
-
<Paragraph style={{ marginTop: 0 }}>
|
|
358
|
-
负责招聘中台、候选人评估等 B 端产品的前端交付;要求熟悉 React、工程化与组件库协作。
|
|
359
|
-
</Paragraph>
|
|
360
|
-
<Paragraph style={{ marginBottom: 0 }}>
|
|
361
|
-
{noPadding
|
|
362
|
-
? 'noPadding=true:预览卡片应贴齐弹窗内容区边缘。'
|
|
363
|
-
: 'noPadding=false:预览卡片四周保留默认内边距。'}
|
|
364
|
-
</Paragraph>
|
|
365
|
-
</div>
|
|
366
|
-
</div>
|
|
367
|
-
</Modal>
|
|
427
|
+
{isDrawer ? (
|
|
428
|
+
<Drawer {...overlayProps}>{content}</Drawer>
|
|
429
|
+
) : (
|
|
430
|
+
<Modal {...overlayProps}>{content}</Modal>
|
|
431
|
+
)}
|
|
368
432
|
</Space>
|
|
369
433
|
);
|
|
370
434
|
};
|
|
371
435
|
|
|
372
|
-
|
|
436
|
+
render(
|
|
373
437
|
<App>
|
|
438
|
+
<DrawerContextHolder />
|
|
374
439
|
<FooterButtonsExample />
|
|
375
440
|
</App>
|
|
376
441
|
);
|
|
377
442
|
|
|
378
|
-
render(<BaseExample />);
|
|
379
|
-
|
|
380
443
|
```
|
|
381
444
|
|
|
382
|
-
- useModal
|
|
383
|
-
-
|
|
445
|
+
- 命令式打开(useModal / useDrawer)
|
|
446
|
+
- 切换 Modal / Drawer:命令式快览,children 函数内可 close()
|
|
384
447
|
- _ReactModal(@kne/current-lib_react-modal)[import * as _ReactModal from "@kne/react-modal"],(@kne/current-lib_react-modal/dist/index.css),antd(antd)
|
|
385
448
|
|
|
386
449
|
```jsx
|
|
387
|
-
const { useModal } = _ReactModal;
|
|
388
|
-
const { Button, Space, message,
|
|
450
|
+
const { useModal, useDrawer, DrawerContextHolder } = _ReactModal;
|
|
451
|
+
const { Button, Space, message, Descriptions, Tag, Typography, Radio, App } = antd;
|
|
452
|
+
const { useState } = React;
|
|
389
453
|
|
|
390
454
|
const { Text, Paragraph } = Typography;
|
|
391
455
|
|
|
392
456
|
const CommandExample = () => {
|
|
393
457
|
const modal = useModal();
|
|
458
|
+
const drawer = useDrawer();
|
|
459
|
+
const [mode, setMode] = useState('modal');
|
|
460
|
+
const isDrawer = mode === 'drawer';
|
|
394
461
|
|
|
395
462
|
const openDetail = () => {
|
|
396
|
-
modal
|
|
397
|
-
|
|
463
|
+
const api = isDrawer ? drawer : modal;
|
|
464
|
+
api({
|
|
465
|
+
title: isDrawer ? '候选人快览(侧滑)' : '候选人快览',
|
|
398
466
|
size: 'small',
|
|
399
467
|
confirmText: '加入待评估',
|
|
400
468
|
children: ({ close }) => (
|
|
@@ -408,8 +476,8 @@ const CommandExample = () => {
|
|
|
408
476
|
</Descriptions.Item>
|
|
409
477
|
</Descriptions>
|
|
410
478
|
<Paragraph type="secondary" style={{ marginBottom: 12 }}>
|
|
411
|
-
|
|
412
|
-
主动关闭。
|
|
479
|
+
命令式 {isDrawer ? 'Drawer' : 'Modal'} 适用于列表页「快速查看」;children 为函数时可调用{' '}
|
|
480
|
+
<Text code>close()</Text> 主动关闭。
|
|
413
481
|
</Paragraph>
|
|
414
482
|
<Button size="small" onClick={() => close()}>
|
|
415
483
|
关闭
|
|
@@ -424,10 +492,129 @@ const CommandExample = () => {
|
|
|
424
492
|
|
|
425
493
|
return (
|
|
426
494
|
<Space direction="vertical">
|
|
495
|
+
<Space align="center" wrap>
|
|
496
|
+
<Text type="secondary">打开方式</Text>
|
|
497
|
+
<Radio.Group
|
|
498
|
+
value={mode}
|
|
499
|
+
optionType="button"
|
|
500
|
+
size="small"
|
|
501
|
+
options={[
|
|
502
|
+
{ label: 'Modal', value: 'modal' },
|
|
503
|
+
{ label: 'Drawer', value: 'drawer' }
|
|
504
|
+
]}
|
|
505
|
+
onChange={e => setMode(e.target.value)}
|
|
506
|
+
/>
|
|
507
|
+
</Space>
|
|
427
508
|
<Button type="primary" onClick={openDetail}>
|
|
428
509
|
从列表打开候选人快览
|
|
429
510
|
</Button>
|
|
430
|
-
<Text type="secondary"
|
|
511
|
+
<Text type="secondary">
|
|
512
|
+
Drawer 模式需挂载 DrawerContextHolder;Modal 使用 antd App 内置 useModal。
|
|
513
|
+
</Text>
|
|
514
|
+
</Space>
|
|
515
|
+
);
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
render(
|
|
519
|
+
<App>
|
|
520
|
+
<DrawerContextHolder />
|
|
521
|
+
<CommandExample />
|
|
522
|
+
</App>
|
|
523
|
+
);
|
|
524
|
+
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
- useConfirmModal 确认框
|
|
528
|
+
- confirm / info / success / warning / error 命令式确认与提示(仅 Modal)
|
|
529
|
+
- _ReactModal(@kne/current-lib_react-modal)[import * as _ReactModal from "@kne/react-modal"],(@kne/current-lib_react-modal/dist/index.css),antd(antd)
|
|
530
|
+
|
|
531
|
+
```jsx
|
|
532
|
+
const { useConfirmModal } = _ReactModal;
|
|
533
|
+
const { Button, Space, App } = antd;
|
|
534
|
+
|
|
535
|
+
const CommandExample = () => {
|
|
536
|
+
const confirmModal = useConfirmModal();
|
|
537
|
+
|
|
538
|
+
return (
|
|
539
|
+
<Space wrap>
|
|
540
|
+
<Button
|
|
541
|
+
onClick={() => {
|
|
542
|
+
confirmModal({
|
|
543
|
+
danger: true,
|
|
544
|
+
type: 'confirm',
|
|
545
|
+
title: '确定要删除该记录吗?',
|
|
546
|
+
message: '此操作将永久删除该记录,相关数据将无法恢复。请确认是否继续删除操作?'
|
|
547
|
+
});
|
|
548
|
+
}}
|
|
549
|
+
>
|
|
550
|
+
confirm
|
|
551
|
+
</Button>
|
|
552
|
+
<Button
|
|
553
|
+
onClick={() => {
|
|
554
|
+
confirmModal({
|
|
555
|
+
type: 'confirm',
|
|
556
|
+
confirmType: 'warning',
|
|
557
|
+
title: '确定要编辑此内容吗?',
|
|
558
|
+
message: '编辑后需要重新提交审核,未保存的修改将丢失。请确认是否继续编辑?'
|
|
559
|
+
});
|
|
560
|
+
}}
|
|
561
|
+
>
|
|
562
|
+
confirm 警告
|
|
563
|
+
</Button>
|
|
564
|
+
<Button
|
|
565
|
+
onClick={() => {
|
|
566
|
+
confirmModal({
|
|
567
|
+
type: 'info',
|
|
568
|
+
title: '操作提示',
|
|
569
|
+
message: '该操作将更新系统配置,可能影响其他用户的使用。建议在非工作时间进行此操作。'
|
|
570
|
+
});
|
|
571
|
+
}}
|
|
572
|
+
>
|
|
573
|
+
info
|
|
574
|
+
</Button>
|
|
575
|
+
<Button
|
|
576
|
+
onClick={() => {
|
|
577
|
+
confirmModal({
|
|
578
|
+
type: 'info',
|
|
579
|
+
message: '数据已保存成功,系统将在后台进行同步处理,请稍候查看处理结果。'
|
|
580
|
+
});
|
|
581
|
+
}}
|
|
582
|
+
>
|
|
583
|
+
info 无标题
|
|
584
|
+
</Button>
|
|
585
|
+
<Button
|
|
586
|
+
onClick={() => {
|
|
587
|
+
confirmModal({
|
|
588
|
+
type: 'success',
|
|
589
|
+
title: '操作成功',
|
|
590
|
+
message: '恭喜!您的操作已成功完成。系统已发送通知邮件给相关团队成员。'
|
|
591
|
+
});
|
|
592
|
+
}}
|
|
593
|
+
>
|
|
594
|
+
success
|
|
595
|
+
</Button>
|
|
596
|
+
<Button
|
|
597
|
+
onClick={() => {
|
|
598
|
+
confirmModal({
|
|
599
|
+
type: 'warning',
|
|
600
|
+
title: '操作警告',
|
|
601
|
+
message: '检测到数据异常,继续操作可能导致数据不一致。建议先备份数据或联系技术支持。'
|
|
602
|
+
});
|
|
603
|
+
}}
|
|
604
|
+
>
|
|
605
|
+
warning
|
|
606
|
+
</Button>
|
|
607
|
+
<Button
|
|
608
|
+
onClick={() => {
|
|
609
|
+
confirmModal({
|
|
610
|
+
type: 'error',
|
|
611
|
+
title: '操作失败',
|
|
612
|
+
message: '系统处理出错,请检查网络连接或联系系统管理员。错误代码:ERR-500'
|
|
613
|
+
});
|
|
614
|
+
}}
|
|
615
|
+
>
|
|
616
|
+
error
|
|
617
|
+
</Button>
|
|
431
618
|
</Space>
|
|
432
619
|
);
|
|
433
620
|
};
|
|
@@ -443,13 +630,13 @@ render(<BaseExample />);
|
|
|
443
630
|
```
|
|
444
631
|
|
|
445
632
|
- 长内容滚动
|
|
446
|
-
-
|
|
633
|
+
- Modal / Drawer 切换:SimpleBar vs bodyScroll=false 自管滚动
|
|
447
634
|
- _ReactModal(@kne/current-lib_react-modal)[import * as _ReactModal from "@kne/react-modal"],(@kne/current-lib_react-modal/dist/index.css),antd(antd)
|
|
448
635
|
|
|
449
636
|
```jsx
|
|
450
|
-
const { default: Modal } = _ReactModal;
|
|
451
|
-
const { Button, Space,
|
|
452
|
-
const { useState } = React;
|
|
637
|
+
const { default: Modal, Drawer, DrawerContextHolder } = _ReactModal;
|
|
638
|
+
const { Button, Space, Typography, Divider, Radio, App } = antd;
|
|
639
|
+
const { useState, useEffect } = React;
|
|
453
640
|
|
|
454
641
|
const { Title, Paragraph, Text } = Typography;
|
|
455
642
|
|
|
@@ -508,11 +695,38 @@ const EvaluationContent = () => (
|
|
|
508
695
|
const LongContentExample = () => {
|
|
509
696
|
const [open, setOpen] = useState(false);
|
|
510
697
|
const [openSelfScroll, setOpenSelfScroll] = useState(false);
|
|
698
|
+
const [mode, setMode] = useState('modal');
|
|
699
|
+
const isDrawer = mode === 'drawer';
|
|
700
|
+
|
|
701
|
+
useEffect(() => {
|
|
702
|
+
if (open) {
|
|
703
|
+
setOpen(false);
|
|
704
|
+
}
|
|
705
|
+
if (openSelfScroll) {
|
|
706
|
+
setOpenSelfScroll(false);
|
|
707
|
+
}
|
|
708
|
+
}, [mode]);
|
|
709
|
+
|
|
710
|
+
const contentHeightVar = isDrawer ? '--kne-drawer-content-height' : '--kne-modal-content-height';
|
|
711
|
+
const Overlay = isDrawer ? Drawer : Modal;
|
|
511
712
|
|
|
512
713
|
return (
|
|
513
714
|
<Space direction="vertical" style={{ width: '100%' }} size="middle">
|
|
715
|
+
<Space align="center" wrap>
|
|
716
|
+
<Text type="secondary">打开方式</Text>
|
|
717
|
+
<Radio.Group
|
|
718
|
+
value={mode}
|
|
719
|
+
optionType="button"
|
|
720
|
+
size="small"
|
|
721
|
+
options={[
|
|
722
|
+
{ label: 'Modal', value: 'modal' },
|
|
723
|
+
{ label: 'Drawer', value: 'drawer' }
|
|
724
|
+
]}
|
|
725
|
+
onChange={e => setMode(e.target.value)}
|
|
726
|
+
/>
|
|
727
|
+
</Space>
|
|
514
728
|
<Paragraph type="secondary" style={{ margin: 0 }}>
|
|
515
|
-
对比默认 SimpleBar 与 bodyScroll=false
|
|
729
|
+
对比默认 SimpleBar 与 bodyScroll=false 自管滚动;切换 Modal / Drawer 观察高度链一致。
|
|
516
730
|
</Paragraph>
|
|
517
731
|
<Space wrap>
|
|
518
732
|
<Button type="primary" onClick={() => setOpen(true)}>
|
|
@@ -520,25 +734,27 @@ const LongContentExample = () => {
|
|
|
520
734
|
</Button>
|
|
521
735
|
<Button onClick={() => setOpenSelfScroll(true)}>自管滚动(bodyScroll=false)</Button>
|
|
522
736
|
</Space>
|
|
523
|
-
<
|
|
737
|
+
<Overlay
|
|
524
738
|
title="陈思远 · 面试评估纪要"
|
|
525
739
|
open={open}
|
|
526
740
|
onClose={() => setOpen(false)}
|
|
527
741
|
onConfirm={() => {}}
|
|
528
742
|
confirmText="保存纪要"
|
|
743
|
+
size={isDrawer ? 'default' : undefined}
|
|
529
744
|
>
|
|
530
745
|
<EvaluationContent />
|
|
531
|
-
</
|
|
532
|
-
<
|
|
746
|
+
</Overlay>
|
|
747
|
+
<Overlay
|
|
533
748
|
title="自管滚动示例"
|
|
534
749
|
open={openSelfScroll}
|
|
535
750
|
onClose={() => setOpenSelfScroll(false)}
|
|
536
751
|
bodyScroll={false}
|
|
537
752
|
footer={null}
|
|
753
|
+
size={isDrawer ? 'large' : undefined}
|
|
538
754
|
>
|
|
539
755
|
<div
|
|
540
756
|
style={{
|
|
541
|
-
height:
|
|
757
|
+
height: `var(${contentHeightVar})`,
|
|
542
758
|
minHeight: 0,
|
|
543
759
|
overflow: 'auto',
|
|
544
760
|
boxSizing: 'border-box',
|
|
@@ -547,35 +763,36 @@ const LongContentExample = () => {
|
|
|
547
763
|
>
|
|
548
764
|
<div style={{ padding: 20 }}>
|
|
549
765
|
<Paragraph style={{ marginTop: 0 }}>
|
|
550
|
-
内容区高度绑定 <Text code
|
|
766
|
+
内容区高度绑定 <Text code>{contentHeightVar}</Text>,Tabs / 分栏场景同样适用。
|
|
551
767
|
</Paragraph>
|
|
552
768
|
<EvaluationContent />
|
|
553
769
|
</div>
|
|
554
770
|
</div>
|
|
555
|
-
</
|
|
771
|
+
</Overlay>
|
|
556
772
|
</Space>
|
|
557
773
|
);
|
|
558
774
|
};
|
|
559
775
|
|
|
560
|
-
|
|
776
|
+
render(
|
|
561
777
|
<App>
|
|
778
|
+
<DrawerContextHolder />
|
|
562
779
|
<LongContentExample />
|
|
563
780
|
</App>
|
|
564
781
|
);
|
|
565
782
|
|
|
566
|
-
render(<BaseExample />);
|
|
567
|
-
|
|
568
783
|
```
|
|
569
784
|
|
|
570
785
|
- 高度 CSS 变量
|
|
571
|
-
-
|
|
786
|
+
- Modal / Drawer 切换:默认/自定义 CSS 变量;色块绑定 content-height,探针显示变量与 clientHeight
|
|
572
787
|
- _ReactModal(@kne/current-lib_react-modal)[import * as _ReactModal from "@kne/react-modal"],(@kne/current-lib_react-modal/dist/index.css),antd(antd)
|
|
573
788
|
|
|
574
789
|
```jsx
|
|
575
|
-
const { default: Modal } = _ReactModal;
|
|
576
|
-
const { Button, Space, Switch, Tag, Descriptions, App } = antd;
|
|
790
|
+
const { default: Modal, Drawer, DrawerContextHolder } = _ReactModal;
|
|
791
|
+
const { Button, Space, Switch, Tag, Descriptions, Radio, Typography, App } = antd;
|
|
577
792
|
const { useState, useEffect } = React;
|
|
578
793
|
|
|
794
|
+
const { Text } = Typography;
|
|
795
|
+
|
|
579
796
|
const readCssVar = (el, name) => {
|
|
580
797
|
if (!el) {
|
|
581
798
|
return '-';
|
|
@@ -583,8 +800,22 @@ const readCssVar = (el, name) => {
|
|
|
583
800
|
return getComputedStyle(el).getPropertyValue(name).trim() || '-';
|
|
584
801
|
};
|
|
585
802
|
|
|
586
|
-
const
|
|
803
|
+
const getOverlayChrome = mode => {
|
|
804
|
+
const isDrawer = mode === 'drawer';
|
|
805
|
+
return {
|
|
806
|
+
isDrawer,
|
|
807
|
+
testId: isDrawer ? 'react-drawer' : 'react-modal',
|
|
808
|
+
bodyClass: isDrawer ? 'drawer-body' : 'modal-body',
|
|
809
|
+
innerClass: isDrawer ? 'drawer-body-inner' : 'modal-body-inner',
|
|
810
|
+
contentHeightVar: isDrawer ? '--kne-drawer-content-height' : '--kne-modal-content-height',
|
|
811
|
+
bodyHeightVar: isDrawer ? '--kne-drawer-body-height' : '--kne-modal-body-height',
|
|
812
|
+
varPrefix: isDrawer ? '--kne-drawer' : '--kne-modal'
|
|
813
|
+
};
|
|
814
|
+
};
|
|
815
|
+
|
|
816
|
+
const HeightProbe = ({ open, revision, mode }) => {
|
|
587
817
|
const [metrics, setMetrics] = useState(null);
|
|
818
|
+
const chrome = getOverlayChrome(mode);
|
|
588
819
|
|
|
589
820
|
useEffect(() => {
|
|
590
821
|
if (!open) {
|
|
@@ -592,34 +823,34 @@ const HeightProbe = ({ open, revision }) => {
|
|
|
592
823
|
return undefined;
|
|
593
824
|
}
|
|
594
825
|
const timer = setTimeout(() => {
|
|
595
|
-
const outer = document.querySelector(
|
|
596
|
-
const body = outer && outer.querySelector(
|
|
597
|
-
const inner = outer && outer.querySelector(
|
|
826
|
+
const outer = document.querySelector(`[data-testid="${chrome.testId}"]`);
|
|
827
|
+
const body = outer && outer.querySelector(`.${chrome.bodyClass}`);
|
|
828
|
+
const inner = outer && outer.querySelector(`.${chrome.innerClass}`);
|
|
598
829
|
const fill = outer && outer.querySelector('[data-vars-fill]');
|
|
599
830
|
setMetrics({
|
|
600
|
-
gutter: readCssVar(outer,
|
|
601
|
-
bodyHeightVar: readCssVar(body || outer,
|
|
602
|
-
contentHeightVar: readCssVar(inner || body || outer,
|
|
603
|
-
bodyMin: readCssVar(outer,
|
|
604
|
-
paddingV: readCssVar(outer,
|
|
831
|
+
gutter: readCssVar(outer, `${chrome.varPrefix}-viewport-gutter`),
|
|
832
|
+
bodyHeightVar: readCssVar(body || outer, chrome.bodyHeightVar),
|
|
833
|
+
contentHeightVar: readCssVar(inner || body || outer, chrome.contentHeightVar),
|
|
834
|
+
bodyMin: readCssVar(outer, `${chrome.varPrefix}-body-min-height`),
|
|
835
|
+
paddingV: readCssVar(outer, `${chrome.varPrefix}-body-padding-vertical`),
|
|
605
836
|
bodyClient: body ? `${body.clientHeight}px` : '-',
|
|
606
837
|
fillClient: fill ? `${fill.clientHeight}px` : '-'
|
|
607
838
|
});
|
|
608
839
|
}, 80);
|
|
609
840
|
return () => clearTimeout(timer);
|
|
610
|
-
}, [open, revision]);
|
|
841
|
+
}, [open, revision, mode, chrome]);
|
|
611
842
|
|
|
612
843
|
if (!metrics) {
|
|
613
|
-
return <Tag
|
|
844
|
+
return <Tag>打开弹层后显示变量与实测高度</Tag>;
|
|
614
845
|
}
|
|
615
846
|
|
|
616
847
|
return (
|
|
617
848
|
<Descriptions size="small" bordered column={1} style={{ maxWidth: 640 }}>
|
|
618
|
-
<Descriptions.Item label=
|
|
619
|
-
<Descriptions.Item label=
|
|
620
|
-
<Descriptions.Item label=
|
|
621
|
-
<Descriptions.Item label=
|
|
622
|
-
<Descriptions.Item label=
|
|
849
|
+
<Descriptions.Item label={`${chrome.varPrefix}-viewport-gutter`}>{metrics.gutter}</Descriptions.Item>
|
|
850
|
+
<Descriptions.Item label={`${chrome.varPrefix}-body-min-height`}>{metrics.bodyMin}</Descriptions.Item>
|
|
851
|
+
<Descriptions.Item label={`${chrome.varPrefix}-body-padding-vertical`}>{metrics.paddingV}</Descriptions.Item>
|
|
852
|
+
<Descriptions.Item label={chrome.bodyHeightVar}>{metrics.bodyHeightVar}</Descriptions.Item>
|
|
853
|
+
<Descriptions.Item label={chrome.contentHeightVar}>{metrics.contentHeightVar}</Descriptions.Item>
|
|
623
854
|
<Descriptions.Item label="body.clientHeight">{metrics.bodyClient}</Descriptions.Item>
|
|
624
855
|
<Descriptions.Item label="色块 clientHeight(应≈ content-height)">{metrics.fillClient}</Descriptions.Item>
|
|
625
856
|
</Descriptions>
|
|
@@ -631,18 +862,47 @@ const fillLines = Array.from({ length: 18 }, (_, i) => `填充行 ${i + 1}
|
|
|
631
862
|
const HeightVarsExample = () => {
|
|
632
863
|
const [open, setOpen] = useState(false);
|
|
633
864
|
const [customVars, setCustomVars] = useState(true);
|
|
634
|
-
const
|
|
865
|
+
const [mode, setMode] = useState('modal');
|
|
866
|
+
const isDrawer = mode === 'drawer';
|
|
635
867
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
868
|
+
useEffect(() => {
|
|
869
|
+
if (open) {
|
|
870
|
+
setOpen(false);
|
|
871
|
+
}
|
|
872
|
+
}, [mode]);
|
|
873
|
+
|
|
874
|
+
const chrome = getOverlayChrome(mode);
|
|
875
|
+
const revision = `${mode}|${customVars}|${open}`;
|
|
876
|
+
const Overlay = isDrawer ? Drawer : Modal;
|
|
877
|
+
|
|
878
|
+
const overlayStyle = customVars
|
|
879
|
+
? isDrawer
|
|
880
|
+
? {
|
|
881
|
+
'--kne-drawer-body-min-height': '180px',
|
|
882
|
+
'--kne-drawer-body-padding-vertical': '64px'
|
|
883
|
+
}
|
|
884
|
+
: {
|
|
885
|
+
'--kne-modal-viewport-gutter': '240px',
|
|
886
|
+
'--kne-modal-body-min-height': '180px',
|
|
887
|
+
'--kne-modal-body-padding-vertical': '64px'
|
|
888
|
+
}
|
|
642
889
|
: undefined;
|
|
643
890
|
|
|
644
891
|
return (
|
|
645
892
|
<Space direction="vertical" style={{ width: '100%' }} size="middle">
|
|
893
|
+
<Space align="center" wrap>
|
|
894
|
+
<Text type="secondary">打开方式</Text>
|
|
895
|
+
<Radio.Group
|
|
896
|
+
value={mode}
|
|
897
|
+
optionType="button"
|
|
898
|
+
size="small"
|
|
899
|
+
options={[
|
|
900
|
+
{ label: 'Modal', value: 'modal' },
|
|
901
|
+
{ label: 'Drawer', value: 'drawer' }
|
|
902
|
+
]}
|
|
903
|
+
onChange={e => setMode(e.target.value)}
|
|
904
|
+
/>
|
|
905
|
+
</Space>
|
|
646
906
|
<Space wrap align="center">
|
|
647
907
|
<Switch
|
|
648
908
|
checked={customVars}
|
|
@@ -651,29 +911,29 @@ const HeightVarsExample = () => {
|
|
|
651
911
|
unCheckedChildren="默认变量"
|
|
652
912
|
/>
|
|
653
913
|
<Button type="primary" onClick={() => setOpen(true)}>
|
|
654
|
-
|
|
914
|
+
打开弹层对比高度
|
|
655
915
|
</Button>
|
|
656
916
|
<Tag color={customVars ? 'blue' : 'default'}>{customVars ? '已覆盖 CSS 变量' : '库内默认值'}</Tag>
|
|
657
917
|
</Space>
|
|
658
918
|
<div style={{ color: 'rgba(0,0,0,0.45)', maxWidth: 640 }}>
|
|
659
|
-
通过
|
|
660
|
-
<code>
|
|
661
|
-
<code>--kne-modal-content-height</code>,应与实测 content 高度一致。
|
|
919
|
+
通过 <code>style</code> 覆盖 {chrome.varPrefix}-* 变量。Drawer 无 viewport-gutter;Modal 可加大 gutter
|
|
920
|
+
使 body 明显变矮。色块绑定 <code>{chrome.contentHeightVar}</code>。
|
|
662
921
|
</div>
|
|
663
|
-
<HeightProbe open={open} revision={revision} />
|
|
664
|
-
<
|
|
922
|
+
<HeightProbe open={open} revision={revision} mode={mode} />
|
|
923
|
+
<Overlay
|
|
665
924
|
title={customVars ? '自定义高度 CSS 变量' : '默认高度 CSS 变量'}
|
|
666
925
|
open={open}
|
|
667
926
|
onClose={() => setOpen(false)}
|
|
668
927
|
onConfirm={() => {}}
|
|
669
928
|
bodyScroll={false}
|
|
670
929
|
noPadding={false}
|
|
671
|
-
style={
|
|
930
|
+
style={overlayStyle}
|
|
931
|
+
size={isDrawer ? 'default' : undefined}
|
|
672
932
|
>
|
|
673
933
|
<div
|
|
674
934
|
data-vars-fill
|
|
675
935
|
style={{
|
|
676
|
-
height:
|
|
936
|
+
height: `var(${chrome.contentHeightVar})`,
|
|
677
937
|
minHeight: 0,
|
|
678
938
|
overflow: 'auto',
|
|
679
939
|
boxSizing: 'border-box',
|
|
@@ -683,16 +943,8 @@ const HeightVarsExample = () => {
|
|
|
683
943
|
>
|
|
684
944
|
<div style={{ padding: 12 }}>
|
|
685
945
|
<p style={{ marginTop: 0, fontWeight: 600 }}>
|
|
686
|
-
色块 height = var(
|
|
946
|
+
色块 height = var({chrome.contentHeightVar})。切换「自定义/默认」与 Modal/Drawer 后重新打开对比。
|
|
687
947
|
</p>
|
|
688
|
-
{customVars ? (
|
|
689
|
-
<p style={{ margin: '0 0 8px' }}>
|
|
690
|
-
当前覆盖:gutter 240px(默认 120px)、padding-vertical 64px(默认 48px)、body-min-height
|
|
691
|
-
180px。
|
|
692
|
-
</p>
|
|
693
|
-
) : (
|
|
694
|
-
<p style={{ margin: '0 0 8px' }}>未传 style,使用库内 calc 与 size 默认 min-height。</p>
|
|
695
|
-
)}
|
|
696
948
|
{fillLines.map(text => (
|
|
697
949
|
<p key={text} style={{ margin: '4px 0' }}>
|
|
698
950
|
{text}
|
|
@@ -700,30 +952,31 @@ const HeightVarsExample = () => {
|
|
|
700
952
|
))}
|
|
701
953
|
</div>
|
|
702
954
|
</div>
|
|
703
|
-
</
|
|
955
|
+
</Overlay>
|
|
704
956
|
</Space>
|
|
705
957
|
);
|
|
706
958
|
};
|
|
707
959
|
|
|
708
|
-
|
|
960
|
+
render(
|
|
709
961
|
<App>
|
|
962
|
+
<DrawerContextHolder />
|
|
710
963
|
<HeightVarsExample />
|
|
711
964
|
</App>
|
|
712
965
|
);
|
|
713
966
|
|
|
714
|
-
render(<BaseExample />);
|
|
715
|
-
|
|
716
967
|
```
|
|
717
968
|
|
|
718
969
|
- title / footer / noPadding 高度探针
|
|
719
|
-
-
|
|
970
|
+
- Modal / Drawer 切换:title、footer、noPadding、bodyScroll 组合实测高度变量
|
|
720
971
|
- _ReactModal(@kne/current-lib_react-modal)[import * as _ReactModal from "@kne/react-modal"],(@kne/current-lib_react-modal/dist/index.css),antd(antd)
|
|
721
972
|
|
|
722
973
|
```jsx
|
|
723
|
-
const { default: Modal } = _ReactModal;
|
|
724
|
-
const { Button, Space, Switch, Radio, Tag, Descriptions, App } = antd;
|
|
974
|
+
const { default: Modal, Drawer, DrawerContextHolder } = _ReactModal;
|
|
975
|
+
const { Button, Space, Switch, Radio, Tag, Descriptions, Typography, App } = antd;
|
|
725
976
|
const { useState, useEffect } = React;
|
|
726
977
|
|
|
978
|
+
const { Text } = Typography;
|
|
979
|
+
|
|
727
980
|
const readCssVar = (el, name) => {
|
|
728
981
|
if (!el) {
|
|
729
982
|
return '-';
|
|
@@ -731,8 +984,22 @@ const readCssVar = (el, name) => {
|
|
|
731
984
|
return getComputedStyle(el).getPropertyValue(name).trim() || '-';
|
|
732
985
|
};
|
|
733
986
|
|
|
734
|
-
const
|
|
987
|
+
const getOverlayChrome = mode => {
|
|
988
|
+
const isDrawer = mode === 'drawer';
|
|
989
|
+
return {
|
|
990
|
+
isDrawer,
|
|
991
|
+
testId: isDrawer ? 'react-drawer' : 'react-modal',
|
|
992
|
+
bodyClass: isDrawer ? 'drawer-body' : 'modal-body',
|
|
993
|
+
innerClass: isDrawer ? 'drawer-body-inner' : 'modal-body-inner',
|
|
994
|
+
contentHeightVar: isDrawer ? '--kne-drawer-content-height' : '--kne-modal-content-height',
|
|
995
|
+
bodyHeightVar: isDrawer ? '--kne-drawer-body-height' : '--kne-modal-body-height',
|
|
996
|
+
varPrefix: isDrawer ? '--kne-drawer' : '--kne-modal'
|
|
997
|
+
};
|
|
998
|
+
};
|
|
999
|
+
|
|
1000
|
+
const HeightProbe = ({ open, revision, mode }) => {
|
|
735
1001
|
const [metrics, setMetrics] = useState(null);
|
|
1002
|
+
const chrome = getOverlayChrome(mode);
|
|
736
1003
|
|
|
737
1004
|
useEffect(() => {
|
|
738
1005
|
if (!open) {
|
|
@@ -740,39 +1007,39 @@ const HeightProbe = ({ open, revision }) => {
|
|
|
740
1007
|
return undefined;
|
|
741
1008
|
}
|
|
742
1009
|
const timer = setTimeout(() => {
|
|
743
|
-
const outer = document.querySelector(
|
|
744
|
-
const body = outer && outer.querySelector(
|
|
745
|
-
const inner = outer && outer.querySelector(
|
|
1010
|
+
const outer = document.querySelector(`[data-testid="${chrome.testId}"]`);
|
|
1011
|
+
const body = outer && outer.querySelector(`.${chrome.bodyClass}`);
|
|
1012
|
+
const inner = outer && outer.querySelector(`.${chrome.innerClass}`);
|
|
746
1013
|
const scrollHost = outer && outer.querySelector('[data-height-scroll-host]');
|
|
747
1014
|
setMetrics({
|
|
748
1015
|
outerClass: outer ? outer.className : '-',
|
|
749
|
-
bodyHeightVar: readCssVar(body || outer,
|
|
750
|
-
contentHeightVar: readCssVar(inner || body || outer,
|
|
751
|
-
paddingV: readCssVar(outer,
|
|
752
|
-
paddingH: readCssVar(outer,
|
|
753
|
-
titleH: readCssVar(outer,
|
|
754
|
-
footerH: readCssVar(outer,
|
|
1016
|
+
bodyHeightVar: readCssVar(body || outer, chrome.bodyHeightVar),
|
|
1017
|
+
contentHeightVar: readCssVar(inner || body || outer, chrome.contentHeightVar),
|
|
1018
|
+
paddingV: readCssVar(outer, `${chrome.varPrefix}-body-padding-vertical`),
|
|
1019
|
+
paddingH: readCssVar(outer, `${chrome.varPrefix}-body-padding-horizontal`),
|
|
1020
|
+
titleH: readCssVar(outer, `${chrome.varPrefix}-title-height`),
|
|
1021
|
+
footerH: readCssVar(outer, `${chrome.varPrefix}-footer-height`),
|
|
755
1022
|
bodyClient: body ? `${body.clientHeight}px` : '-',
|
|
756
1023
|
innerClient: inner ? `${inner.clientHeight}px` : '-',
|
|
757
1024
|
scrollClient: scrollHost ? `${scrollHost.clientHeight}px` : '-'
|
|
758
1025
|
});
|
|
759
1026
|
}, 80);
|
|
760
1027
|
return () => clearTimeout(timer);
|
|
761
|
-
}, [open, revision]);
|
|
1028
|
+
}, [open, revision, mode, chrome]);
|
|
762
1029
|
|
|
763
1030
|
if (!metrics) {
|
|
764
|
-
return <Tag
|
|
1031
|
+
return <Tag>打开弹层后显示实测高度</Tag>;
|
|
765
1032
|
}
|
|
766
1033
|
|
|
767
1034
|
return (
|
|
768
1035
|
<Descriptions size="small" bordered column={1} style={{ maxWidth: 640 }}>
|
|
769
1036
|
<Descriptions.Item label="outer class">{metrics.outerClass}</Descriptions.Item>
|
|
770
|
-
<Descriptions.Item label=
|
|
771
|
-
<Descriptions.Item label=
|
|
772
|
-
<Descriptions.Item label=
|
|
773
|
-
<Descriptions.Item label=
|
|
774
|
-
<Descriptions.Item label=
|
|
775
|
-
<Descriptions.Item label=
|
|
1037
|
+
<Descriptions.Item label={`${chrome.varPrefix}-title-height`}>{metrics.titleH}</Descriptions.Item>
|
|
1038
|
+
<Descriptions.Item label={`${chrome.varPrefix}-footer-height`}>{metrics.footerH}</Descriptions.Item>
|
|
1039
|
+
<Descriptions.Item label={`${chrome.varPrefix}-body-padding-vertical`}>{metrics.paddingV}</Descriptions.Item>
|
|
1040
|
+
<Descriptions.Item label={`${chrome.varPrefix}-body-padding-horizontal`}>{metrics.paddingH}</Descriptions.Item>
|
|
1041
|
+
<Descriptions.Item label={chrome.bodyHeightVar}>{metrics.bodyHeightVar}</Descriptions.Item>
|
|
1042
|
+
<Descriptions.Item label={chrome.contentHeightVar}>{metrics.contentHeightVar}</Descriptions.Item>
|
|
776
1043
|
<Descriptions.Item label="body.clientHeight">{metrics.bodyClient}</Descriptions.Item>
|
|
777
1044
|
<Descriptions.Item label="body-inner.clientHeight">{metrics.innerClient}</Descriptions.Item>
|
|
778
1045
|
<Descriptions.Item label="scrollHost.clientHeight">{metrics.scrollClient}</Descriptions.Item>
|
|
@@ -783,39 +1050,59 @@ const HeightProbe = ({ open, revision }) => {
|
|
|
783
1050
|
const ChromeHeightExample = () => {
|
|
784
1051
|
const [open, setOpen] = useState(false);
|
|
785
1052
|
const [hasTitle, setHasTitle] = useState(true);
|
|
786
|
-
const [footerMode, setFooterMode] = useState('default');
|
|
1053
|
+
const [footerMode, setFooterMode] = useState('default');
|
|
787
1054
|
const [bodyScroll, setBodyScroll] = useState(false);
|
|
788
|
-
const [noPaddingMode, setNoPaddingMode] = useState('auto');
|
|
789
|
-
const
|
|
1055
|
+
const [noPaddingMode, setNoPaddingMode] = useState('auto');
|
|
1056
|
+
const [mode, setMode] = useState('modal');
|
|
1057
|
+
const isDrawer = mode === 'drawer';
|
|
790
1058
|
|
|
791
|
-
|
|
792
|
-
|
|
1059
|
+
useEffect(() => {
|
|
1060
|
+
if (open) {
|
|
1061
|
+
setOpen(false);
|
|
1062
|
+
}
|
|
1063
|
+
}, [mode]);
|
|
1064
|
+
|
|
1065
|
+
const chrome = getOverlayChrome(mode);
|
|
1066
|
+
const Overlay = isDrawer ? Drawer : Modal;
|
|
1067
|
+
const revision = [mode, hasTitle, footerMode, bodyScroll, noPaddingMode, open].join('|');
|
|
793
1068
|
|
|
794
|
-
const
|
|
1069
|
+
const noPaddingProp = noPaddingMode === 'auto' ? undefined : noPaddingMode === 'true';
|
|
1070
|
+
|
|
1071
|
+
const overlayProps = {
|
|
795
1072
|
open,
|
|
796
1073
|
onClose: () => setOpen(false),
|
|
797
1074
|
bodyScroll,
|
|
798
1075
|
...(hasTitle ? { title: '候选人评估 · 高度调试' } : {}),
|
|
799
|
-
...(noPaddingProp === undefined ? {} : { noPadding: noPaddingProp })
|
|
1076
|
+
...(noPaddingProp === undefined ? {} : { noPadding: noPaddingProp }),
|
|
1077
|
+
size: isDrawer ? 'default' : undefined
|
|
800
1078
|
};
|
|
801
1079
|
|
|
802
1080
|
if (footerMode === 'null') {
|
|
803
|
-
|
|
1081
|
+
overlayProps.footer = null;
|
|
804
1082
|
} else if (footerMode === 'emptyButtons') {
|
|
805
|
-
|
|
806
|
-
|
|
1083
|
+
overlayProps.footer = <span>仅左侧 footer,按钮为空数组</span>;
|
|
1084
|
+
overlayProps.footerButtons = [];
|
|
807
1085
|
} else {
|
|
808
|
-
|
|
1086
|
+
overlayProps.onConfirm = () => {};
|
|
809
1087
|
}
|
|
810
1088
|
|
|
811
|
-
const openModal = () => {
|
|
812
|
-
setOpen(true);
|
|
813
|
-
};
|
|
814
|
-
|
|
815
1089
|
const lines = Array.from({ length: 24 }, (_, i) => `行 ${i + 1} · 用于观察滚动与高度是否贴齐`);
|
|
816
1090
|
|
|
817
1091
|
return (
|
|
818
1092
|
<Space direction="vertical" style={{ width: '100%' }} size="middle">
|
|
1093
|
+
<Space align="center" wrap>
|
|
1094
|
+
<Text type="secondary">打开方式</Text>
|
|
1095
|
+
<Radio.Group
|
|
1096
|
+
value={mode}
|
|
1097
|
+
optionType="button"
|
|
1098
|
+
size="small"
|
|
1099
|
+
options={[
|
|
1100
|
+
{ label: 'Modal', value: 'modal' },
|
|
1101
|
+
{ label: 'Drawer', value: 'drawer' }
|
|
1102
|
+
]}
|
|
1103
|
+
onChange={e => setMode(e.target.value)}
|
|
1104
|
+
/>
|
|
1105
|
+
</Space>
|
|
819
1106
|
<Space wrap align="center">
|
|
820
1107
|
<span>title</span>
|
|
821
1108
|
<Switch checked={hasTitle} onChange={setHasTitle} checkedChildren="有" unCheckedChildren="空" />
|
|
@@ -852,29 +1139,21 @@ const ChromeHeightExample = () => {
|
|
|
852
1139
|
]}
|
|
853
1140
|
onChange={e => setNoPaddingMode(e.target.value)}
|
|
854
1141
|
/>
|
|
855
|
-
<div style={{ marginTop: 8, color: 'rgba(0,0,0,0.45)' }}>
|
|
856
|
-
bodyScroll=false 且未传 noPadding 时默认去掉内边距;显式 false 可保留 padding 观察差异。
|
|
857
|
-
</div>
|
|
858
1142
|
</div>
|
|
859
1143
|
<Space wrap>
|
|
860
|
-
<Button type="primary" onClick={
|
|
861
|
-
|
|
1144
|
+
<Button type="primary" onClick={() => setOpen(true)}>
|
|
1145
|
+
打开并测量
|
|
862
1146
|
</Button>
|
|
863
|
-
<Tag>
|
|
1147
|
+
<Tag>mode={mode}</Tag>
|
|
864
1148
|
<Tag>footerMode={footerMode}</Tag>
|
|
865
|
-
<Tag>bodyScroll={String(bodyScroll)}</Tag>
|
|
866
|
-
<Tag>
|
|
867
|
-
noPadding=
|
|
868
|
-
{noPaddingProp === undefined ? 'undefined(auto)' : String(noPaddingProp)}
|
|
869
|
-
</Tag>
|
|
870
1149
|
</Space>
|
|
871
|
-
<HeightProbe open={open} revision={revision} />
|
|
872
|
-
<
|
|
1150
|
+
<HeightProbe open={open} revision={revision} mode={mode} />
|
|
1151
|
+
<Overlay {...overlayProps}>
|
|
873
1152
|
{bodyScroll === false ? (
|
|
874
1153
|
<div
|
|
875
1154
|
data-height-scroll-host
|
|
876
1155
|
style={{
|
|
877
|
-
height:
|
|
1156
|
+
height: `var(${chrome.contentHeightVar})`,
|
|
878
1157
|
minHeight: 0,
|
|
879
1158
|
overflow: 'auto',
|
|
880
1159
|
boxSizing: 'border-box',
|
|
@@ -884,8 +1163,7 @@ const ChromeHeightExample = () => {
|
|
|
884
1163
|
>
|
|
885
1164
|
<div style={{ padding: 12 }}>
|
|
886
1165
|
<p style={{ marginTop: 0, fontWeight: 600 }}>
|
|
887
|
-
|
|
888
|
-
内容区同高;footer=null 时 footer 变量应为 0。
|
|
1166
|
+
滚动宿主 height: var({chrome.contentHeightVar})。footer=null 时 footer 变量应为 0。
|
|
889
1167
|
</p>
|
|
890
1168
|
{lines.map(text => (
|
|
891
1169
|
<p key={text}>{text}</p>
|
|
@@ -909,31 +1187,32 @@ const ChromeHeightExample = () => {
|
|
|
909
1187
|
</div>
|
|
910
1188
|
</div>
|
|
911
1189
|
)}
|
|
912
|
-
</
|
|
1190
|
+
</Overlay>
|
|
913
1191
|
</Space>
|
|
914
1192
|
);
|
|
915
1193
|
};
|
|
916
1194
|
|
|
917
|
-
|
|
1195
|
+
render(
|
|
918
1196
|
<App>
|
|
1197
|
+
<DrawerContextHolder />
|
|
919
1198
|
<ChromeHeightExample />
|
|
920
1199
|
</App>
|
|
921
1200
|
);
|
|
922
1201
|
|
|
923
|
-
render(<BaseExample />);
|
|
924
|
-
|
|
925
1202
|
```
|
|
926
1203
|
|
|
927
1204
|
- 批量候选人评估(Tabs + 分栏)
|
|
928
|
-
-
|
|
1205
|
+
- Modal / Drawer 切换:Tabs 分栏 / Splitter / 批次概览
|
|
929
1206
|
- _ReactModal(@kne/current-lib_react-modal)[import * as _ReactModal from "@kne/react-modal"],(@kne/current-lib_react-modal/dist/index.css),antd(antd),(@kne/current-lib_react-modal/doc/style.scss)
|
|
930
1207
|
|
|
931
1208
|
```jsx
|
|
932
1209
|
const {
|
|
933
1210
|
default: Modal,
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
1211
|
+
Drawer,
|
|
1212
|
+
DrawerContextHolder,
|
|
1213
|
+
TabsLayout,
|
|
1214
|
+
ColumnsLayout,
|
|
1215
|
+
ScrollRegion,
|
|
937
1216
|
modalClassNames
|
|
938
1217
|
} = _ReactModal;
|
|
939
1218
|
const {
|
|
@@ -948,9 +1227,10 @@ const {
|
|
|
948
1227
|
Typography,
|
|
949
1228
|
Divider,
|
|
950
1229
|
App,
|
|
951
|
-
message
|
|
1230
|
+
message,
|
|
1231
|
+
Radio
|
|
952
1232
|
} = antd;
|
|
953
|
-
const { useState, useMemo } = React;
|
|
1233
|
+
const { useState, useMemo, useEffect } = React;
|
|
954
1234
|
|
|
955
1235
|
const { Text, Title, Paragraph } = Typography;
|
|
956
1236
|
|
|
@@ -1234,8 +1514,8 @@ const ColumnsPane = () => {
|
|
|
1234
1514
|
const { setActive, search, setSearch, filtered, current } = useCandidatePanel();
|
|
1235
1515
|
|
|
1236
1516
|
return (
|
|
1237
|
-
<
|
|
1238
|
-
<
|
|
1517
|
+
<ColumnsLayout widths={['34%', '1fr']}>
|
|
1518
|
+
<ScrollRegion>
|
|
1239
1519
|
<CandidateList
|
|
1240
1520
|
items={filtered}
|
|
1241
1521
|
activeKey={current?.key}
|
|
@@ -1243,11 +1523,11 @@ const ColumnsPane = () => {
|
|
|
1243
1523
|
search={search}
|
|
1244
1524
|
onSearchChange={setSearch}
|
|
1245
1525
|
/>
|
|
1246
|
-
</
|
|
1247
|
-
<
|
|
1526
|
+
</ScrollRegion>
|
|
1527
|
+
<ScrollRegion inset>
|
|
1248
1528
|
<CandidateDetail candidate={current} />
|
|
1249
|
-
</
|
|
1250
|
-
</
|
|
1529
|
+
</ScrollRegion>
|
|
1530
|
+
</ColumnsLayout>
|
|
1251
1531
|
);
|
|
1252
1532
|
};
|
|
1253
1533
|
|
|
@@ -1265,7 +1545,7 @@ const SplitterPane = () => {
|
|
|
1265
1545
|
max="52%"
|
|
1266
1546
|
>
|
|
1267
1547
|
<Splitter.Panel>
|
|
1268
|
-
<
|
|
1548
|
+
<ScrollRegion>
|
|
1269
1549
|
<CandidateList
|
|
1270
1550
|
items={filtered}
|
|
1271
1551
|
activeKey={current?.key}
|
|
@@ -1273,12 +1553,12 @@ const SplitterPane = () => {
|
|
|
1273
1553
|
search={search}
|
|
1274
1554
|
onSearchChange={setSearch}
|
|
1275
1555
|
/>
|
|
1276
|
-
</
|
|
1556
|
+
</ScrollRegion>
|
|
1277
1557
|
</Splitter.Panel>
|
|
1278
1558
|
<Splitter.Panel>
|
|
1279
|
-
<
|
|
1559
|
+
<ScrollRegion inset>
|
|
1280
1560
|
<CandidateDetail candidate={current} />
|
|
1281
|
-
</
|
|
1561
|
+
</ScrollRegion>
|
|
1282
1562
|
</Splitter.Panel>
|
|
1283
1563
|
</Splitter>
|
|
1284
1564
|
</div>
|
|
@@ -1302,7 +1582,7 @@ const OverviewPane = () => {
|
|
|
1302
1582
|
];
|
|
1303
1583
|
|
|
1304
1584
|
return (
|
|
1305
|
-
<
|
|
1585
|
+
<ScrollRegion>
|
|
1306
1586
|
<div className="candidate-overview-stats">
|
|
1307
1587
|
{[
|
|
1308
1588
|
{ label: '本批人数', value: stats.total },
|
|
@@ -1344,7 +1624,7 @@ const OverviewPane = () => {
|
|
|
1344
1624
|
})}
|
|
1345
1625
|
<Divider />
|
|
1346
1626
|
<Paragraph type="secondary" style={{ marginBottom: 0 }}>
|
|
1347
|
-
概览 Tab 同样使用
|
|
1627
|
+
概览 Tab 同样使用 ScrollRegion:批次统计与日程较长时在本面板内滚动,不影响 Tabs 顶栏与底部操作区。
|
|
1348
1628
|
</Paragraph>
|
|
1349
1629
|
{Array.from({ length: 6 }, (_, i) => (
|
|
1350
1630
|
<Paragraph key={i} style={{ marginBottom: 8, color: 'rgba(0,0,0,0.45)' }}>
|
|
@@ -1352,25 +1632,60 @@ const OverviewPane = () => {
|
|
|
1352
1632
|
</Paragraph>
|
|
1353
1633
|
))}
|
|
1354
1634
|
</div>
|
|
1355
|
-
</
|
|
1635
|
+
</ScrollRegion>
|
|
1356
1636
|
);
|
|
1357
1637
|
};
|
|
1358
1638
|
|
|
1359
1639
|
const ExtendLayoutExample = () => {
|
|
1360
1640
|
const [open, setOpen] = useState(false);
|
|
1361
1641
|
const [tab, setTab] = useState('columns');
|
|
1642
|
+
const [mode, setMode] = useState('modal');
|
|
1643
|
+
const isDrawer = mode === 'drawer';
|
|
1644
|
+
|
|
1645
|
+
useEffect(() => {
|
|
1646
|
+
if (open) {
|
|
1647
|
+
setOpen(false);
|
|
1648
|
+
}
|
|
1649
|
+
}, [mode]);
|
|
1650
|
+
|
|
1651
|
+
const Overlay = isDrawer ? Drawer : Modal;
|
|
1652
|
+
|
|
1653
|
+
const layout = (
|
|
1654
|
+
<TabsLayout
|
|
1655
|
+
activeKey={tab}
|
|
1656
|
+
onChange={setTab}
|
|
1657
|
+
items={[
|
|
1658
|
+
{ key: 'columns', label: '候选人', children: <ColumnsPane /> },
|
|
1659
|
+
{ key: 'split', label: '可调分栏', children: <SplitterPane /> },
|
|
1660
|
+
{ key: 'overview', label: '批次概览', children: <OverviewPane /> }
|
|
1661
|
+
]}
|
|
1662
|
+
/>
|
|
1663
|
+
);
|
|
1362
1664
|
|
|
1363
1665
|
return (
|
|
1364
1666
|
<Space direction="vertical" style={{ width: '100%' }} size="middle">
|
|
1667
|
+
<Space align="center" wrap>
|
|
1668
|
+
<Text type="secondary">打开方式</Text>
|
|
1669
|
+
<Radio.Group
|
|
1670
|
+
value={mode}
|
|
1671
|
+
optionType="button"
|
|
1672
|
+
size="small"
|
|
1673
|
+
options={[
|
|
1674
|
+
{ label: 'Modal', value: 'modal' },
|
|
1675
|
+
{ label: 'Drawer', value: 'drawer' }
|
|
1676
|
+
]}
|
|
1677
|
+
onChange={e => setMode(e.target.value)}
|
|
1678
|
+
/>
|
|
1679
|
+
</Space>
|
|
1365
1680
|
<div>
|
|
1366
1681
|
<Button type="primary" onClick={() => setOpen(true)}>
|
|
1367
|
-
|
|
1682
|
+
打开批量评估弹层
|
|
1368
1683
|
</Button>
|
|
1369
1684
|
<Paragraph type="secondary" style={{ margin: '8px 0 0' }}>
|
|
1370
|
-
模拟 HR 批量面试评估:Tabs 占 title
|
|
1685
|
+
模拟 HR 批量面试评估:Tabs 占 title 位,左列表右详情;切换 Modal / Drawer 对比布局高度链。
|
|
1371
1686
|
</Paragraph>
|
|
1372
1687
|
</div>
|
|
1373
|
-
<
|
|
1688
|
+
<Overlay
|
|
1374
1689
|
open={open}
|
|
1375
1690
|
onClose={() => setOpen(false)}
|
|
1376
1691
|
bodyScroll={false}
|
|
@@ -1382,39 +1697,30 @@ const ExtendLayoutExample = () => {
|
|
|
1382
1697
|
confirmText="保存本批评估"
|
|
1383
1698
|
cancelText="稍后处理"
|
|
1384
1699
|
>
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
onChange={setTab}
|
|
1388
|
-
items={[
|
|
1389
|
-
{ key: 'columns', label: '候选人', children: <ColumnsPane /> },
|
|
1390
|
-
{ key: 'split', label: '可调分栏', children: <SplitterPane /> },
|
|
1391
|
-
{ key: 'overview', label: '批次概览', children: <OverviewPane /> }
|
|
1392
|
-
]}
|
|
1393
|
-
/>
|
|
1394
|
-
</Modal>
|
|
1700
|
+
{layout}
|
|
1701
|
+
</Overlay>
|
|
1395
1702
|
</Space>
|
|
1396
1703
|
);
|
|
1397
1704
|
};
|
|
1398
1705
|
|
|
1399
|
-
|
|
1706
|
+
render(
|
|
1400
1707
|
<App>
|
|
1708
|
+
<DrawerContextHolder />
|
|
1401
1709
|
<ExtendLayoutExample />
|
|
1402
1710
|
</App>
|
|
1403
1711
|
);
|
|
1404
1712
|
|
|
1405
|
-
render(<BaseExample />);
|
|
1406
|
-
|
|
1407
1713
|
```
|
|
1408
1714
|
|
|
1409
|
-
-
|
|
1410
|
-
-
|
|
1715
|
+
- 嵌套弹层(外层命令式 + 内层 Modal)
|
|
1716
|
+
- Modal / Drawer 切换外层;内层声明式 Modal 嵌套 hoist
|
|
1411
1717
|
- _ReactModal(@kne/current-lib_react-modal)[import * as _ReactModal from "@kne/react-modal"],(@kne/current-lib_react-modal/dist/index.css),antd(antd)
|
|
1412
1718
|
|
|
1413
1719
|
```jsx
|
|
1414
|
-
const { default: Modal, useModal } = _ReactModal;
|
|
1415
|
-
const { Button, Space, Typography, Tag,
|
|
1720
|
+
const { default: Modal, useModal, useDrawer, DrawerContextHolder } = _ReactModal;
|
|
1721
|
+
const { Button, Space, Typography, Tag, message, Checkbox, Radio, App } = antd;
|
|
1416
1722
|
const { useState, useEffect } = React;
|
|
1417
|
-
const { Text, Paragraph
|
|
1723
|
+
const { Text, Paragraph } = Typography;
|
|
1418
1724
|
|
|
1419
1725
|
const EXPORT_FIELDS = [
|
|
1420
1726
|
{ label: '基本信息', value: 'basic' },
|
|
@@ -1423,7 +1729,7 @@ const EXPORT_FIELDS = [
|
|
|
1423
1729
|
{ label: '附件简历', value: 'resume' }
|
|
1424
1730
|
];
|
|
1425
1731
|
|
|
1426
|
-
const NestProbe = ({ open }) => {
|
|
1732
|
+
const NestProbe = ({ open, isDrawer }) => {
|
|
1427
1733
|
const [info, setInfo] = useState(null);
|
|
1428
1734
|
|
|
1429
1735
|
useEffect(() => {
|
|
@@ -1432,46 +1738,40 @@ const NestProbe = ({ open }) => {
|
|
|
1432
1738
|
return;
|
|
1433
1739
|
}
|
|
1434
1740
|
const id = window.setTimeout(() => {
|
|
1435
|
-
const
|
|
1436
|
-
const
|
|
1741
|
+
const outerRootClass = isDrawer ? '.ant-drawer-root' : '.ant-modal-root';
|
|
1742
|
+
const titleClass = isDrawer ? '.drawer-title' : '.modal-title';
|
|
1743
|
+
const titles = Array.from(document.querySelectorAll(`${outerRootClass} ${titleClass}`));
|
|
1744
|
+
const outerTitle = titles.find(el => el.textContent === (isDrawer ? '导出评估报告(侧滑)' : '导出评估报告'));
|
|
1437
1745
|
const innerTitle = titles.find(el => el.textContent === '选择导出字段');
|
|
1438
|
-
const outerRoot = outerTitle?.closest(
|
|
1746
|
+
const outerRoot = outerTitle?.closest(outerRootClass.slice(1));
|
|
1439
1747
|
const innerRoot = innerTitle?.closest('.ant-modal-root');
|
|
1440
|
-
const outerBody = outerRoot?.querySelector('.ant-modal-body');
|
|
1441
|
-
const outerZ = Number(outerRoot?.querySelector('.ant-modal-wrap')?.style?.zIndex || 0);
|
|
1442
|
-
const innerZ = Number(innerRoot?.querySelector('.ant-modal-wrap')?.style?.zIndex || 0);
|
|
1748
|
+
const outerBody = outerRoot?.querySelector(isDrawer ? '.ant-drawer-body' : '.ant-modal-body');
|
|
1443
1749
|
const hoisted = !!(innerRoot && outerBody && !outerBody.contains(innerRoot));
|
|
1444
|
-
setInfo({ hoisted
|
|
1750
|
+
setInfo({ hoisted });
|
|
1445
1751
|
}, 50);
|
|
1446
1752
|
return () => window.clearTimeout(id);
|
|
1447
|
-
}, [open]);
|
|
1753
|
+
}, [open, isDrawer]);
|
|
1448
1754
|
|
|
1449
1755
|
if (!info) {
|
|
1450
1756
|
return <Text type="secondary">打开内层后显示挂载探针</Text>;
|
|
1451
1757
|
}
|
|
1452
1758
|
|
|
1453
1759
|
return (
|
|
1454
|
-
<
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
<Tag color={info.hoisted ? 'success' : 'error'} style={{ marginLeft: 8 }}>
|
|
1458
|
-
{info.hoisted ? '外层 modal-root 外侧' : '异常'}
|
|
1459
|
-
</Tag>
|
|
1460
|
-
</span>
|
|
1461
|
-
<Text type="secondary">zIndex 外层 {info.outerZ} / 内层 {info.innerZ}(antd 管理)</Text>
|
|
1462
|
-
</Space>
|
|
1760
|
+
<Tag color={info.hoisted ? 'success' : 'error'}>
|
|
1761
|
+
内层 Modal {info.hoisted ? '已 hoist 到外层外侧' : '挂载异常'}
|
|
1762
|
+
</Tag>
|
|
1463
1763
|
);
|
|
1464
1764
|
};
|
|
1465
1765
|
|
|
1466
|
-
const ExportFieldPicker = () => {
|
|
1766
|
+
const ExportFieldPicker = ({ isDrawer }) => {
|
|
1467
1767
|
const [innerOpen, setInnerOpen] = useState(false);
|
|
1468
1768
|
const [checked, setChecked] = useState(['basic', 'scores', 'notes']);
|
|
1469
1769
|
|
|
1470
1770
|
return (
|
|
1471
1771
|
<div>
|
|
1472
1772
|
<Paragraph>
|
|
1473
|
-
已选择本批 <Text strong>8</Text>
|
|
1474
|
-
<Text code>Modal</Text
|
|
1773
|
+
已选择本批 <Text strong>8</Text> 位候选人。内层始终为声明式 Modal;外层当前为{' '}
|
|
1774
|
+
<Text code>{isDrawer ? 'Drawer' : 'Modal'}</Text>。
|
|
1475
1775
|
</Paragraph>
|
|
1476
1776
|
<Space>
|
|
1477
1777
|
<Button type="primary" onClick={() => setInnerOpen(true)}>
|
|
@@ -1480,7 +1780,7 @@ const ExportFieldPicker = () => {
|
|
|
1480
1780
|
<Text type="secondary">已选 {checked.length} 项</Text>
|
|
1481
1781
|
</Space>
|
|
1482
1782
|
<div style={{ marginTop: 12 }}>
|
|
1483
|
-
<NestProbe open={innerOpen} />
|
|
1783
|
+
<NestProbe open={innerOpen} isDrawer={isDrawer} />
|
|
1484
1784
|
</div>
|
|
1485
1785
|
<Modal
|
|
1486
1786
|
title="选择导出字段"
|
|
@@ -1506,47 +1806,63 @@ const ExportFieldPicker = () => {
|
|
|
1506
1806
|
|
|
1507
1807
|
const NestedModalExample = () => {
|
|
1508
1808
|
const modal = useModal();
|
|
1809
|
+
const drawer = useDrawer();
|
|
1810
|
+
const [mode, setMode] = useState('modal');
|
|
1811
|
+
const isDrawer = mode === 'drawer';
|
|
1509
1812
|
|
|
1510
1813
|
return (
|
|
1511
1814
|
<Space direction="vertical" style={{ width: '100%' }} size="middle">
|
|
1815
|
+
<Space align="center" wrap>
|
|
1816
|
+
<Text type="secondary">打开方式</Text>
|
|
1817
|
+
<Radio.Group
|
|
1818
|
+
value={mode}
|
|
1819
|
+
optionType="button"
|
|
1820
|
+
size="small"
|
|
1821
|
+
options={[
|
|
1822
|
+
{ label: 'Modal', value: 'modal' },
|
|
1823
|
+
{ label: 'Drawer', value: 'drawer' }
|
|
1824
|
+
]}
|
|
1825
|
+
onChange={e => setMode(e.target.value)}
|
|
1826
|
+
/>
|
|
1827
|
+
</Space>
|
|
1512
1828
|
<Button
|
|
1513
1829
|
type="primary"
|
|
1514
1830
|
onClick={() => {
|
|
1515
|
-
modal
|
|
1516
|
-
|
|
1831
|
+
const api = isDrawer ? drawer : modal;
|
|
1832
|
+
api({
|
|
1833
|
+
title: isDrawer ? '导出评估报告(侧滑)' : '导出评估报告',
|
|
1517
1834
|
size: 'default',
|
|
1518
|
-
children: <ExportFieldPicker />,
|
|
1835
|
+
children: <ExportFieldPicker isDrawer={isDrawer} />,
|
|
1519
1836
|
onConfirm: () => message.success('报告已进入生成队列'),
|
|
1520
1837
|
confirmText: '开始导出'
|
|
1521
1838
|
});
|
|
1522
1839
|
}}
|
|
1523
1840
|
>
|
|
1524
|
-
|
|
1841
|
+
导出本批评估(嵌套弹层)
|
|
1525
1842
|
</Button>
|
|
1526
|
-
<Text type="secondary">外层
|
|
1843
|
+
<Text type="secondary">外层 Modal / Drawer 命令式 + 内层声明式 Modal 嵌套 hoist。</Text>
|
|
1527
1844
|
</Space>
|
|
1528
1845
|
);
|
|
1529
1846
|
};
|
|
1530
1847
|
|
|
1531
|
-
|
|
1848
|
+
render(
|
|
1532
1849
|
<App>
|
|
1850
|
+
<DrawerContextHolder />
|
|
1533
1851
|
<NestedModalExample />
|
|
1534
1852
|
</App>
|
|
1535
1853
|
);
|
|
1536
1854
|
|
|
1537
|
-
render(<BaseExample />);
|
|
1538
|
-
|
|
1539
1855
|
```
|
|
1540
1856
|
|
|
1541
|
-
-
|
|
1542
|
-
-
|
|
1857
|
+
- FormModal + renderModal
|
|
1858
|
+
- Modal / Drawer 切换:超长表单,createModalRender / createDrawerRender
|
|
1543
1859
|
- _ReactModal(@kne/current-lib_react-modal)[import * as _ReactModal from "@kne/react-modal"],(@kne/current-lib_react-modal/dist/index.css),_FormInfo(@kne/form-info)[import * as _FormInfo from "@kne/form-info"],(@kne/form-info/dist/index.css),antd(antd)
|
|
1544
1860
|
|
|
1545
1861
|
```jsx
|
|
1546
|
-
const { createModalRender } = _ReactModal;
|
|
1862
|
+
const { createModalRender, createDrawerRender, DrawerContextHolder } = _ReactModal;
|
|
1547
1863
|
const { default: FormInfo, FormModal, Input, TextArea } = _FormInfo;
|
|
1548
|
-
const { Button, Space, Typography, App, message, Switch, Flex } = antd;
|
|
1549
|
-
const { useState, useMemo } = React;
|
|
1864
|
+
const { Button, Space, Typography, App, message, Switch, Flex, Radio } = antd;
|
|
1865
|
+
const { useState, useMemo, useEffect } = React;
|
|
1550
1866
|
const { Text, Paragraph } = Typography;
|
|
1551
1867
|
|
|
1552
1868
|
const SECTION_DEFS = [
|
|
@@ -1668,13 +1984,19 @@ const LongFormFields = () => (
|
|
|
1668
1984
|
const FormInfoModalExample = () => {
|
|
1669
1985
|
const [open, setOpen] = useState(false);
|
|
1670
1986
|
const [bodyScroll, setBodyScroll] = useState(true);
|
|
1987
|
+
const [mode, setMode] = useState('modal');
|
|
1988
|
+
const isDrawer = mode === 'drawer';
|
|
1671
1989
|
const initialData = useMemo(() => buildInitialData(), []);
|
|
1672
1990
|
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
});
|
|
1991
|
+
useEffect(() => {
|
|
1992
|
+
if (open) {
|
|
1993
|
+
setOpen(false);
|
|
1994
|
+
}
|
|
1995
|
+
}, [mode]);
|
|
1996
|
+
|
|
1997
|
+
const renderModalBase = isDrawer
|
|
1998
|
+
? createDrawerRender({ placement: 'right', footerButtons: [], bodyScroll: true, size: 'large' })
|
|
1999
|
+
: createModalRender({ footerButtons: [], bodyScroll: true, size: 'large' });
|
|
1678
2000
|
|
|
1679
2001
|
const renderModal = ({
|
|
1680
2002
|
formProps,
|
|
@@ -1697,6 +2019,19 @@ const FormInfoModalExample = () => {
|
|
|
1697
2019
|
|
|
1698
2020
|
return (
|
|
1699
2021
|
<Space direction="vertical" style={{ width: '100%' }}>
|
|
2022
|
+
<Space align="center" wrap>
|
|
2023
|
+
<Text type="secondary">打开方式</Text>
|
|
2024
|
+
<Radio.Group
|
|
2025
|
+
value={mode}
|
|
2026
|
+
optionType="button"
|
|
2027
|
+
size="small"
|
|
2028
|
+
options={[
|
|
2029
|
+
{ label: 'Modal', value: 'modal' },
|
|
2030
|
+
{ label: 'Drawer', value: 'drawer' }
|
|
2031
|
+
]}
|
|
2032
|
+
onChange={e => setMode(e.target.value)}
|
|
2033
|
+
/>
|
|
2034
|
+
</Space>
|
|
1700
2035
|
<Space wrap>
|
|
1701
2036
|
<Button type="primary" onClick={() => setOpen(true)}>
|
|
1702
2037
|
打开深度评估表单(FormModal)
|
|
@@ -1707,11 +2042,11 @@ const FormInfoModalExample = () => {
|
|
|
1707
2042
|
</Space>
|
|
1708
2043
|
</Space>
|
|
1709
2044
|
<Text type="secondary">
|
|
1710
|
-
createModalRender
|
|
2045
|
+
createModalRender / createDrawerRender 注入默认 props;切换 Modal / Drawer 对比 form-info 宿主集成。
|
|
1711
2046
|
</Text>
|
|
1712
2047
|
|
|
1713
2048
|
<FormModal
|
|
1714
|
-
title=
|
|
2049
|
+
title={isDrawer ? '候选人深度评估(侧滑)' : '候选人深度评估(超长表单)'}
|
|
1715
2050
|
open={open}
|
|
1716
2051
|
onCancel={() => setOpen(false)}
|
|
1717
2052
|
renderModal={renderModal}
|
|
@@ -1732,35 +2067,43 @@ const FormInfoModalExample = () => {
|
|
|
1732
2067
|
);
|
|
1733
2068
|
};
|
|
1734
2069
|
|
|
1735
|
-
|
|
2070
|
+
render(
|
|
1736
2071
|
<App>
|
|
2072
|
+
<DrawerContextHolder />
|
|
1737
2073
|
<FormInfoModalExample />
|
|
1738
2074
|
</App>
|
|
1739
2075
|
);
|
|
1740
2076
|
|
|
1741
|
-
render(<BaseExample />);
|
|
1742
|
-
|
|
1743
2077
|
```
|
|
1744
2078
|
|
|
1745
2079
|
- FormStepsModal + renderModal
|
|
1746
|
-
-
|
|
2080
|
+
- Modal / Drawer 切换:分步表单,步骤内 FormInfo gap + Flex 垂直间距
|
|
1747
2081
|
- _ReactModal(@kne/current-lib_react-modal)[import * as _ReactModal from "@kne/react-modal"],(@kne/current-lib_react-modal/dist/index.css),_FormInfo(@kne/form-info)[import * as _FormInfo from "@kne/form-info"],(@kne/form-info/dist/index.css),antd(antd)
|
|
1748
2082
|
|
|
1749
2083
|
```jsx
|
|
1750
|
-
const { createModalRender, modalClassNames } = _ReactModal;
|
|
2084
|
+
const { createModalRender, createDrawerRender, modalClassNames, DrawerContextHolder } = _ReactModal;
|
|
1751
2085
|
const { default: FormInfo, FormStepsModal, List, Input, TextArea } = _FormInfo;
|
|
1752
|
-
const { Button, Space, Typography, App, message, Flex } = antd;
|
|
1753
|
-
const { useState } = React;
|
|
2086
|
+
const { Button, Space, Typography, App, message, Flex, Radio } = antd;
|
|
2087
|
+
const { useState, useEffect } = React;
|
|
1754
2088
|
const { Text } = Typography;
|
|
1755
2089
|
|
|
1756
|
-
const renderStepsModalBase =
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
2090
|
+
const renderStepsModalBase = isDrawer =>
|
|
2091
|
+
isDrawer
|
|
2092
|
+
? createDrawerRender({
|
|
2093
|
+
placement: 'right',
|
|
2094
|
+
footerButtons: [],
|
|
2095
|
+
bodyScroll: true,
|
|
2096
|
+
size: 'default',
|
|
2097
|
+
className: modalClassNames.stepsForm
|
|
2098
|
+
})
|
|
2099
|
+
: createModalRender({
|
|
2100
|
+
footerButtons: [],
|
|
2101
|
+
bodyScroll: true,
|
|
2102
|
+
size: 'default',
|
|
2103
|
+
className: modalClassNames.stepsForm
|
|
2104
|
+
});
|
|
1762
2105
|
|
|
1763
|
-
const renderStepsModal = ({
|
|
2106
|
+
const renderStepsModal = isDrawer => ({
|
|
1764
2107
|
formProps,
|
|
1765
2108
|
saveText,
|
|
1766
2109
|
autoClose,
|
|
@@ -1771,7 +2114,7 @@ const renderStepsModal = ({
|
|
|
1771
2114
|
className,
|
|
1772
2115
|
...props
|
|
1773
2116
|
}) =>
|
|
1774
|
-
renderStepsModalBase({
|
|
2117
|
+
renderStepsModalBase(isDrawer)({
|
|
1775
2118
|
...props,
|
|
1776
2119
|
className,
|
|
1777
2120
|
onClose: onCancel,
|
|
@@ -1805,15 +2148,34 @@ const STEP_DATA = {
|
|
|
1805
2148
|
|
|
1806
2149
|
const FormInfoStepsModalExample = () => {
|
|
1807
2150
|
const [open, setOpen] = useState(false);
|
|
2151
|
+
const [mode, setMode] = useState('modal');
|
|
2152
|
+
const isDrawer = mode === 'drawer';
|
|
2153
|
+
|
|
2154
|
+
useEffect(() => {
|
|
2155
|
+
if (open) {
|
|
2156
|
+
setOpen(false);
|
|
2157
|
+
}
|
|
2158
|
+
}, [mode]);
|
|
1808
2159
|
|
|
1809
2160
|
return (
|
|
1810
2161
|
<Space direction="vertical" style={{ width: '100%' }}>
|
|
2162
|
+
<Space align="center" wrap>
|
|
2163
|
+
<Text type="secondary">打开方式</Text>
|
|
2164
|
+
<Radio.Group
|
|
2165
|
+
value={mode}
|
|
2166
|
+
optionType="button"
|
|
2167
|
+
size="small"
|
|
2168
|
+
options={[
|
|
2169
|
+
{ label: 'Modal', value: 'modal' },
|
|
2170
|
+
{ label: 'Drawer', value: 'drawer' }
|
|
2171
|
+
]}
|
|
2172
|
+
onChange={e => setMode(e.target.value)}
|
|
2173
|
+
/>
|
|
2174
|
+
</Space>
|
|
1811
2175
|
<Button type="primary" onClick={() => setOpen(true)}>
|
|
1812
2176
|
打开分步评估(FormStepsModal)
|
|
1813
2177
|
</Button>
|
|
1814
|
-
<Text type="secondary">
|
|
1815
|
-
三步完成候选人评估:基本信息 → 维度评分 → 经历与结论。
|
|
1816
|
-
</Text>
|
|
2178
|
+
<Text type="secondary">三步完成候选人评估;切换 Modal / Drawer 对比分步表单高度链。</Text>
|
|
1817
2179
|
|
|
1818
2180
|
<FormStepsModal
|
|
1819
2181
|
autoStep
|
|
@@ -1825,10 +2187,10 @@ const FormInfoStepsModalExample = () => {
|
|
|
1825
2187
|
}}
|
|
1826
2188
|
modalProps={{
|
|
1827
2189
|
open,
|
|
1828
|
-
title: '候选人评估(分步)',
|
|
2190
|
+
title: isDrawer ? '候选人评估(侧滑分步)' : '候选人评估(分步)',
|
|
1829
2191
|
width: 900,
|
|
1830
2192
|
onCancel: () => setOpen(false),
|
|
1831
|
-
renderModal: renderStepsModal
|
|
2193
|
+
renderModal: renderStepsModal(isDrawer)
|
|
1832
2194
|
}}
|
|
1833
2195
|
items={[
|
|
1834
2196
|
{
|
|
@@ -1920,14 +2282,13 @@ const FormInfoStepsModalExample = () => {
|
|
|
1920
2282
|
);
|
|
1921
2283
|
};
|
|
1922
2284
|
|
|
1923
|
-
|
|
2285
|
+
render(
|
|
1924
2286
|
<App>
|
|
2287
|
+
<DrawerContextHolder />
|
|
1925
2288
|
<FormInfoStepsModalExample />
|
|
1926
2289
|
</App>
|
|
1927
2290
|
);
|
|
1928
2291
|
|
|
1929
|
-
render(<BaseExample />);
|
|
1930
|
-
|
|
1931
2292
|
```
|
|
1932
2293
|
|
|
1933
2294
|
### API
|
|
@@ -1986,6 +2347,39 @@ render(<BaseExample />);
|
|
|
1986
2347
|
|------|------|------|
|
|
1987
2348
|
| modal | function | 调用后弹出 Modal;返回 `{ close }`;默认 `zIndex` 为 1100 |
|
|
1988
2349
|
|
|
2350
|
+
#### useConfirmModal
|
|
2351
|
+
|
|
2352
|
+
命令式确认 / 提示弹窗,底层走 antd `App.modal` 的 `confirm` / `info` / `success` / `warning` / `error`。需在 antd `App` 上下文中使用。
|
|
2353
|
+
|
|
2354
|
+
##### 参数
|
|
2355
|
+
|
|
2356
|
+
| 属性 | 类型 | 默认值 | 描述 |
|
|
2357
|
+
|------|------|--------|------|
|
|
2358
|
+
| type | `confirm` \| `info` \| `success` \| `warning` \| `error` | `confirm` | 调用 `modal[type]` |
|
|
2359
|
+
| title | ReactNode | - | 标题 |
|
|
2360
|
+
| message | ReactNode | - | 正文 |
|
|
2361
|
+
| danger | boolean | false | 为 true 时展示语义图标,确认钮危险色 |
|
|
2362
|
+
| confirmType | `info` \| `warning` \| `error` \| `success` | `info` | `type=confirm` 时图标语义 |
|
|
2363
|
+
| icon | ReactNode | - | 自定义图标,覆盖默认 |
|
|
2364
|
+
| onConfirm | function | - | 映射 antd `onOk` |
|
|
2365
|
+
| onCancel | function | - | 映射 antd `onCancel` |
|
|
2366
|
+
| confirmText | ReactNode | - | 映射 `okText` |
|
|
2367
|
+
| cancelText | ReactNode | - | 映射 `cancelText` |
|
|
2368
|
+
| onClose | function | - | 调用 `close()` 时触发 |
|
|
2369
|
+
| maskClosable | boolean | false | 默认不可点蒙层关闭 |
|
|
2370
|
+
| getContainer | HTMLElement \| function | - | 嵌套时挂到外层 modal 外侧 |
|
|
2371
|
+
| afterClose / zIndex / wrapClassName | - | - | 透传;默认 `zIndex` 1100 |
|
|
2372
|
+
|
|
2373
|
+
其余未列出参数按 antd Modal.confirm 习惯透传。
|
|
2374
|
+
|
|
2375
|
+
##### 返回值
|
|
2376
|
+
|
|
2377
|
+
| 属性 | 类型 | 描述 |
|
|
2378
|
+
|------|------|------|
|
|
2379
|
+
| confirmModal | function | 调用后弹出确认框;返回 `{ close }` |
|
|
2380
|
+
|
|
2381
|
+
与 `useModal` 对比:专用于短文案确认 / 提示,桌面居中窄宽(约 400px),无 SimpleBar body。
|
|
2382
|
+
|
|
1989
2383
|
#### CSS 变量
|
|
1990
2384
|
|
|
1991
2385
|
挂在 `.modal` / `.modal-outer` 上,可业务覆盖。
|
|
@@ -2005,33 +2399,33 @@ render(<BaseExample />);
|
|
|
2005
2399
|
| `--kne-modal-content-min-height` | content 侧 min |
|
|
2006
2400
|
| `--kne-modal-content-width` | 内容宽度契约(扣 horizontal padding) |
|
|
2007
2401
|
|
|
2008
|
-
####
|
|
2402
|
+
#### 布局组合(Modal / Drawer 共用)
|
|
2009
2403
|
|
|
2010
|
-
Tabs / N 列分栏等复杂内容区须 **`bodyScroll={false}`**,由 `
|
|
2404
|
+
Tabs / N 列分栏等复杂内容区须 **`bodyScroll={false}`**,由 `ScrollRegion`(SimpleBar)分区滚动,避免与弹层 body 双层 SimpleBar。
|
|
2011
2405
|
|
|
2012
|
-
#####
|
|
2406
|
+
##### TabsLayout
|
|
2013
2407
|
|
|
2014
|
-
antd `Tabs` 封装:顶栏占 title 位(配合无 `title`
|
|
2408
|
+
antd `Tabs` 封装:顶栏占 title 位(配合无 `title` 的弹层)、antd6 高度链、`destroyOnHidden` 默认 true。其余 props 透传 Tabs。
|
|
2015
2409
|
|
|
2016
|
-
#####
|
|
2410
|
+
##### ColumnsLayout
|
|
2017
2411
|
|
|
2018
2412
|
固定 N 列 flex 分栏。`widths` 与 `children` 等长(如 `['36%', '1fr']`、`['200px', '1fr', '280px']`);`'1fr'` 占剩余宽度。
|
|
2019
2413
|
|
|
2020
|
-
#####
|
|
2414
|
+
##### ScrollRegion
|
|
2021
2415
|
|
|
2022
2416
|
单块 SimpleBar 滚动区,用于 Tab 面板或每一列。首列/末列背景由父级 `:first-child` / `:last-child` 选中。
|
|
2023
2417
|
|
|
2024
2418
|
- `inset`(默认 `false`):为 `true` 时内容区增加 `16px 20px` 内边距(适合详情、概览等单列内容)
|
|
2025
2419
|
- 分栏列表左列通常保持 `inset={false}`,由列表项自行控制间距
|
|
2026
2420
|
|
|
2027
|
-
Tabs /
|
|
2421
|
+
Tabs / 分栏弹层须 **`bodyScroll={false}`**,此时默认 **noPadding**(内容贴边),以便分栏铺满 body;需要外层留白时可传 **`noPadding={false}`**。
|
|
2028
2422
|
|
|
2029
2423
|
##### modalClassNames
|
|
2030
2424
|
|
|
2031
2425
|
| 常量 | 值 | 用途 |
|
|
2032
2426
|
|------|-----|------|
|
|
2033
|
-
| `stepsForm` | `react-modal-steps-form` | 分步弹窗挂
|
|
2034
|
-
| `splitter` | `react-modal-splitter` | antd Splitter
|
|
2427
|
+
| `stepsForm` | `react-modal-steps-form` | 分步弹窗挂 `className`,去横向溢出 |
|
|
2428
|
+
| `splitter` | `react-modal-splitter` | antd Splitter 在弹层内的高度链 |
|
|
2035
2429
|
|
|
2036
2430
|
##### createModalRender
|
|
2037
2431
|
|
|
@@ -2048,3 +2442,38 @@ createModalRender(modalDefaults) => (hostProps) => Modal
|
|
|
2048
2442
|
| 子内容自带 Footer | `{ footerButtons: [] }` |
|
|
2049
2443
|
| 长内容 | `{ footerButtons: [], bodyScroll: true, size: 'large' }` |
|
|
2050
2444
|
| 分步弹窗 | `{ footerButtons: [], bodyScroll: true, size: 'default', className: modalClassNames.stepsForm }` |
|
|
2445
|
+
|
|
2446
|
+
#### Drawer
|
|
2447
|
+
|
|
2448
|
+
声明式侧滑层。基于 antd Drawer,关闭请使用 `onClose`(内部映射 antd `onClose`)。API 与 Modal 对齐处不再重复;差异如下。
|
|
2449
|
+
|
|
2450
|
+
##### 属性(差异与补充)
|
|
2451
|
+
|
|
2452
|
+
| 属性 | 类型 | 默认值 | 描述 |
|
|
2453
|
+
|------|------|--------|------|
|
|
2454
|
+
| placement | `left` \| `right` \| `top` \| `bottom` | `right` | 滑出方向;`size` 映射 width(left/right)或 height(top/bottom) |
|
|
2455
|
+
| size | `small` \| `default` \| `large` | `default` | 600 / 1000 / min(vw−64, 1500) px |
|
|
2456
|
+
| 移动端 | - | 侧滑全宽 | left/right → 100vw;top/bottom → 100vh(非 Modal 式全屏居中) |
|
|
2457
|
+
|
|
2458
|
+
其余 `open` / `onClose` / `title` / `children` / `footer` / `footerButtons` / `bodyScroll` / `noPadding` / `closable` / `maskClosable` / `getContainer` 等与 Modal 相同。
|
|
2459
|
+
|
|
2460
|
+
**FormModal**:传入的 `modalRender` 会在 Drawer 内注入 `ModalForm`,包裹整块 chrome(title / body / footer,与 Modal 的 panel 语义一致);footer 内 Submit/Cancel 需在 Form 上下文内。
|
|
2461
|
+
|
|
2462
|
+
嵌套时默认挂到外层 `.ant-drawer-root` 外侧。
|
|
2463
|
+
|
|
2464
|
+
#### useDrawer
|
|
2465
|
+
|
|
2466
|
+
命令式打开 Drawer,参数同 Drawer。须在 antd `App` 内挂载 **`<DrawerContextHolder />`**(对标 antd 内置 `ModalContextHolder`),签名与 `useModal` 相同:`const drawer = useDrawer(); drawer(props) → { close }`。
|
|
2467
|
+
|
|
2468
|
+
#### Drawer CSS 变量
|
|
2469
|
+
|
|
2470
|
+
挂在 `.drawer` / `.drawer-outer` 上;命名 `--kne-drawer-*`,语义与 Modal 对齐(无 viewport gutter,body 占满面板高度链)。`.drawer-outer` 内 bridge `--kne-modal-content-height` 等,供共用 `ScrollRegion` / `TabsLayout` 高度链。
|
|
2471
|
+
|
|
2472
|
+
#### createDrawerRender
|
|
2473
|
+
|
|
2474
|
+
```ts
|
|
2475
|
+
createDrawerRender(drawerDefaults) => (hostProps) => Drawer
|
|
2476
|
+
```
|
|
2477
|
+
|
|
2478
|
+
单参数合并渲染;宿主字段映射由 `renderModal` 回调内完成(同 `createModalRender`)。
|
|
2479
|
+
|