@douyinfe/semi-ui 2.7.0 → 2.8.0-beta.1

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 (48) hide show
  1. package/_base/_story/index.stories.js +2 -6
  2. package/_portal/_story/portal.stories.js +1 -5
  3. package/_utils/hooks/usePrevFocus.ts +1 -0
  4. package/_utils/index.ts +29 -1
  5. package/datePicker/_story/v2/FixDefaultPickerValue.jsx +31 -0
  6. package/datePicker/_story/v2/InsetInput.jsx +1 -1
  7. package/datePicker/_story/v2/index.js +1 -0
  8. package/datePicker/monthsGrid.tsx +3 -13
  9. package/dist/css/semi.css +30 -21
  10. package/dist/css/semi.min.css +1 -1
  11. package/dist/umd/semi-ui.js +608 -284
  12. package/dist/umd/semi-ui.js.map +1 -1
  13. package/dist/umd/semi-ui.min.js +1 -1
  14. package/dist/umd/semi-ui.min.js.map +1 -1
  15. package/input/_story/input.stories.js +10 -1
  16. package/inputNumber/_story/inputNumber.stories.js +4 -0
  17. package/lib/cjs/_utils/hooks/usePrevFocus.js +1 -0
  18. package/lib/cjs/_utils/index.d.ts +3 -1
  19. package/lib/cjs/_utils/index.js +25 -1
  20. package/lib/cjs/datePicker/monthsGrid.js +11 -19
  21. package/lib/cjs/modal/useModal/HookModal.js +2 -0
  22. package/lib/cjs/notification/useNotification/index.js +1 -1
  23. package/lib/cjs/popover/index.d.ts +18 -3
  24. package/lib/cjs/popover/index.js +53 -23
  25. package/lib/cjs/tooltip/index.d.ts +22 -4
  26. package/lib/cjs/tooltip/index.js +65 -27
  27. package/lib/es/_utils/hooks/usePrevFocus.js +2 -0
  28. package/lib/es/_utils/index.d.ts +3 -1
  29. package/lib/es/_utils/index.js +18 -0
  30. package/lib/es/datePicker/monthsGrid.js +11 -19
  31. package/lib/es/modal/useModal/HookModal.js +2 -0
  32. package/lib/es/notification/useNotification/index.js +2 -1
  33. package/lib/es/popover/index.d.ts +18 -3
  34. package/lib/es/popover/index.js +52 -23
  35. package/lib/es/tooltip/index.d.ts +22 -4
  36. package/lib/es/tooltip/index.js +65 -27
  37. package/modal/_story/modal.stories.js +93 -1
  38. package/modal/useModal/HookModal.tsx +1 -0
  39. package/notification/_story/useNotification/index.jsx +21 -7
  40. package/notification/useNotification/index.tsx +1 -1
  41. package/package.json +9 -9
  42. package/popover/_story/popover.stories.js +75 -1
  43. package/popover/index.tsx +24 -8
  44. package/select/__test__/select.test.js +16 -0
  45. package/table/_story/v2/FixedMemoryLeak/index.jsx +33 -0
  46. package/table/_story/v2/index.js +2 -1
  47. package/toast/_story/toast.stories.js +41 -0
  48. package/tooltip/index.tsx +72 -22
@@ -1,5 +1,7 @@
1
1
  import React, { useState } from 'react';
2
- import { Select, Modal, Button, Tooltip, Popover } from '../../index';
2
+ import en_GB from '../../locale/source/en_GB';
3
+
4
+ import { Select, Modal, Button, Tooltip, Popover, ConfigProvider, Tag, Space } from '../../index';
3
5
  import CollapsibleInModal from './CollapsibleInModal';
4
6
  import DynamicContextDemo from './DynamicContext';
5
7
 
@@ -248,4 +250,94 @@ KeepDomNotLazy.story = {
248
250
  name: 'keepDOM && not lazy',
249
251
  };
250
252
 
