@douyinfe/semi-ui 2.5.1 → 2.7.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/button/__test__/button.test.js +7 -0
- package/button/buttonGroup.tsx +5 -2
- package/calendar/monthCalendar.tsx +14 -13
- package/cascader/__test__/cascader.test.js +159 -81
- package/cascader/_story/cascader.stories.js +36 -23
- package/cascader/index.tsx +47 -8
- package/cascader/item.tsx +25 -5
- package/datePicker/_story/v2/InsetInput.jsx +104 -0
- package/datePicker/_story/v2/InsetInputE2E.jsx +69 -0
- package/datePicker/_story/v2/index.js +2 -0
- package/datePicker/dateInput.tsx +102 -13
- package/datePicker/datePicker.tsx +95 -16
- package/datePicker/index.tsx +15 -0
- package/datePicker/insetInput.tsx +76 -0
- package/datePicker/month.tsx +14 -7
- package/datePicker/monthsGrid.tsx +31 -12
- package/datePicker/navigation.tsx +8 -4
- package/datePicker/quickControl.tsx +1 -0
- package/datePicker/yearAndMonth.tsx +1 -1
- package/dist/css/semi.css +120 -8
- package/dist/css/semi.min.css +1 -1
- package/dist/umd/semi-ui.js +1100 -193
- package/dist/umd/semi-ui.js.map +1 -1
- package/dist/umd/semi-ui.min.js +1 -1
- package/dist/umd/semi-ui.min.js.map +1 -1
- package/form/hoc/withField.tsx +1 -1
- package/input/_story/input.stories.js +13 -0
- package/lib/cjs/_base/base.css +5 -5
- package/lib/cjs/button/buttonGroup.d.ts +1 -0
- package/lib/cjs/button/buttonGroup.js +6 -2
- package/lib/cjs/calendar/monthCalendar.js +21 -5
- package/lib/cjs/cascader/index.d.ts +10 -2
- package/lib/cjs/cascader/index.js +52 -10
- package/lib/cjs/cascader/item.d.ts +6 -2
- package/lib/cjs/cascader/item.js +33 -4
- package/lib/cjs/datePicker/dateInput.d.ts +9 -4
- package/lib/cjs/datePicker/dateInput.js +107 -13
- package/lib/cjs/datePicker/datePicker.d.ts +7 -2
- package/lib/cjs/datePicker/datePicker.js +138 -30
- package/lib/cjs/datePicker/index.js +24 -2
- package/lib/cjs/datePicker/insetInput.d.ts +21 -0
- package/lib/cjs/datePicker/insetInput.js +80 -0
- package/lib/cjs/datePicker/month.d.ts +1 -0
- package/lib/cjs/datePicker/month.js +18 -2
- package/lib/cjs/datePicker/monthsGrid.js +35 -11
- package/lib/cjs/datePicker/navigation.js +8 -0
- package/lib/cjs/datePicker/quickControl.js +1 -0
- package/lib/cjs/datePicker/yearAndMonth.js +1 -0
- package/lib/cjs/form/hoc/withField.js +1 -1
- package/lib/cjs/navigation/Item.d.ts +2 -2
- package/lib/cjs/navigation/Item.js +8 -6
- package/lib/cjs/navigation/SubNav.js +2 -2
- package/lib/cjs/scrollList/scrollItem.d.ts +2 -1
- package/lib/cjs/scrollList/scrollItem.js +13 -3
- package/lib/cjs/table/Body/index.d.ts +2 -0
- package/lib/cjs/table/Body/index.js +13 -4
- package/lib/cjs/tree/index.js +5 -3
- package/lib/cjs/tree/interface.d.ts +1 -0
- package/lib/cjs/tree/nodeList.js +2 -1
- package/lib/cjs/treeSelect/index.js +7 -3
- package/lib/es/_base/base.css +5 -5
- package/lib/es/button/buttonGroup.d.ts +1 -0
- package/lib/es/button/buttonGroup.js +5 -2
- package/lib/es/calendar/monthCalendar.js +22 -5
- package/lib/es/cascader/index.d.ts +10 -2
- package/lib/es/cascader/index.js +49 -7
- package/lib/es/cascader/item.d.ts +6 -2
- package/lib/es/cascader/item.js +31 -4
- package/lib/es/datePicker/dateInput.d.ts +9 -4
- package/lib/es/datePicker/dateInput.js +106 -13
- package/lib/es/datePicker/datePicker.d.ts +7 -2
- package/lib/es/datePicker/datePicker.js +139 -30
- package/lib/es/datePicker/index.js +20 -0
- package/lib/es/datePicker/insetInput.d.ts +21 -0
- package/lib/es/datePicker/insetInput.js +65 -0
- package/lib/es/datePicker/month.d.ts +1 -0
- package/lib/es/datePicker/month.js +18 -2
- package/lib/es/datePicker/monthsGrid.js +35 -11
- package/lib/es/datePicker/navigation.js +8 -0
- package/lib/es/datePicker/quickControl.js +2 -0
- package/lib/es/datePicker/yearAndMonth.js +1 -0
- package/lib/es/form/hoc/withField.js +1 -1
- package/lib/es/navigation/Item.d.ts +2 -2
- package/lib/es/navigation/Item.js +8 -6
- package/lib/es/navigation/SubNav.js +2 -2
- package/lib/es/scrollList/scrollItem.d.ts +2 -1
- package/lib/es/scrollList/scrollItem.js +13 -3
- package/lib/es/table/Body/index.d.ts +2 -0
- package/lib/es/table/Body/index.js +13 -4
- package/lib/es/tree/index.js +5 -3
- package/lib/es/tree/interface.d.ts +1 -0
- package/lib/es/tree/nodeList.js +2 -1
- package/lib/es/treeSelect/index.js +7 -3
- package/navigation/Item.tsx +15 -12
- package/navigation/SubNav.tsx +4 -4
- package/package.json +9 -9
- package/scrollList/_story/ScrollList/index.js +3 -0
- package/scrollList/_story/WheelList/index.js +3 -0
- package/scrollList/scrollItem.tsx +30 -9
- package/table/Body/index.tsx +15 -4
- package/table/__test__/table.test.js +18 -0
- package/table/_story/v2/FixedExpandedRow/index.jsx +95 -0
- package/table/_story/v2/index.js +2 -1
- package/tree/__test__/tree.test.js +87 -2
- package/tree/_story/tree.stories.js +65 -5
- package/tree/index.tsx +4 -2
- package/tree/interface.ts +1 -0
- package/tree/nodeList.tsx +2 -2
- package/treeSelect/__test__/treeSelect.test.js +28 -0
- package/treeSelect/_story/treeSelect.stories.js +55 -2
- package/treeSelect/index.tsx +11 -3
package/cascader/index.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { Fragment, ReactNode, CSSProperties, MouseEvent } from 'react';
|
|
1
|
+
import React, { Fragment, ReactNode, CSSProperties, MouseEvent, KeyboardEvent } from 'react';
|
|
2
2
|
import ReactDOM from 'react-dom';
|
|
3
3
|
import cls from 'classnames';
|
|
4
4
|
import PropTypes from 'prop-types';
|
|
@@ -54,6 +54,7 @@ export interface CascaderProps extends BasicCascaderProps {
|
|
|
54
54
|
'aria-invalid'?: React.AriaAttributes['aria-invalid'];
|
|
55
55
|
'aria-labelledby'?: React.AriaAttributes['aria-labelledby'];
|
|
56
56
|
'aria-required'?: React.AriaAttributes['aria-required'];
|
|
57
|
+
'aria-label'?: React.AriaAttributes['aria-label'];
|
|
57
58
|
arrowIcon?: ReactNode;
|
|
58
59
|
defaultValue?: Value;
|
|
59
60
|
dropdownStyle?: CSSProperties;
|
|
@@ -100,6 +101,7 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
|
|
|
100
101
|
'aria-errormessage': PropTypes.string,
|
|
101
102
|
'aria-describedby': PropTypes.string,
|
|
102
103
|
'aria-required': PropTypes.bool,
|
|
104
|
+
'aria-label': PropTypes.string,
|
|
103
105
|
arrowIcon: PropTypes.node,
|
|
104
106
|
changeOnSelect: PropTypes.bool,
|
|
105
107
|
defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
|
|
@@ -197,6 +199,7 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
|
|
|
197
199
|
onDropdownVisibleChange: noop,
|
|
198
200
|
onListScroll: noop,
|
|
199
201
|
enableLeafClick: false,
|
|
202
|
+
'aria-label': 'Cascader'
|
|
200
203
|
};
|
|
201
204
|
|
|
202
205
|
options: any;
|
|
@@ -584,7 +587,7 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
|
|
|
584
587
|
);
|
|
585
588
|
}
|
|
586
589
|
|
|
587
|
-
handleItemClick = (e: MouseEvent, item: Entity | Data) => {
|
|
590
|
+
handleItemClick = (e: MouseEvent | KeyboardEvent, item: Entity | Data) => {
|
|
588
591
|
this.foundation.handleItemClick(e, item);
|
|
589
592
|
};
|
|
590
593
|
|
|
@@ -708,9 +711,32 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
|
|
|
708
711
|
);
|
|
709
712
|
};
|
|
710
713
|
|
|
714
|
+
renderDisplayText = (): ReactNode => {
|
|
715
|
+
const { displayProp, separator, displayRender } = this.props;
|
|
716
|
+
const { selectedKeys } = this.state;
|
|
717
|
+
let displayText: ReactNode = '';
|
|
718
|
+
if (selectedKeys.size) {
|
|
719
|
+
const displayPath = this.foundation.getItemPropPath([...selectedKeys][0], displayProp);
|
|
720
|
+
if (displayRender && typeof displayRender === 'function') {
|
|
721
|
+
displayText = displayRender(displayPath);
|
|
722
|
+
} else {
|
|
723
|
+
displayText = displayPath.map((path: ReactNode, index: number)=>(
|
|
724
|
+
<Fragment key={`${path}-${index}`}>
|
|
725
|
+
{
|
|
726
|
+
index<displayPath.length-1
|
|
727
|
+
? <>{path}{separator}</>
|
|
728
|
+
: path
|
|
729
|
+
}
|
|
730
|
+
</Fragment>
|
|
731
|
+
));
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
return displayText;
|
|
735
|
+
}
|
|
736
|
+
|
|
711
737
|
renderSelectContent = () => {
|
|
712
738
|
const { placeholder, filterTreeNode, multiple } = this.props;
|
|
713
|
-
const {
|
|
739
|
+
const { checkedKeys } = this.state;
|
|
714
740
|
const searchable = Boolean(filterTreeNode);
|
|
715
741
|
if (!searchable) {
|
|
716
742
|
if (multiple) {
|
|
@@ -719,11 +745,9 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
|
|
|
719
745
|
}
|
|
720
746
|
return this.renderMultipleTags();
|
|
721
747
|
} else {
|
|
722
|
-
const displayText =
|
|
723
|
-
this.foundation.renderDisplayText([...selectedKeys][0]) :
|
|
724
|
-
'';
|
|
748
|
+
const displayText = this.renderDisplayText();
|
|
725
749
|
const spanCls = cls({
|
|
726
|
-
[`${prefixcls}-selection-placeholder`]: !displayText
|
|
750
|
+
[`${prefixcls}-selection-placeholder`]: !displayText,
|
|
727
751
|
});
|
|
728
752
|
return <span className={spanCls}>{displayText ? displayText : placeholder}</span>;
|
|
729
753
|
}
|
|
@@ -798,6 +822,14 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
|
|
|
798
822
|
this.foundation.handleClear();
|
|
799
823
|
};
|
|
800
824
|
|
|
825
|
+
/**
|
|
826
|
+
* A11y: simulate clear button click
|
|
827
|
+
*/
|
|
828
|
+
handleClearEnterPress = (e: KeyboardEvent) => {
|
|
829
|
+
e && e.stopPropagation();
|
|
830
|
+
this.foundation.handleClearEnterPress();
|
|
831
|
+
};
|
|
832
|
+
|
|
801
833
|
showClearBtn = () => {
|
|
802
834
|
const { showClear, disabled, multiple } = this.props;
|
|
803
835
|
const { selectedKeys, isOpen, isHovering, checkedKeys } = this.state;
|
|
@@ -811,7 +843,13 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
|
|
|
811
843
|
const allowClear = this.showClearBtn();
|
|
812
844
|
if (allowClear) {
|
|
813
845
|
return (
|
|
814
|
-
<div
|
|
846
|
+
<div
|
|
847
|
+
className={clearCls}
|
|
848
|
+
onClick={this.handleClear}
|
|
849
|
+
onKeyPress={this.handleClearEnterPress}
|
|
850
|
+
role='button'
|
|
851
|
+
tabIndex={0}
|
|
852
|
+
>
|
|
815
853
|
<IconClear />
|
|
816
854
|
</div>
|
|
817
855
|
);
|
|
@@ -891,6 +929,7 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
|
|
|
891
929
|
style={style}
|
|
892
930
|
ref={this.triggerRef}
|
|
893
931
|
onClick={e => this.foundation.handleClick(e)}
|
|
932
|
+
onKeyPress={e => this.foundation.handleSelectionEnterPress(e)}
|
|
894
933
|
aria-invalid={this.props['aria-invalid']}
|
|
895
934
|
aria-errormessage={this.props['aria-errormessage']}
|
|
896
935
|
aria-label={this.props['aria-label']}
|
package/cascader/item.tsx
CHANGED
|
@@ -2,6 +2,7 @@ import React, { PureComponent } from 'react';
|
|
|
2
2
|
import cls from 'classnames';
|
|
3
3
|
import PropTypes from 'prop-types';
|
|
4
4
|
import { cssClasses, strings } from '@douyinfe/semi-foundation/cascader/constants';
|
|
5
|
+
import isEnterPress from '@douyinfe/semi-foundation/utils/isEnterPress';
|
|
5
6
|
import { includes } from 'lodash';
|
|
6
7
|
import ConfigContext from '../configProvider/context';
|
|
7
8
|
import LocaleConsumer from '../locale/localeConsumer';
|
|
@@ -43,7 +44,7 @@ export interface CascaderItemProps {
|
|
|
43
44
|
selectedKeys: Set<string>;
|
|
44
45
|
loadedKeys: Set<string>;
|
|
45
46
|
loadingKeys: Set<string>;
|
|
46
|
-
onItemClick: (e: React.MouseEvent, item: Entity | Data) => void;
|
|
47
|
+
onItemClick: (e: React.MouseEvent | React.KeyboardEvent, item: Entity | Data) => void;
|
|
47
48
|
onItemHover: (e: React.MouseEvent, item: Entity) => void;
|
|
48
49
|
showNext: ShowNextType;
|
|
49
50
|
onItemCheckboxClick: (item: Entity | Data) => void;
|
|
@@ -84,7 +85,7 @@ export default class Item extends PureComponent<CascaderItemProps> {
|
|
|
84
85
|
empty: false,
|
|
85
86
|
};
|
|
86
87
|
|
|
87
|
-
onClick = (e: React.MouseEvent, item: Entity | Data) => {
|
|
88
|
+
onClick = (e: React.MouseEvent | React.KeyboardEvent, item: Entity | Data) => {
|
|
88
89
|
const { onItemClick } = this.props;
|
|
89
90
|
if (item.data.disabled || ('disabled' in item && item.disabled)) {
|
|
90
91
|
return;
|
|
@@ -92,6 +93,15 @@ export default class Item extends PureComponent<CascaderItemProps> {
|
|
|
92
93
|
onItemClick(e, item);
|
|
93
94
|
};
|
|
94
95
|
|
|
96
|
+
/**
|
|
97
|
+
* A11y: simulate item click
|
|
98
|
+
*/
|
|
99
|
+
handleItemEnterPress = (keyboardEvent: React.KeyboardEvent, item: Entity | Data) => {
|
|
100
|
+
if (isEnterPress(keyboardEvent)) {
|
|
101
|
+
this.onClick(keyboardEvent, item);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
95
105
|
onHover = (e: React.MouseEvent, item: Entity) => {
|
|
96
106
|
const { showNext, onItemHover } = this.props;
|
|
97
107
|
if (item.data.disabled) {
|
|
@@ -136,7 +146,7 @@ export default class Item extends PureComponent<CascaderItemProps> {
|
|
|
136
146
|
case 'loading':
|
|
137
147
|
return <Spin wrapperClassName={`${prefixcls}-spin-icon`} />;
|
|
138
148
|
case 'empty':
|
|
139
|
-
return (<span className={`${prefixcls}-icon ${prefixcls}-icon-empty`} />);
|
|
149
|
+
return (<span aria-hidden={true} className={`${prefixcls}-icon ${prefixcls}-icon-empty`} />);
|
|
140
150
|
default:
|
|
141
151
|
return null;
|
|
142
152
|
}
|
|
@@ -179,11 +189,13 @@ export default class Item extends PureComponent<CascaderItemProps> {
|
|
|
179
189
|
});
|
|
180
190
|
return (
|
|
181
191
|
<li
|
|
192
|
+
role='menuitem'
|
|
182
193
|
className={className}
|
|
183
194
|
key={key}
|
|
184
195
|
onClick={e => {
|
|
185
196
|
this.onClick(e, item);
|
|
186
197
|
}}
|
|
198
|
+
onKeyPress={e => this.handleItemEnterPress(e, item)}
|
|
187
199
|
>
|
|
188
200
|
<span className={`${prefixcls}-label`}>
|
|
189
201
|
{!multiple && this.renderIcon('empty')}
|
|
@@ -211,9 +223,9 @@ export default class Item extends PureComponent<CascaderItemProps> {
|
|
|
211
223
|
let showChildItem: Entity;
|
|
212
224
|
const ind = content.length;
|
|
213
225
|
content.push(
|
|
214
|
-
<ul className={`${prefixcls}-list`} key={renderData[0].key} onScroll={e => this.props.onListScroll(e, ind)}>
|
|
226
|
+
<ul role='menu' className={`${prefixcls}-list`} key={renderData[0].key} onScroll={e => this.props.onListScroll(e, ind)}>
|
|
215
227
|
{renderData.map(item => {
|
|
216
|
-
const { data, key } = item;
|
|
228
|
+
const { data, key, parentKey } = item;
|
|
217
229
|
const { children, label, disabled, isLeaf } = data;
|
|
218
230
|
const { active, selected, loading } = this.getItemStatus(key);
|
|
219
231
|
const hasChild = Boolean(children) && children.length;
|
|
@@ -226,13 +238,21 @@ export default class Item extends PureComponent<CascaderItemProps> {
|
|
|
226
238
|
[`${prefixcls}-select`]: selected && !multiple,
|
|
227
239
|
[`${prefixcls}-disabled`]: disabled
|
|
228
240
|
});
|
|
241
|
+
const otherAriaProps = parentKey ? { ['aria-owns']: `cascaderItem-${parentKey}` } : {};
|
|
229
242
|
return (
|
|
230
243
|
<li
|
|
244
|
+
role='menuitem'
|
|
245
|
+
id={`cascaderItem-${key}`}
|
|
246
|
+
aria-expanded={active}
|
|
247
|
+
aria-haspopup={Boolean(showExpand)}
|
|
248
|
+
aria-disabled={disabled}
|
|
249
|
+
{...otherAriaProps}
|
|
231
250
|
className={className}
|
|
232
251
|
key={key}
|
|
233
252
|
onClick={e => {
|
|
234
253
|
this.onClick(e, item);
|
|
235
254
|
}}
|
|
255
|
+
onKeyPress={e => this.handleItemEnterPress(e, item)}
|
|
236
256
|
onMouseEnter={e => {
|
|
237
257
|
this.onHover(e, item);
|
|
238
258
|
}}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { DatePicker, Space, Input, Button, Select } from '@douyinfe/semi-ui';
|
|
3
|
+
import { Position } from '@douyinfe/semi-foundation/tooltip/foundation';
|
|
4
|
+
import { strings } from '@douyinfe/semi-foundation/tooltip/constants';
|
|
5
|
+
import * as dateFns from 'date-fns';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Test with Chromatic
|
|
9
|
+
*/
|
|
10
|
+
export default function App() {
|
|
11
|
+
const spacing = [200, 400];
|
|
12
|
+
const [date, setDate] = React.useState();
|
|
13
|
+
const [insetInput, setInsetInput] = React.useState(true);
|
|
14
|
+
const [position, setPosition] = React.useState('leftTopOver');
|
|
15
|
+
const [needConfirm, setNeedConfirm] = React.useState(false);
|
|
16
|
+
const [density, setDensity] = React.useState(true);
|
|
17
|
+
|
|
18
|
+
const props = {
|
|
19
|
+
defaultOpen: true,
|
|
20
|
+
motion: false,
|
|
21
|
+
insetInput,
|
|
22
|
+
defaultPickerValue: '2021-12-01',
|
|
23
|
+
position,
|
|
24
|
+
density: density ? 'compact' : 'default',
|
|
25
|
+
autoAdjustOverflow: false,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const positionOptionList = strings.POSITION_SET.map((position) => ({
|
|
29
|
+
value: position,
|
|
30
|
+
label: position,
|
|
31
|
+
key: position,
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
const triggerRender = ({ placeholder }) => {
|
|
35
|
+
const format = 'Pp';
|
|
36
|
+
const value = (date && dateFns.format(date, format)) || placeholder;
|
|
37
|
+
return <Input value={value} readOnly />;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const handleDateChange = (date) => {
|
|
41
|
+
setDate(date);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const handleBtnClick = () => {
|
|
45
|
+
setInsetInput(!insetInput);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const handleReset = () => {
|
|
49
|
+
setInsetInput(true);
|
|
50
|
+
setPosition('leftTopOver');
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const handleToggleNeedConfirm = () => {
|
|
54
|
+
setNeedConfirm(!needConfirm);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const handleToggleDensity = () => {
|
|
58
|
+
setDensity(!density);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div style={{ height: '200vh' }}>
|
|
63
|
+
<div style={{ marginBottom: 12 }}>
|
|
64
|
+
<Space>
|
|
65
|
+
<Button onClick={handleBtnClick}>
|
|
66
|
+
{`insetInput=${insetInput}`}
|
|
67
|
+
</Button>
|
|
68
|
+
<Button onClick={handleToggleNeedConfirm}>
|
|
69
|
+
{`needConfirm=${needConfirm}`}
|
|
70
|
+
</Button>
|
|
71
|
+
<Button onClick={handleToggleDensity}>
|
|
72
|
+
{`小尺寸=${density}`}
|
|
73
|
+
</Button>
|
|
74
|
+
<Select
|
|
75
|
+
style={{ width: 200 }}
|
|
76
|
+
optionList={positionOptionList}
|
|
77
|
+
placeholder='选择position'
|
|
78
|
+
value={position}
|
|
79
|
+
onChange={setPosition}
|
|
80
|
+
showClear
|
|
81
|
+
/>
|
|
82
|
+
<Button onClick={handleReset} theme="solid">reset</Button>
|
|
83
|
+
</Space>
|
|
84
|
+
</div>
|
|
85
|
+
<Space wrap spacing={spacing}>
|
|
86
|
+
<DatePicker placeholder='选择单个日期' {...props} />
|
|
87
|
+
<DatePicker placeholder='选择月' {...props} type='month' />
|
|
88
|
+
<DatePicker placeholder='选择日期时间' {...props} type='dateTime' needConfirm={needConfirm} />
|
|
89
|
+
<DatePicker placeholder='选择日期范围' {...props} type='dateRange' />
|
|
90
|
+
<DatePicker placeholder='选择日期时间范围' {...props} type='dateTimeRange' needConfirm={needConfirm} />
|
|
91
|
+
<DatePicker placeholder='format=Pp' {...props} format='yyyy-MM-dd HH:mm:ss' type='dateTimeRange' />
|
|
92
|
+
</Space>
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
App.parameters = {
|
|
98
|
+
chromatic: {
|
|
99
|
+
disableSnapshot: false,
|
|
100
|
+
delay: 3000,
|
|
101
|
+
viewports: [1800]
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
App.storyName = 'inset input';
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { DatePicker, Space, Button } from '@douyinfe/semi-ui';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Test with Cypress
|
|
6
|
+
* Don't modify DOM structure
|
|
7
|
+
*/
|
|
8
|
+
export default function App() {
|
|
9
|
+
const [needConfirm, setNeedConfirm] = React.useState(false);
|
|
10
|
+
|
|
11
|
+
const spacing = [200, 400];
|
|
12
|
+
const props = {
|
|
13
|
+
insetInput: true,
|
|
14
|
+
defaultPickerValue: '2021-12-01',
|
|
15
|
+
motion: false,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const handleToggleNeedConfirm = () => {
|
|
19
|
+
setNeedConfirm(!needConfirm);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div style={{ height: '300vh' }}>
|
|
24
|
+
<div style={{ marginBottom: 12 }}>
|
|
25
|
+
<Space>
|
|
26
|
+
<Button onClick={handleToggleNeedConfirm} data-cy="btn">
|
|
27
|
+
{`needConfirm=${needConfirm}`}
|
|
28
|
+
</Button>
|
|
29
|
+
</Space>
|
|
30
|
+
</div>
|
|
31
|
+
<Space wrap spacing={spacing}>
|
|
32
|
+
<div data-cy="date">
|
|
33
|
+
<DatePicker placeholder="选择单个日期" {...props} />
|
|
34
|
+
</div>
|
|
35
|
+
<div data-cy="month">
|
|
36
|
+
<DatePicker
|
|
37
|
+
{...props}
|
|
38
|
+
placeholder="选择月"
|
|
39
|
+
type="month"
|
|
40
|
+
defaultValue="2021-12"
|
|
41
|
+
timePickerOpts={{
|
|
42
|
+
scrollItemProps: { motion: false },
|
|
43
|
+
}}
|
|
44
|
+
/>
|
|
45
|
+
</div>
|
|
46
|
+
<div data-cy="dateTime">
|
|
47
|
+
<DatePicker placeholder="选择日期时间" {...props} type="dateTime" needConfirm={needConfirm} />
|
|
48
|
+
</div>
|
|
49
|
+
<div data-cy="dateRange">
|
|
50
|
+
<DatePicker placeholder="选择日期范围" {...props} type="dateRange" />
|
|
51
|
+
</div>
|
|
52
|
+
<div data-cy="dateTimeRange">
|
|
53
|
+
<DatePicker
|
|
54
|
+
placeholder="选择日期时间范围"
|
|
55
|
+
{...props}
|
|
56
|
+
type="dateTimeRange"
|
|
57
|
+
needConfirm={needConfirm}
|
|
58
|
+
/>
|
|
59
|
+
</div>
|
|
60
|
+
<div data-cy="customFormat">
|
|
61
|
+
<DatePicker placeholder="选择日期范围" {...props} type="dateRange" format="Pp" />
|
|
62
|
+
</div>
|
|
63
|
+
</Space>
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
App.parameters = { chromatic: { disableSnapshot: true } };
|
|
69
|
+
App.storyName = 'inset input e2e test';
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export { default as YearButton } from './YearButton';
|
|
2
2
|
export { default as PanelOpen } from './PanelOpen';
|
|
3
3
|
export { default as FixInputRangeFocus } from './FixInputRangeFocus';
|
|
4
|
+
export { default as InsetInput } from './InsetInput';
|
|
5
|
+
export { default as InsetInputE2E } from './InsetInputE2E';
|
package/datePicker/dateInput.tsx
CHANGED
|
@@ -1,21 +1,28 @@
|
|
|
1
|
+
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
|
2
|
+
/* eslint-disable jsx-a11y/no-static-element-interactions */
|
|
1
3
|
/* eslint-disable max-lines-per-function */
|
|
2
4
|
/* eslint-disable no-unused-vars */
|
|
3
5
|
import React from 'react';
|
|
4
6
|
import cls from 'classnames';
|
|
5
7
|
import PropTypes from 'prop-types';
|
|
8
|
+
|
|
6
9
|
import DateInputFoundation, {
|
|
7
10
|
DateInputAdapter,
|
|
8
11
|
DateInputFoundationProps,
|
|
9
|
-
RangeType
|
|
12
|
+
RangeType,
|
|
13
|
+
InsetInputChangeProps,
|
|
14
|
+
InsetInputChangeFoundationProps,
|
|
10
15
|
} from '@douyinfe/semi-foundation/datePicker/inputFoundation';
|
|
11
16
|
import { cssClasses, strings } from '@douyinfe/semi-foundation/datePicker/constants';
|
|
12
17
|
import { noop } from '@douyinfe/semi-foundation/utils/function';
|
|
13
18
|
import isNullOrUndefined from '@douyinfe/semi-foundation/utils/isNullOrUndefined';
|
|
14
|
-
import BaseComponent, { BaseProps } from '../_base/baseComponent';
|
|
15
|
-
import Input from '../input/index';
|
|
16
19
|
import { IconCalendar, IconCalendarClock, IconClear } from '@douyinfe/semi-icons';
|
|
17
20
|
import { BaseValueType, ValueType } from '@douyinfe/semi-foundation/datePicker/foundation';
|
|
18
21
|
|
|
22
|
+
import BaseComponent, { BaseProps } from '../_base/baseComponent';
|
|
23
|
+
import Input from '../input/index';
|
|
24
|
+
import { InsetDateInput, InsetTimeInput } from './insetInput';
|
|
25
|
+
|
|
19
26
|
export interface DateInputProps extends DateInputFoundationProps, BaseProps {
|
|
20
27
|
insetLabel?: React.ReactNode;
|
|
21
28
|
prefix?: React.ReactNode;
|
|
@@ -25,9 +32,10 @@ export interface DateInputProps extends DateInputFoundationProps, BaseProps {
|
|
|
25
32
|
onBlur?: (e: React.MouseEvent<HTMLInputElement>) => void;
|
|
26
33
|
onFocus?: (e: React.MouseEvent<HTMLInputElement>, rangeType?: RangeType) => void;
|
|
27
34
|
onClear?: (e: React.MouseEvent<HTMLDivElement>) => void;
|
|
35
|
+
onInsetInputChange?: (options: InsetInputChangeProps) => void;
|
|
36
|
+
value?: Date[];
|
|
28
37
|
}
|
|
29
38
|
|
|
30
|
-
|
|
31
39
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
32
40
|
export default class DateInput extends BaseComponent<DateInputProps, {}> {
|
|
33
41
|
static propTypes = {
|
|
@@ -40,7 +48,6 @@ export default class DateInput extends BaseComponent<DateInputProps, {}> {
|
|
|
40
48
|
value: PropTypes.array,
|
|
41
49
|
disabled: PropTypes.bool,
|
|
42
50
|
type: PropTypes.oneOf(strings.TYPE_SET),
|
|
43
|
-
multiple: PropTypes.bool,
|
|
44
51
|
showClear: PropTypes.bool,
|
|
45
52
|
format: PropTypes.string, // Attributes not used
|
|
46
53
|
inputStyle: PropTypes.object,
|
|
@@ -55,6 +62,8 @@ export default class DateInput extends BaseComponent<DateInputProps, {}> {
|
|
|
55
62
|
rangeInputStartRef: PropTypes.object,
|
|
56
63
|
rangeInputEndRef: PropTypes.object,
|
|
57
64
|
rangeSeparator: PropTypes.string,
|
|
65
|
+
insetInput: PropTypes.bool,
|
|
66
|
+
insetInputValue: PropTypes.object,
|
|
58
67
|
};
|
|
59
68
|
|
|
60
69
|
static defaultProps = {
|
|
@@ -65,7 +74,6 @@ export default class DateInput extends BaseComponent<DateInputProps, {}> {
|
|
|
65
74
|
onBlur: noop,
|
|
66
75
|
onClear: noop,
|
|
67
76
|
onFocus: noop,
|
|
68
|
-
multiple: false,
|
|
69
77
|
type: 'date',
|
|
70
78
|
inputStyle: {},
|
|
71
79
|
inputReadOnly: false,
|
|
@@ -92,6 +100,7 @@ export default class DateInput extends BaseComponent<DateInputProps, {}> {
|
|
|
92
100
|
notifyRangeInputClear: (...args) => this.props.onRangeClear(...args),
|
|
93
101
|
notifyRangeInputFocus: (...args) => this.props.onFocus(...args),
|
|
94
102
|
notifyTabPress: (...args) => this.props.onRangeEndTabPress(...args),
|
|
103
|
+
notifyInsetInputChange: options => this.props.onInsetInputChange(options),
|
|
95
104
|
};
|
|
96
105
|
}
|
|
97
106
|
|
|
@@ -140,6 +149,10 @@ export default class DateInput extends BaseComponent<DateInputProps, {}> {
|
|
|
140
149
|
this.handleRangeInputFocus(e, 'rangeStart');
|
|
141
150
|
};
|
|
142
151
|
|
|
152
|
+
handleInsetInputChange = (options: InsetInputChangeFoundationProps) => {
|
|
153
|
+
this.foundation.handleInsetInputChange(options);
|
|
154
|
+
};
|
|
155
|
+
|
|
143
156
|
getRangeInputValue = (rangeStart: string, rangeEnd: string) => {
|
|
144
157
|
const { rangeSeparator } = this.props;
|
|
145
158
|
const rangeInputValue = `${rangeStart}${rangeSeparator}${rangeEnd}`;
|
|
@@ -177,9 +190,12 @@ export default class DateInput extends BaseComponent<DateInputProps, {}> {
|
|
|
177
190
|
const allowClear = (rangeStart || rangeEnd) && showClear;
|
|
178
191
|
return allowClear && !disabled ? (
|
|
179
192
|
<div
|
|
193
|
+
role="button"
|
|
194
|
+
tabIndex={0}
|
|
195
|
+
aria-label="Clear range input value"
|
|
180
196
|
className={`${prefixCls}-range-input-clearbtn`}
|
|
181
197
|
onMouseDown={e => !disabled && this.handleRangeInputClear(e)}>
|
|
182
|
-
<IconClear />
|
|
198
|
+
<IconClear aria-hidden />
|
|
183
199
|
</div>
|
|
184
200
|
) : null;
|
|
185
201
|
}
|
|
@@ -223,11 +239,11 @@ export default class DateInput extends BaseComponent<DateInputProps, {}> {
|
|
|
223
239
|
const rangePlaceholder = Array.isArray(placeholder) ? placeholder : [placeholder, placeholder];
|
|
224
240
|
const [rangeStartPlaceholder, rangeEndPlaceholder] = rangePlaceholder;
|
|
225
241
|
const inputLeftWrapperCls = cls(`${prefixCls}-range-input-wrapper-start`, `${prefixCls}-range-input-wrapper`, {
|
|
226
|
-
[`${prefixCls}-range-input-wrapper-active`]: rangeInputFocus === 'rangeStart',
|
|
227
|
-
[`${prefixCls}-range-input-wrapper-start-with-prefix`]: this.props.prefix || this.props.insetLabel
|
|
242
|
+
[`${prefixCls}-range-input-wrapper-active`]: rangeInputFocus === 'rangeStart' && !disabled,
|
|
243
|
+
[`${prefixCls}-range-input-wrapper-start-with-prefix`]: this.props.prefix || this.props.insetLabel,
|
|
228
244
|
});
|
|
229
245
|
const inputRightWrapperCls = cls(`${prefixCls}-range-input-wrapper-end`, `${prefixCls}-range-input-wrapper`, {
|
|
230
|
-
[`${prefixCls}-range-input-wrapper-active`]: rangeInputFocus === 'rangeEnd'
|
|
246
|
+
[`${prefixCls}-range-input-wrapper-active`]: rangeInputFocus === 'rangeEnd' && !disabled,
|
|
231
247
|
});
|
|
232
248
|
return (
|
|
233
249
|
<>
|
|
@@ -279,7 +295,72 @@ export default class DateInput extends BaseComponent<DateInputProps, {}> {
|
|
|
279
295
|
);
|
|
280
296
|
}
|
|
281
297
|
|
|
282
|
-
|
|
298
|
+
renderInputInset() {
|
|
299
|
+
const {
|
|
300
|
+
type,
|
|
301
|
+
handleInsetDateFocus,
|
|
302
|
+
handleInsetTimeFocus,
|
|
303
|
+
value,
|
|
304
|
+
insetInputValue,
|
|
305
|
+
prefixCls,
|
|
306
|
+
rangeInputStartRef,
|
|
307
|
+
rangeInputEndRef,
|
|
308
|
+
density,
|
|
309
|
+
} = this.props;
|
|
310
|
+
|
|
311
|
+
const _isRangeType = type.includes('Range');
|
|
312
|
+
const newInsetInputValue = this.foundation.getInsetInputValue({ value, insetInputValue });
|
|
313
|
+
const { datePlaceholder, timePlaceholder } = this.foundation.getInsetInputPlaceholder();
|
|
314
|
+
|
|
315
|
+
const insetInputWrapperCls = `${prefixCls}-inset-input-wrapper`;
|
|
316
|
+
const separatorCls = `${prefixCls}-inset-input-separator`;
|
|
317
|
+
|
|
318
|
+
return (
|
|
319
|
+
<div className={insetInputWrapperCls} x-type={type}>
|
|
320
|
+
<InsetDateInput
|
|
321
|
+
forwardRef={rangeInputStartRef}
|
|
322
|
+
insetInputValue={newInsetInputValue}
|
|
323
|
+
placeholder={datePlaceholder}
|
|
324
|
+
valuePath={'monthLeft.dateInput'}
|
|
325
|
+
onChange={this.handleInsetInputChange}
|
|
326
|
+
onFocus={e => handleInsetDateFocus(e, 'rangeStart')}
|
|
327
|
+
/>
|
|
328
|
+
<InsetTimeInput
|
|
329
|
+
disabled={!newInsetInputValue.monthLeft.dateInput}
|
|
330
|
+
insetInputValue={newInsetInputValue}
|
|
331
|
+
placeholder={timePlaceholder}
|
|
332
|
+
type={type}
|
|
333
|
+
valuePath={'monthLeft.timeInput'}
|
|
334
|
+
onChange={this.handleInsetInputChange}
|
|
335
|
+
onFocus={handleInsetTimeFocus}
|
|
336
|
+
/>
|
|
337
|
+
{_isRangeType && (
|
|
338
|
+
<>
|
|
339
|
+
<div className={separatorCls}>{density === 'compact' ? null : '-'}</div>
|
|
340
|
+
<InsetDateInput
|
|
341
|
+
forwardRef={rangeInputEndRef}
|
|
342
|
+
insetInputValue={newInsetInputValue}
|
|
343
|
+
placeholder={datePlaceholder}
|
|
344
|
+
valuePath={'monthRight.dateInput'}
|
|
345
|
+
onChange={this.handleInsetInputChange}
|
|
346
|
+
onFocus={e => handleInsetDateFocus(e, 'rangeEnd')}
|
|
347
|
+
/>
|
|
348
|
+
<InsetTimeInput
|
|
349
|
+
disabled={!newInsetInputValue.monthRight.dateInput}
|
|
350
|
+
insetInputValue={newInsetInputValue}
|
|
351
|
+
placeholder={timePlaceholder}
|
|
352
|
+
type={type}
|
|
353
|
+
valuePath={'monthRight.timeInput'}
|
|
354
|
+
onChange={this.handleInsetInputChange}
|
|
355
|
+
onFocus={handleInsetTimeFocus}
|
|
356
|
+
/>
|
|
357
|
+
</>
|
|
358
|
+
)}
|
|
359
|
+
</div>
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
renderTriggerInput() {
|
|
283
364
|
const {
|
|
284
365
|
placeholder,
|
|
285
366
|
type,
|
|
@@ -293,6 +374,7 @@ export default class DateInput extends BaseComponent<DateInputProps, {}> {
|
|
|
293
374
|
validateStatus,
|
|
294
375
|
block,
|
|
295
376
|
prefixCls,
|
|
377
|
+
multiple, // Whether to allow multiple values for email and file types
|
|
296
378
|
dateFnsLocale, // No need to pass to input
|
|
297
379
|
onBlur,
|
|
298
380
|
onClear,
|
|
@@ -308,10 +390,12 @@ export default class DateInput extends BaseComponent<DateInputProps, {}> {
|
|
|
308
390
|
onRangeEndTabPress,
|
|
309
391
|
rangeInputFocus,
|
|
310
392
|
rangeSeparator,
|
|
393
|
+
insetInput,
|
|
394
|
+
insetInputValue,
|
|
311
395
|
...rest
|
|
312
396
|
} = this.props;
|
|
313
|
-
const dateIcon = <IconCalendar />;
|
|
314
|
-
const dateTimeIcon = <IconCalendarClock />;
|
|
397
|
+
const dateIcon = <IconCalendar aria-hidden />;
|
|
398
|
+
const dateTimeIcon = <IconCalendarClock aria-hidden />;
|
|
315
399
|
const suffix = type.includes('Time') ? dateTimeIcon : dateIcon;
|
|
316
400
|
let text = '';
|
|
317
401
|
|
|
@@ -355,4 +439,9 @@ export default class DateInput extends BaseComponent<DateInputProps, {}> {
|
|
|
355
439
|
/>
|
|
356
440
|
);
|
|
357
441
|
}
|
|
442
|
+
|
|
443
|
+
render() {
|
|
444
|
+
const { insetInput } = this.props;
|
|
445
|
+
return insetInput ? this.renderInputInset() : this.renderTriggerInput();
|
|
446
|
+
}
|
|
358
447
|
}
|