Browse Source

feat: Optimize the scrolling animation, change the ScrollItem mode in… (#1211)

* feat: Optimize the scrolling animation, change the ScrollItem mode in timepicker and datePicker to normal

* feat: Optimize the scrolling animation, change the ScrollItem mode in timepicker and datePicker to normal
YannLynn 3 years ago
parent
commit
829a686f39

+ 1 - 0
content/input/datepicker/index-en-US.md

@@ -922,6 +922,7 @@ function Demo() {
 | onOpenChange       | Callback when popup open or close                                                                                                                                 | (isOpen) => void                                                                                                                                                                                 |                                                                                       |                           |
 | onPanelChange      | Callback when the year or date of the panel is switched|  <ApiType detail='(date: DateType \| DateType[], dateStr: StringType \| StringType[])=>void'>(date, dateStr) => void</ApiType>  |  |**1.28.0**|
 | onPresetClick      | Callback when click preset button                                                                          | <ApiType detail='(item: Object, e: Event) => void'>(item, e) => void</ApiType>       |   **1.24.0**                           |
+| yearAndMonthOpts | Other parameters that can be transparently passed to the year-month selector, see details in [ScrollList#API](/zh-CN/show/scrolllist#ScrollItem)|  | object | **2.22.0** |
 
 
 ## Interface Define

+ 1 - 0
content/input/datepicker/index.md

@@ -892,6 +892,7 @@ function Demo() {
 | onOpenChange | 面板显示或隐藏状态切换的回调 | <ApiType detail='(isOpen: boolean) => void'>(isOpen) => void</ApiType> |  |  |
 | onPanelChange | 切换面板的年份或者日期时的回调 | <ApiType detail='(date: DateType \| DateType[], dateStr: StringType \| StringType[])=>void'>(date, dateStr) => void</ApiType> | function | **1.28.0** |
 | onPresetClick | 点击快捷选择按钮的回调 | <ApiType detail='(item: Object, e: Event) => void'>(item, e) => void</ApiType> | () => {}  | **1.24.0** |
+| yearAndMonthOpts | 其他可以透传给年月选择器的参数,详见 [ScrollList#API](/zh-CN/show/scrolllist#ScrollItem)|  | object | **2.20.0** |
 
 ## 类型定义
 

+ 14 - 0
content/input/timepicker/index-en-US.md

@@ -29,6 +29,20 @@ function Demo() {
 }
 ```
 
+### Infinite Scroll
+
+Starting from version V2.22.0, we changed the default mode of ScrollItem in TimePicker from `wheel` to `normal`. If you want to apply the effect of infinite scrolling back, please refer to the following example.
+
+```jsx live=true
+import React from 'react';
+import { TimePicker } from '@douyinfe/semi-ui';
+
+function Demo() {
+    return <TimePicker scrollItemProps={{ mode: "wheel", cycled: true }}/>;
+}
+```
+
+
 ### With an Embedded Label
 
 ```jsx live=true

+ 13 - 0
content/input/timepicker/index.md

@@ -28,6 +28,19 @@ function Demo() {
 }
 ```
 
+### 无限滚动
+
+版本V2.22.0开始,我们将 TimePicker 内的 ScrollItem 的默认模式从 `wheel` 变更为了 `normal`, 若想应用回无限滚动的效果,可参考以下示例。
+
+```jsx live=true
+import React from 'react';
+import { TimePicker } from '@douyinfe/semi-ui';
+
+function Demo() {
+    return <TimePicker scrollItemProps={{ mode: "wheel", cycled: true }}/>;
+}
+```
+
 ### 带内嵌标签
 
 ```jsx live=true

+ 1 - 0
content/show/scrolllist/index-en-US.md

@@ -128,6 +128,7 @@ class ScrollListDemo extends React.Component {
 | cycled      | Whether it is an infinite loop, effective only if the mode is "wheel"                | boolean                  | false   |
 | className   | classname of scroll item                | string                  | ''   |
 | list        | List content                                                                         | [Item Data](#ItemData)[] | []      |
+| mode        | mode selection                                                                       | "normal" \| "wheel"      | "wheel"|
 | motion      | Whether to start the scroll animation                                                | Motion                  | true    |
 | onSelect    | Select callback                                                                      | (data: [ItemData](#ItemData)) => void                 | NOOP    |
 | selectIndex | Index of selected items                                                              | number                   | 0       |

+ 11 - 10
content/show/scrolllist/index.md

@@ -145,16 +145,17 @@ class ScrollListDemo extends React.Component {
 
 ### ScrollItem
 
-| 属性        | 说明                                        | 类型                    | 默认值 |
-| ----------- | ------------------------------------------- | ----------------------- | ------ |
-| className   | 样式类名 | string             | ''  |
-| cycled      | 是否为无限循环,仅在 mode 为 "wheel" 时生效 | boolean             | false  |
-| list        | 列表内容                                    | [ItemData](#ItemData)[] | []     |
-| motion      | 是否开启滚动动画                            | Motion                 | true   |
-| onSelect    | 选中回调                                    | (data: [ItemData](#ItemData)) => void                | NOOP   |
-| selectIndex | 选中项的索引                                | number                  | 0      |
-| style       | 内联样式                                   | CSSProperties                  | {}      |
-| transform   | 对选中项的变换,返回值会作为文案进行显示    | (value: any, text: string) => string                | v => v |
+| 属性        | 说明                                                | 类型                                | 默认值 |
+| ----------- | -------------------------------------------------- | ----------------------------------- | ------ |
+| className   | 样式类名 | string                                   | ''                                  |
+| cycled      | 是否为无限循环,仅在 mode 为 "wheel" 时生效 | boolean  | false                                |
+| list        | 列表内容                                            | [ItemData](#ItemData)[]              | []     |
+| mode        | 模式选择                                            | "normal" \| "wheel"                  | "wheel"|
+| motion      | 是否开启滚动动画                                     | Motion                                | true   |
+| onSelect    | 选中回调                                            | (data: [ItemData](#ItemData)) => void| NOOP   |
+| selectIndex | 选中项的索引                                         | number                               | 0      |
+| style       | 内联样式                                            | CSSProperties                        | {}      |
+| transform   | 对选中项的变换,返回值会作为文案进行显示                  | (value: any, text: string) => string | v => v |
 
 #### ItemData
 

+ 10 - 0
packages/semi-foundation/datePicker/datePicker.scss

@@ -2,12 +2,22 @@
 @import "./variables.scss";
 
 $module: #{$prefix}-datepicker;
+$module-list: #{$prefix}-scrolllist;
 
 .#{$module} {
     box-sizing: border-box;
     display: inline-block;
     vertical-align: top;
 
+    .#{$module-list}-body {
+
+        .#{$module-list}-item {
+            &::-webkit-scrollbar {
+                display: none;
+            }
+        }
+    }
+
     // 双月网格
 
     &-month-grid {

+ 1 - 0
packages/semi-foundation/datePicker/yearAndMonthFoundation.ts

@@ -16,6 +16,7 @@ export interface YearAndMonthFoundationProps {
     presetPosition?: PresetPosition;
     renderQuickControls?: any;
     renderDateInput?: any;
+    yearAndMonthOpts?: any;
 }
 
 export interface YearAndMonthFoundationState {

+ 1 - 0
packages/semi-foundation/scrollList/scrollList.scss

@@ -126,6 +126,7 @@ $module: #{$prefix}-scrolllist;
 
             .#{$module}-item-selected {
                 font-weight: $font-scrollList_item_wheel_item_selected-fontWeight;
+                color: var(--semi-color-primary) !important;
             }
 
             .#{$module}-list-outer {

+ 6 - 0
packages/semi-foundation/timePicker/timePicker.scss

@@ -24,6 +24,12 @@ $module-list: #{$prefix}-scrolllist;
                     padding-bottom: ($height-timePicker_panel_body - $height-scrollList_item) * 0.5;
                 }
             }
+            .#{$module-list}-item {
+                &::-webkit-scrollbar {
+                    display: none;
+                }
+            }
+
         }
 
         .#{$module-list}-item,

+ 8 - 2
packages/semi-ui/datePicker/datePicker.tsx

@@ -21,6 +21,7 @@ import YearAndMonth, { YearAndMonthProps } from './yearAndMonth';
 import '@douyinfe/semi-foundation/datePicker/datePicker.scss';
 import { Locale } from '../locale/interface';
 import { TimePickerProps } from '../timePicker/TimePicker';
+import { ScrollItemProps } from '../scrollList/scrollItem';
 import { InsetInputValue, InsetInputChangeProps } from '@douyinfe/semi-foundation/datePicker/inputFoundation';
 
 export interface DatePickerProps extends DatePickerFoundationProps {
@@ -44,6 +45,7 @@ export interface DatePickerProps extends DatePickerFoundationProps {
     onPresetClick?: (item: PresetType, e: React.MouseEvent<HTMLDivElement>) => void;
     locale?: Locale['DatePicker'];
     dateFnsLocale?: Locale['dateFnsLocale'];
+    yearAndMonthOpts?: ScrollItemProps<any>;
 }
 
 export type DatePickerState = DatePickerFoundationState;
@@ -127,6 +129,7 @@ export default class DatePicker extends BaseComponent<DatePickerProps, DatePicke
         onPanelChange: PropTypes.func,
         rangeSeparator: PropTypes.string,
         preventScroll: PropTypes.bool,
+        yearAndMonthOpts: PropTypes.object
     };
 
     static defaultProps = {
@@ -418,7 +421,8 @@ export default class DatePicker extends BaseComponent<DatePickerProps, DatePicke
             timeZone,
             triggerRender,
             insetInput,
-            presetPosition
+            presetPosition,
+            yearAndMonthOpts
         } = this.props;
         const { cachedSelectedValue, motionEnd, rangeInputFocus } = this.state;
 
@@ -462,6 +466,7 @@ export default class DatePicker extends BaseComponent<DatePickerProps, DatePicke
                 presetPosition={presetPosition}
                 renderQuickControls={this.renderQuickControls()}
                 renderDateInput={this.renderDateInput()}
+                yearAndMonthOpts={yearAndMonthOpts}
             />
         );
     }
@@ -708,7 +713,7 @@ export default class DatePicker extends BaseComponent<DatePickerProps, DatePicke
     };
 
     renderYearMonthPanel = (locale: Locale['DatePicker'], localeCode: string) => {
-        const { density, presetPosition } = this.props;
+        const { density, presetPosition, yearAndMonthOpts } = this.props;
 
         const date = this.state.value[0];
         let year = 0;
@@ -733,6 +738,7 @@ export default class DatePicker extends BaseComponent<DatePickerProps, DatePicke
                 presetPosition={presetPosition}
                 renderQuickControls={this.renderQuickControls()}
                 renderDateInput={this.renderDateInput()}
+                yearAndMonthOpts={yearAndMonthOpts}
             />
         );
     };

+ 9 - 6
packages/semi-ui/datePicker/monthsGrid.tsx

@@ -20,6 +20,7 @@ import YearAndMonth from './yearAndMonth';
 import { IconClock, IconCalendar } from '@douyinfe/semi-icons';
 import { getDefaultFormatTokenByType } from '@douyinfe/semi-foundation/datePicker/_utils/getDefaultFormatToken';
 import getDefaultPickerDate from '@douyinfe/semi-foundation/datePicker/_utils/getDefaultPickerDate';
+import { ScrollItemProps } from '../scrollList/scrollItem';
 
 const prefixCls = cssClasses.PREFIX;
 
@@ -29,6 +30,7 @@ export interface MonthsGridProps extends MonthsGridFoundationProps, BaseProps {
     renderDate?: () => React.ReactNode;
     renderFullDate?: () => React.ReactNode;
     focusRecordsRef?: React.RefObject<{ rangeStart: boolean; rangeEnd: boolean }>;
+    yearAndMonthOpts?: ScrollItemProps<any>;
 }
 
 export type MonthsGridState = MonthsGridFoundationState;
@@ -508,7 +510,7 @@ export default class MonthsGrid extends BaseComponent<MonthsGridProps, MonthsGri
 
     renderYearAndMonth(panelType: PanelType, panelDetail: MonthInfo) {
         const { pickerDate } = panelDetail;
-        const { locale, localeCode, density } = this.props;
+        const { locale, localeCode, density, yearAndMonthOpts } = this.props;
         const y = pickerDate.getFullYear();
         const m = pickerDate.getMonth() + 1;
         return (
@@ -529,6 +531,7 @@ export default class MonthsGrid extends BaseComponent<MonthsGridProps, MonthsGri
                     }
                 }}
                 density={density}
+                yearAndMonthOpts={yearAndMonthOpts}
             />
         );
     }
@@ -569,13 +572,13 @@ export default class MonthsGrid extends BaseComponent<MonthsGridProps, MonthsGri
 
         const switchCls = classnames(`${prefixCls}-switch`);
         const dateCls = classnames({
-            [`${prefixCls }-switch-date`]: true,
-            [`${prefixCls }-switch-date-active`]: !isTimePickerOpen,
+            [`${prefixCls}-switch-date`]: true,
+            [`${prefixCls}-switch-date-active`]: !isTimePickerOpen,
         });
         const timeCls = classnames({
-            [`${prefixCls }-switch-time`]: true,
+            [`${prefixCls}-switch-time`]: true,
             [`${prefixCls}-switch-time-disabled`]: disabledTimePicker,
-            [`${prefixCls }-switch-date-active`]: isTimePickerOpen,
+            [`${prefixCls}-switch-date-active`]: isTimePickerOpen,
         });
         const textCls = classnames(`${prefixCls}-switch-text`);
 
@@ -608,7 +611,7 @@ export default class MonthsGrid extends BaseComponent<MonthsGridProps, MonthsGri
         const { monthLeft, monthRight } = this.state;
         const { type, insetInput, presetPosition, renderQuickControls, renderDateInput } = this.props;
         const monthGridCls = classnames({
-            [`${prefixCls }-month-grid`]: true,
+            [`${prefixCls}-month-grid`]: true,
         });
         const panelTypeLeft = strings.PANEL_TYPE_LEFT;
         const panelTypeRight = strings.PANEL_TYPE_RIGHT;

+ 11 - 9
packages/semi-ui/datePicker/yearAndMonth.tsx

@@ -120,7 +120,7 @@ class YearAndMonth extends BaseComponent<YearAndMonthProps, YearAndMonthState> {
 
     renderColYear() {
         const { years, currentYear, currentMonth } = this.state;
-        const { disabledDate, localeCode, yearCycled } = this.props;
+        const { disabledDate, localeCode, yearCycled, yearAndMonthOpts } = this.props;
         const currentDate = setMonth(Date.now(), currentMonth - 1);
         const list: any[] = years.map(({ value, year }) => ({
             year,
@@ -130,7 +130,7 @@ class YearAndMonth extends BaseComponent<YearAndMonthProps, YearAndMonthState> {
         let transform = (val: string) => val;
         if (localeCode === 'zh-CN' || localeCode === 'zh-TW') {
             // Only Chinese needs to add [year] after the selected year
-            transform = val => `${val }年`;
+            transform = val => `${val}年`;
         }
         return (
             <ScrollItem
@@ -141,6 +141,8 @@ class YearAndMonth extends BaseComponent<YearAndMonthProps, YearAndMonthState> {
                 selectedIndex={years.findIndex(item => item.value === currentYear)}
                 type="year"
                 onSelect={this.selectYear}
+                mode="normal"
+                {...yearAndMonthOpts}
             />
         );
     }
@@ -167,12 +169,13 @@ class YearAndMonth extends BaseComponent<YearAndMonthProps, YearAndMonthState> {
 
     renderColMonth() {
         const { months, currentMonth, currentYear } = this.state;
-        const { locale, localeCode, monthCycled, disabledDate } = this.props;
+        const { locale, localeCode, monthCycled, disabledDate, yearAndMonthOpts } = this.props;
+        console.log('yearAndMonthOpts', yearAndMonthOpts);
         let transform = (val: string) => val;
         const currentDate = setYear(Date.now(), currentYear);
         if (localeCode === 'zh-CN' || localeCode === 'zh-TW') {
             // Only Chinese needs to add [month] after the selected month
-            transform = val => `${val }月`;
+            transform = val => `${val}月`;
         }
         // i18n
         const list: MonthScrollItem[] = months.map(({ value, month }) => ({
@@ -190,6 +193,8 @@ class YearAndMonth extends BaseComponent<YearAndMonthProps, YearAndMonthState> {
                 selectedIndex={selectedIndex}
                 type="month"
                 onSelect={this.selectMonth}
+                mode='normal'
+                {...yearAndMonthOpts}
             />
         );
     }
@@ -223,7 +228,7 @@ class YearAndMonth extends BaseComponent<YearAndMonthProps, YearAndMonthState> {
                 )}
                 {
                     presetPosition ? (
-                        <div style={{ display: 'flex' }}> 
+                        <div style={{ display: 'flex' }}>
                             {presetPosition === "left" && renderQuickControls}
                             <div>
                                 {renderDateInput}
@@ -234,7 +239,7 @@ class YearAndMonth extends BaseComponent<YearAndMonthProps, YearAndMonthState> {
                             </div>
                             {presetPosition === "right" && renderQuickControls}
                         </div>
-                    ) : 
+                    ) :
                         <>
                             {renderDateInput}
                             <ScrollList>
@@ -242,10 +247,7 @@ class YearAndMonth extends BaseComponent<YearAndMonthProps, YearAndMonthState> {
                                 {this.renderColMonth()}
                             </ScrollList>
                         </>
-
-                    
                 }
-             
             </React.Fragment>
         );
     }

+ 1 - 1
packages/semi-ui/scrollList/_story/WheelList/index.jsx

@@ -81,7 +81,7 @@ class ScrollListDemo extends React.Component {
             // mode: 'normal',
             mode: 'wheel',
             cycled: false,
-            motion: false,
+            motion: true,
         };
         return (
             <div>

+ 5 - 5
packages/semi-ui/scrollList/scrollItem.tsx

@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
 import classnames from 'classnames';
 import { noop, debounce, throttle, find, map, findIndex, times } from 'lodash';
 
-import { cssClasses, numbers } from '@douyinfe/semi-foundation/scrollList/constants';
+import { cssClasses, numbers, strings } from '@douyinfe/semi-foundation/scrollList/constants';
 import ItemFoundation, { Item, ScrollItemAdapter } from '@douyinfe/semi-foundation/scrollList/itemFoundation';
 import animatedScrollTo from '@douyinfe/semi-foundation/scrollList/scrollTo';
 import isElement from '@douyinfe/semi-foundation/utils/isElement';
@@ -37,7 +37,7 @@ export interface ScrollItemState {
 }
 export default class ScrollItem<T extends Item> extends BaseComponent<ScrollItemProps<T>, ScrollItemState> {
     static propTypes = {
-        mode: PropTypes.string,
+        mode: PropTypes.oneOf(strings.MODE),
         cycled: PropTypes.bool,
         list: PropTypes.array,
         selectedIndex: PropTypes.number,
@@ -98,7 +98,7 @@ export default class ScrollItem<T extends Item> extends BaseComponent<ScrollItem
         this.debouncedSelect = debounce((e, nearestNode) => {
             this._cacheSelectedNode(nearestNode);
             this.foundation.selectNode(nearestNode, this.list);
-        }, msPerFrame * 5);
+        }, msPerFrame * 2);
     }
 
     get adapter(): ScrollItemAdapter<ScrollItemProps<T>, ScrollItemState, T> {
@@ -113,7 +113,7 @@ export default class ScrollItem<T extends Item> extends BaseComponent<ScrollItem
             scrollToCenter: this.scrollToCenter,
         };
     }
-    componentWillUnmount(){
+    componentWillUnmount() {
         if (this.props.cycled) {
             this.throttledAdjustList.cancel();
             this.debouncedSelect.cancel();
@@ -329,7 +329,7 @@ export default class ScrollItem<T extends Item> extends BaseComponent<ScrollItem
         const { wrapper } = this;
         const wrapperHeight = wrapper.offsetHeight;
         const itemHeight = this.getItmHeight(node);
-        const targetTop = (node.offsetTop || this.list.children.length * itemHeight / 2 ) - (wrapperHeight - itemHeight) / 2;
+        const targetTop = (node.offsetTop || this.list.children.length * itemHeight / 2) - (wrapperHeight - itemHeight) / 2;
 
         this.scrollToPos(targetTop, duration);
     };

+ 5 - 9
packages/semi-ui/timePicker/Combobox.tsx

@@ -195,9 +195,8 @@ class Combobox extends BaseComponent<ComboboxProps, ComboboxState> {
         return (
             <ScrollItem<FormatOptionReturn>
                 ref={current => this.cacheRefCurrent('hour', current)}
-                mode={'wheel'}
+                mode={'normal'}
                 transform={transformHour}
-                cycled={true}
                 className={className}
                 list={hourOptionsAdj.map(option => formatOption(option, disabledOptions))}
                 selectedIndex={hourOptionsAdj.indexOf(hourAdj)}
@@ -226,9 +225,8 @@ class Combobox extends BaseComponent<ComboboxProps, ComboboxState> {
         return (
             <ScrollItem<FormatOptionReturn>
                 ref={current => this.cacheRefCurrent('minute', current)}
-                mode={'wheel'}
+                mode={'normal'}
                 transform={transformMinute}
-                cycled={true}
                 list={minuteOptions.map(option => formatOption(option, disabledOptions))}
                 selectedIndex={minuteOptions.indexOf(minute)}
                 type="minute"
@@ -258,9 +256,8 @@ class Combobox extends BaseComponent<ComboboxProps, ComboboxState> {
         return (
             <ScrollItem<FormatOptionReturn>
                 ref={current => this.cacheRefCurrent('second', current)}
-                mode={'wheel'}
+                mode={'normal'}
                 transform={transformSecond}
-                cycled={true}
                 list={secondOptions.map(option => formatOption(option, disabledOptions))}
                 selectedIndex={secondOptions.indexOf(second)}
                 className={className}
@@ -295,9 +292,8 @@ class Combobox extends BaseComponent<ComboboxProps, ComboboxState> {
         return (
             <ScrollItem<AMPMOptionItem>
                 ref={current => this.cacheRefCurrent('ampm', current)}
-                mode={'wheel'}
+                mode={'normal'}
                 className={className}
-                cycled={false}
                 list={AMPMOptions}
                 selectedIndex={selected}
                 type="ampm"
@@ -319,7 +315,7 @@ class Combobox extends BaseComponent<ComboboxProps, ComboboxState> {
                 {(locale: Locale['TimePicker'], localeCode: Locale['code']) => (
                     <ScrollList
                         header={panelHeader}
-                        footer={panelFooter} 
+                        footer={panelFooter}
                         x-semi-header-alias="panelHeader"
                         x-semi-footer-alias="panelFooter"
                     >