253
+ export const UseModalDemo = () => {
254
+ const [modal, contextHolder] = Modal.useModal();
255
+ const config = { 'title': 'old title', 'content': 'old content' };
256
+
257
+ return (
258
+ <ConfigProvider locale={en_GB}>
259
+ <div>
260
+ <Button
261
+ onClick={() => {
262
+ const currentModal = modal.confirm(config);
263
+
264
+ setTimeout(() => {
265
+ currentModal.update({ title: "new title", content: "new content" });
266
+ }, 1000);
267
+ }}
268
+ >
269
+ Confirm Modal
270
+ </Button>
271
+ </div>
272
+ {contextHolder}
273
+ </ConfigProvider>
274
+ );
275
+ };
276
+ UseModalDemo.storyName = "useModal";
277
+
278
+ export const UseModalDestroy = () => {
279
+ const [modal, contextHolder] = Modal.useModal();
280
+ const config = { 'title': 'old title', 'content': 'old content' };
281
+
282
+ return (
283
+ <ConfigProvider locale={en_GB}>
284
+ <div>
285
+ <Button
286
+ onClick={() => {
287
+ const currentModal = modal.confirm(config);
288
+
289
+ setTimeout(() => {
290
+ currentModal.destroy();
291
+ }, 1000);
292
+ }}
293
+ >
294
+ Confirm Modal
295
+ </Button>
296
+ </div>
297
+ {contextHolder}
298
+ </ConfigProvider>
299
+ );
300
+ };
301
+ UseModalDestroy.storyName = "useModal destroy";
302
+
303
+ export const UseModalAfterClose = () => {
304
+ const [modal, contextHolder] = Modal.useModal();
305
+ const [closed, setClosed] = React.useState(false);
306
+ const [leave, setLeave] = React.useState(false);
307
+
308
+ const config = {
309
+ title: 'old title',
310
+ content: 'old content',
311
+ afterClose: () => {
312
+ setClosed(true);
313
+ },
314
+ motion: {
315
+ didLeave: () => {
316
+ console.log('didLeave');
317
+ setLeave(true);
318
+ }
319
+ }
320
+ };
251
321
 
322
+ return (
323
+ <ConfigProvider locale={en_GB}>
324
+ <Space>
325
+ <Button
326
+ onClick={() => {
327
+ const currentModal = modal.confirm(config);
328
+
329
+ setTimeout(() => {
330
+ currentModal.destroy();
331
+ }, 0);
332
+ }}
333
+ >
334
+ Confirm Modal
335
+ </Button>
336
+ <Tag>{`closed: ${closed}`}</Tag>
337
+ {/* <Tag>{`motion leave: ${leave}`}</Tag> */}
338
+ </Space>
339
+ {contextHolder}
340
+ </ConfigProvider>
341
+ );
342
+ };
343
+ UseModalAfterClose.storyName = "useModal afterClose";
@@ -35,6 +35,7 @@ const HookModal = ({ afterClose, config, ...props }: PropsWithChildren<HookModal
35
35
  }));
36
36
 
37
37
  const { motion } = props;
38
+ /* istanbul ignore next */
38
39
  const mergedMotion =
39
40
  typeof motion === 'undefined' || motion ?
40
41
  {
@@ -4,18 +4,32 @@ import { Button, ConfigProvider } from '../../../index';
4
4
  import Context from './context';
5
5
 
6
6
  function App({ children, globalVars }) {
7
- return <Context.Provider value={{ title: '1111', ...globalVars }}>{children}</Context.Provider>;
7
+ return (
8
+ <div data-cy="notice-container">
9
+ <Context.Provider value={{ title: '1111', ...globalVars }}>{children}</Context.Provider>
10
+ </div>
11
+ );
8
12
  }
9
13
 
10
14
  export default function Demo() {
11
- const [Notice, elements] = useNotification();
15
+ const [notice, elements] = useNotification();
16
+ const config = {
17
+ content: 'Hello World',
18
+ position: 'top',
19
+ title: <Context.Consumer>{({ title }) => <strong>{title}</strong>}</Context.Consumer>,
20
+ duration: 0,
21
+ };
12
22
 
13
23
  const addNotice = () => {
14
- Notice.addNotice({
15
- content: 'Hello World',
16
- position: 'top',
17
- title: <Context.Consumer>{({ title }) => <strong>{title}</strong>}</Context.Consumer>,
18
- });
24
+ const id1 = notice.info(config);
25
+ const id2 = notice.success(config);
26
+ const id3 = notice.warning(config);
27
+ const id4 = notice.error(config);
28
+ const id5 = notice.open(config);
29
+
30
+ // setTimeout(() => {
31
+ // notice.close(id5);
32
+ // }, 1000);
19
33
  };
20
34
 
21
35
  return (
@@ -59,8 +59,8 @@ export default function useNotification() {
59
59
  const [elements, patchElement] = usePatchElement();
60
60
  const noticeRef = new Map<string, { close: () => void } & ReactElement>();
61
61
 
62
- const id = getUuid('semi_notice_');
63
62
  const addNotice = (config: NoticeProps) => {
63
+ const id = getUuid('semi_notice_');
64
64
  const mergeConfig = {
65
65
  ...config,
66
66
  id,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@douyinfe/semi-ui",
3
- "version": "2.7.0",
3
+ "version": "2.8.0-beta.1",
4
4
  "description": "",
5
5
  "main": "lib/cjs/index.js",
6
6
  "module": "lib/es/index.js",
@@ -14,12 +14,12 @@
14
14
  },
15
15
  "dependencies": {
16
16
  "@babel/runtime-corejs3": "^7.15.4",
17
- "@douyinfe/semi-animation": "2.7.0",
18
- "@douyinfe/semi-animation-react": "2.7.0",
19
- "@douyinfe/semi-foundation": "2.7.0",
20
- "@douyinfe/semi-icons": "2.7.0",
21
- "@douyinfe/semi-illustrations": "2.7.0",
22
- "@douyinfe/semi-theme-default": "2.7.0",
17
+ "@douyinfe/semi-animation": "2.8.0-beta.1",
18
+ "@douyinfe/semi-animation-react": "2.8.0-beta.1",
19
+ "@douyinfe/semi-foundation": "2.8.0-beta.1",
20
+ "@douyinfe/semi-icons": "2.8.0-beta.1",
21
+ "@douyinfe/semi-illustrations": "2.8.0-beta.1",
22
+ "@douyinfe/semi-theme-default": "2.8.0-beta.1",
23
23
  "@types/react-window": "^1.8.2",
24
24
  "async-validator": "^3.5.0",
25
25
  "classnames": "^2.2.6",
@@ -69,13 +69,13 @@
69
69
  ],
70
70
  "author": "",
71
71
  "license": "MIT",
72
- "gitHead": "b6e4e0a1e22d4dabe68cbf7e3d2cfd0202da0424",
72
+ "gitHead": "1d03945ec34f2ebd37ab7cc67c4b11786b46cb1f",
73
73
  "devDependencies": {
74
74
  "@babel/plugin-proposal-decorators": "^7.15.8",
75
75
  "@babel/plugin-transform-runtime": "^7.15.8",
76
76
  "@babel/preset-env": "^7.15.8",
77
77
  "@babel/preset-react": "^7.14.5",
78
- "@douyinfe/semi-scss-compile": "2.7.0",
78
+ "@douyinfe/semi-scss-compile": "2.8.0-beta.1",
79
79
  "@storybook/addon-knobs": "^6.3.1",
80
80
  "@types/lodash": "^4.14.176",
81
81
  "babel-loader": "^8.2.2",
@@ -2,7 +2,7 @@ import React, { useState } from 'react';
2
2
 
3
3
  import Popover from '../index';
4
4
  import { strings } from '@douyinfe/semi-foundation/tooltip/constants';
5
- import { Button, Input, Table, IconButton, Modal, Tag } from '@douyinfe/semi-ui';
5
+ import { Button, Input, Table, IconButton, Modal, Tag, Space } from '@douyinfe/semi-ui';
6
6
  import SelectInPopover from './SelectInPopover';
7
7
  import BtnClose from './BtnClose';
8
8
  import PopRight from './PopRight';
@@ -572,3 +572,77 @@ export const ArrowPointAtCenterDemo = () => <ArrowPointAtCenter />;
572
572
  ArrowPointAtCenterDemo.story = {
573
573
  name: 'arrow point at center'
574
574
  }
575
+
576
+ export const A11yKeyboard = () => {
577
+ const [visible, setVisible] = React.useState(false);
578
+ const popStyle = { height: 200, width: 200 };
579
+
580
+ const renderContent = ({ initialFocusRef }) => {
581
+ return (
582
+ <div style={popStyle} data-cy="pop">
583
+ <button data-cy="pop-focusable-first">first focusable</button>
584
+ <a href="https://semi.design">link</a>
585
+ {/* <input ref={initialFocusRef} placeholder="init focus" /> */}
586
+ <input placeholder="" defaultValue="semi" />
587
+ <a href="https://semi.design">link2</a>
588
+ <button data-cy="pop-focusable-last">last focusable</button>
589
+ </div>
590
+ );
591
+ };
592
+
593
+ const noFocusableContent = (
594
+ <div style={popStyle}>没有可聚焦元素</div>
595
+ );
596
+
597
+ const initFocusContent = ({ initialFocusRef }) => {
598
+ return (
599
+ <div style={popStyle} data-cy="pop">
600
+ <button data-cy="pop-focusable-first">first focusable</button>
601
+ <input placeholder="" defaultValue="semi" ref={initialFocusRef} data-cy="initial-focus-input" />
602
+ <button data-cy="pop-focusable-last">last focusable</button>
603
+ </div>
604
+ );
605
+ };
606
+
607
+ return (
608
+ <div style={{ paddingLeft: 100, paddingTop: 100 }}>
609
+ <Space spacing={100}>
610
+ <Popover content={renderContent} trigger="click" motion={false}>
611
+ <Button data-cy="click">click</Button>
612
+ </Popover>
613
+ <Popover content={renderContent} trigger="hover">
614
+ <span data-cy="hover">hover</span>
615
+ </Popover>
616
+ <Popover content={renderContent} trigger="focus">
617
+ <Input data-cy="focus" defaultValue="focus" style={{ width: 150 }} />
618
+ </Popover>
619
+ <Popover
620
+ content={renderContent}
621
+ trigger="custom"
622
+ visible={visible}
623
+ onEscKeyDown={() => {
624
+ console.log('esc key down');
625
+ setVisible(false);
626
+ }}
627
+ >
628
+ <Button onClick={() => setVisible(!visible)} data-cy="custom">
629
+ custom trigger + click me toggle show
630
+ </Button>
631
+ </Popover>
632
+ <Popover content={noFocusableContent} trigger="click" data-cy="click-pop-contains-no-focusable">
633
+ <Button>pop内没有可聚焦元素</Button>
634
+ </Popover>
635
+ <Popover content={initFocusContent} trigger="click" motion={false}>
636
+ <Button data-cy="initial-focus">custom initialFocus</Button>
637
+ </Popover>
638
+ <Popover content={renderContent} trigger="click" motion={false} closeOnEsc={false}>
639
+ <Button data-cy="closeOnEsc-false">closeOnEsc=false</Button>
640
+ </Popover>
641
+ <Popover content={renderContent} trigger="click" motion={false} returnFocusOnClose={false}>
642
+ <Button data-cy="returnFocusOnClose-false">returnFocusOnClose=false</Button>
643
+ </Popover>
644
+ </Space>
645
+ </div>
646
+ );
647
+ };
648
+ A11yKeyboard.storyName = "a11y keyboard and focus";
package/popover/index.tsx CHANGED
@@ -3,12 +3,12 @@ import classNames from 'classnames';
3
3
  import PropTypes from 'prop-types';
4
4
  import ConfigContext from '../configProvider/context';
5
5
  import { cssClasses, strings, numbers } from '@douyinfe/semi-foundation/popover/constants';
6
- import Tooltip, { ArrowBounding, Position, Trigger } from '../tooltip/index';
6
+ import Tooltip, { ArrowBounding, Position, TooltipProps, Trigger, RenderContentProps } from '../tooltip/index';
7
7
  import Arrow from './Arrow';
8
8
  import '@douyinfe/semi-foundation/popover/popover.scss';
9
9
  import { BaseProps } from '../_base/baseComponent';
10
10
  import { Motion } from '../_base/base';
11
- import { noop } from 'lodash';
11
+ import { isFunction, noop } from 'lodash';
12
12
 
13
13
  export { ArrowProps } from './Arrow';
14
14
  declare interface ArrowStyle {
@@ -19,7 +19,7 @@ declare interface ArrowStyle {
19
19
 
20
20
  export interface PopoverProps extends BaseProps {
21
21
  children?: React.ReactNode;
22
- content?: React.ReactNode;
22
+ content?: TooltipProps['content'];
23
23
  visible?: boolean;
24
24
  autoAdjustOverflow?: boolean;
25
25
  motion?: Motion;
@@ -40,6 +40,10 @@ export interface PopoverProps extends BaseProps {
40
40
  rePosKey?: string | number;
41
41
  getPopupContainer?: () => HTMLElement;
42
42
  zIndex?: number;
43
+ closeOnEsc?: TooltipProps['closeOnEsc'];
44
+ guardFocus?: TooltipProps['guardFocus'];
45
+ returnFocusOnClose?: TooltipProps['returnFocusOnClose'];
46
+ onEscKeyDown?: TooltipProps['onEscKeyDown'];
43
47
  }
44
48
 
45
49
  export interface PopoverState {
@@ -52,7 +56,7 @@ class Popover extends React.PureComponent<PopoverProps, PopoverState> {
52
56
  static contextType = ConfigContext;
53
57
  static propTypes = {
54
58
  children: PropTypes.node,
55
- content: PropTypes.node,
59
+ content: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
56
60
  visible: PropTypes.bool,
57
61
  autoAdjustOverflow: PropTypes.bool,
58
62
  motion: PropTypes.oneOfType([PropTypes.bool, PropTypes.object, PropTypes.func]),
@@ -76,6 +80,7 @@ class Popover extends React.PureComponent<PopoverProps, PopoverState> {
76
80
  arrowPointAtCenter: PropTypes.bool,
77
81
  arrowBounding: PropTypes.object,
78
82
  prefixCls: PropTypes.string,
83
+ guardFocus: PropTypes.bool,
79
84
  };
80
85
 
81
86
  static defaultProps = {
@@ -90,9 +95,13 @@ class Popover extends React.PureComponent<PopoverProps, PopoverState> {
90
95
  position: 'bottom',
91
96
  prefixCls: cssClasses.PREFIX,
92
97
  onClickOutSide: noop,
98
+ onEscKeyDown: noop,
99
+ closeOnEsc: true,
100
+ returnFocusOnClose: true,
101
+ guardFocus: true,
93
102
  };
94
103
 
95
- renderPopCard() {
104
+ renderPopCard = ({ initialFocusRef }: { initialFocusRef: RenderContentProps['initialFocusRef'] }) => {
96
105
  const { content, contentClassName, prefixCls } = this.props;
97
106
  const { direction } = this.context;
98
107
  const popCardCls = classNames(
@@ -102,13 +111,20 @@ class Popover extends React.PureComponent<PopoverProps, PopoverState> {
102
111
  [`${prefixCls}-rtl`]: direction === 'rtl',
103
112
  }
104
113
  );
114
+ const contentNode = this.renderContentNode({ initialFocusRef, content });
105
115
  return (
106
116
  <div className={popCardCls}>
107
- <div className={`${prefixCls}-content`}>{content}</div>
117
+ <div className={`${prefixCls}-content`}>{contentNode}</div>
108
118
  </div>
109
119
  );
110
120
  }
111
121
 
122
+ renderContentNode = (props: { content: TooltipProps['content'], initialFocusRef: RenderContentProps['initialFocusRef'] }) => {
123
+ const { initialFocusRef, content } = props;
124
+ const contentProps = { initialFocusRef };
125
+ return !isFunction(content) ? content : content(contentProps);
126
+ };
127
+
112
128
  render() {
113
129
  const {
114
130
  children,
@@ -122,7 +138,6 @@ class Popover extends React.PureComponent<PopoverProps, PopoverState> {
122
138
  ...attr
123
139
  } = this.props;
124
140
  let { spacing } = this.props;
125
- const popContent = this.renderPopCard();
126
141
 
127
142
  const arrowProps = {
128
143
  position,
@@ -141,11 +156,12 @@ class Popover extends React.PureComponent<PopoverProps, PopoverState> {
141
156
 
142
157
  return (
143
158
  <Tooltip
159
+ guardFocus
144
160
  {...(attr as any)}
145
161
  trigger={trigger}
146
162
  position={position}
147
163
  style={style}
148
- content={popContent}
164
+ content={this.renderPopCard}
149
165
  prefixCls={prefixCls}
150
166
  spacing={spacing}
151
167
  showArrow={arrow}
@@ -504,6 +504,22 @@ describe('Select', () => {
504
504
  expect(optionList.at(0).text()).toEqual('Abc');
505
505
  });
506
506
 
507
+ it('filter = true,label includes regex special character and key it at first', () => {
508
+ let props = {
509
+ filter: true,
510
+ optionList: [{label: 'label++',value: ''}]
511
+ };
512
+ const select = getSelect(props);
513
+ // click to show input
514
+ select.find(`.${BASE_CLASS_PREFIX}-select`).simulate('click', {});
515
+ let inputValue = '+';
516
+ let event = { target: { value: inputValue } };
517
+ select.find('input').simulate('change', event);
518
+ let optionList = select.find(`.${BASE_CLASS_PREFIX}-select-option-list`).children();
519
+ expect(optionList.length).toEqual(1);
520
+ expect(optionList.at(0).text()).toEqual('label++');
521
+ });
522
+
507
523
  it('filter = custom function', () => {
508
524
  let customFilter = (sugInput, option) => {
509
525
  return option.label == 'Hotsoon';
@@ -0,0 +1,33 @@
1
+ import React, { useState } from 'react';
2
+ import { Popconfirm, Table } from "@douyinfe/semi-ui";
3
+
4
+ export default function App() {
5
+ const [data, setData] = useState([{ a: 1 }]);
6
+ return (
7
+ <Table
8
+ dataSource={data}
9
+ columns={[
10
+ {
11
+ dataIndex: "a",
12
+ title: "a",
13
+ },
14
+ {
15
+ dataIndex: "b",
16
+ render: () => {
17
+ return (
18
+ <Popconfirm
19
+ onConfirm={() => {
20
+ setTimeout(() => {
21
+ setData([]);
22
+ });
23
+ }}
24
+ >
25
+ 删除
26
+ </Popconfirm>
27
+ );
28
+ },
29
+ },
30
+ ]}
31
+ />
32
+ );
33
+ }
@@ -3,4 +3,5 @@ export { default as FixedColumnsChange } from './FixedColumnsChange';
3
3
  export { default as FixedZIndex } from './FixedZIndex';
4
4
  export { default as FixedHeaderMerge } from './FixedHeaderMerge';
5
5
  export { default as FixedResizable } from './FixedResizable';
6
- export { default as FixedExpandedRow } from './FixedExpandedRow';
6
+ export { default as FixedExpandedRow } from './FixedExpandedRow';
7
+ export { default as FixedMemoryLeak } from './FixedMemoryLeak';
@@ -96,3 +96,44 @@ export const _Toast = () => (
96
96
  _Toast.story = {
97
97
  name: 'toast',
98
98
  };
99
+
100
+ const ReachableContext = React.createContext();
101
+
102
+ /**
103
+ * test with cypress
104
+ * @returns
105
+ */
106
+ export const useToastDemo = () => {
107
+ const [toast, contextHolder] = Toast.useToast();
108
+ const config = {
109
+ duration: 0,
110
+ title: 'This is a success message',
111
+ content: <ReachableContext.Consumer>{name => `ReachableContext: ${name}`}</ReachableContext.Consumer>,
112
+ };
113
+
114
+ return (
115
+ <ReachableContext.Provider value="Light">
116
+ <div>
117
+ <Button
118
+ onClick={() => {
119
+ toast.success(config);
120
+ toast.info(config);
121
+ toast.error(config);
122
+ toast.warning(config);
123
+ const id = toast.open(config);
124
+
125
+ setTimeout(() => {
126
+ toast.close(id);
127
+ }, 100);
128
+ }}
129
+ >
130
+ Hook Toast
131
+ </Button>
132
+ </div>
133
+ <div data-cy="context-holder">
134
+ {contextHolder}
135
+ </div>
136
+ </ReachableContext.Provider>
137
+ );
138
+ };
139
+ useToastDemo.storyName = "useToast";