Explorar o código

feat: datepicker add position option to preset panel (#1012)

* feat: add position option to preset panel

* feat: adjust some scss variable values to fit the max panel height
YannLynn %!s(int64=3) %!d(string=hai) anos
pai
achega
8db284e5dc

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

@@ -361,7 +361,7 @@ class App extends React.Component {
     }
 
     render() {
-        return <DatePicker type="dateTime" presets={this.presets} />;
+        return <DatePicker type="dateTime" presets={this.presets} presetPosition="left"/>;
     }
 }
 ```
@@ -864,6 +864,7 @@ function Demo() {
 | prefix             | Prefix content                                                                                                                                                                            | string\|ReactNode                                                                                                                                                                                         |                                                                                       |                           |
 | presets            | Date Time Shortcut                                                                                                                                                                        | Array < {start: string\|Date\|number, end: string\|Date\|number, text: string}\| function(): {start: string\|Date\|number, end: string\|Date\|number, text: string} >                                     | []                                                                                    |                           |
 | preventScroll | Indicates whether the browser should scroll the document to display the newly focused element, acting on the focus method inside the component, excluding the component passed in by the user | boolean |  |  |
+| presetPosition     | Date time shortcut panel position, optional 'left', 'right', 'top', 'bottom' | 'bottom' | **2.18.0** |
 | rangeSeparator     | Custom range type picker separator of input trigger | string | '~' | **1.31.0** 
 | renderDate         | Custom date display content                                                                                                                                                               | (dayNumber, fullDate) => ReactNode                                                                                                                                                                        | -                                                                                     | **1.4.0**            |
 | renderFullDate     | Custom display date box                                                                                                                                                                   | (dayNumber, fullDate, dayStatus) => ReactNode                                                                                                                                                             | -                                                                                     | **1.4.0**            |

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

@@ -342,7 +342,7 @@ import { DatePicker } from '@douyinfe/semi-ui';
             end: new Date(new Date().valueOf() + 1000 * 3600 * 24),
         },
     ];
-    return <DatePicker type="dateTime" presets={presets} />;
+    return <DatePicker type="dateTime" presets={presets} presetPosition="left"/>;
 };
 ```
 
@@ -829,6 +829,7 @@ function Demo() {
 | prefix | 前缀内容 | string\|ReactNode |  |  |
 | presets | 日期时间快捷方式 | Array<{start:BaseValueType, end:BaseValueType, text:string}\| function():{start:BaseValueType, end:BaseValueType, text:string}> | [] |  |
 | preventScroll | 指示浏览器是否应滚动文档以显示新聚焦的元素,作用于组件内的 focus 方法 | boolean |  |  |
+| presetPosition | 日期时间快捷方式面板位置, 可选值'left', 'right', 'top', 'bottom' | string |  'bottom' | **2.18.0** |
 | rangeSeparator | 自定义范围类型输入框的日期分隔符 | string | '~' | **1.31.0** |
 | renderDate | 自定义日期显示内容 | (dayNumber, fullDate) => ReactNode | - | **1.4.0** |
 | renderFullDate | 自定义显示日期格子内容 | (dayNumber, fullDate, dayStatus) => ReactNode | - | **1.4.0** |

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

@@ -47,6 +47,7 @@ const strings = {
     DEFAULT_SEPARATOR_RANGE: ' ~ ',
     SIZE_SET: ['small', 'default', 'large'],
     TYPE_SET: ['date', 'dateRange', 'year', 'month', 'dateTime', 'dateTimeRange'],
+    PRESET_POSITION_SET: ['left', 'right', 'top', 'bottom'],
     DENSITY_SET: ['default', 'compact'],
     PANEL_TYPE_LEFT: 'left',
     PANEL_TYPE_RIGHT: 'right',

+ 335 - 17
packages/semi-foundation/datePicker/datePicker.scss

@@ -74,6 +74,14 @@ $module: #{$prefix}-datepicker;
             }
         }
 
+        // when preset postion is left or right, and insetinput is false, let the month panel get the fixed height
+        &[x-preset-position=left][x-insetinput=false],
+        &[x-preset-position=right][x-insetinput=false] { 
+            .#{$module}-month {
+                height: $height-datepicker_month_max;
+            }
+        }
+
         // 年月选择返回主面板
         .#{$module}-yearmonth-header {
             background: $color-datepicker_header-bg-default;
@@ -138,7 +146,8 @@ $module: #{$prefix}-datepicker;
     // 年月选择器
 
     &-panel-yam {
-        max-width: $width-datepicker_monthPanel_max;
+        // add left or right preset panel to panel yam, max-width will be bigger
+        max-width: $width-datepicker_monthPanel_max + $width-datepicker_presetPanel_left_and_right;
 
         .#{$prefix}-scrolllist {
             box-shadow: none;
@@ -560,30 +569,188 @@ $module: #{$prefix}-datepicker;
     }
 
     // 预设
-
     &-quick-control {
         box-sizing: border-box;
-        border-top: $width-datepicker-border solid $color-datepicker_border-bg-default;
-        display: flex;
-        align-items: center;
         background-color: $color-datepicker_quick-bg-default;
-        padding: $spacing-datepicker_quick_control_padding;
-        border-radius: 0 0 $width-datepicker_quick_control-borderRadius $width-datepicker_quick_control-borderRadius;
-        flex-wrap: wrap;
 
-        &-item {
-            margin-right: $spacing-datepicker_quick_control_item-marginRight;
+        &-header {
+            padding: $spacing-datepicker_quick_control_header-paddingTop $spacing-datepicker_quick_control_content-paddingX 0;
+            font-weight: $font-datepicker_preset_header-fontWeight;
+        }
+
+        &-left {
+            border-right: $width-datepicker-border solid $color-datepicker_border-bg-default;
+        }
+
+        &-right {
+            border-left: $width-datepicker-border solid $color-datepicker_border-bg-default;
+        }
+
+        &-top {
+            border-bottom: $width-datepicker-border solid $color-datepicker_border-bg-default;
+        }
+
+        &-bottom {
+            border-top: $width-datepicker-border solid $color-datepicker_border-bg-default;
+        }
+
+        &-left-content-wrapper,
+        &-right-content-wrapper {
+            max-width: $width-datepicker_presetPanel_left_and_right;
+            margin-top: $spacing-datepicker_quick_control_content-marginTop;
+            overflow-y: auto;
+        }
+
+        &-top-content-wrapper,
+        &-bottom-content-wrapper {
+            overflow-y: auto;
+            max-height: $height-datepicker_presetPanel_top_and_bottom_max;
+        }
+
+        &-left-content,
+        &-right-content {
+            box-sizing: border-box;
+            display: grid;
+            align-content: flex-start;
+            grid-gap: $spacing-datepicker_quick_control_item-margin;
+            grid-template-columns: repeat(2, minmax($width-datepicker_presetPanel_left_and_right_two_col_button - $width-datepicker_presetPanel_scroll_bar * 0.5 , $width-datepicker_presetPanel_left_and_right_two_col_button));
+            padding: 0 $spacing-datepicker_quick_control_content-paddingX $spacing-datepicker_quick_control_content-paddingX;
+
+            &-item {
+                max-width: $width-datepicker_presetPanel_left_and_right_two_col_button;
+                &-ellipsis {
+                    // ellipsis text content, $spacing-datepicker_quick_control_item-margin(default 8px)space left and right
+                    width: $width-datepicker_presetPanel_left_and_right_two_col_button - ($spacing-datepicker_quick_control_item-margin * 2);
+                    color: $color-datepicker_quick_button-text-default;
+                }
+            }
+        }
+
+        &-top-content,
+        &-bottom-content {
+            display: grid;
+            grid-gap: $spacing-datepicker_quick_control_item-margin;
+            // 5px = $width-datepicker_presetPanel_scroll_bar / 3
+            grid-template-columns: repeat(3, minmax($width-datepicker_presetPanel_top_and_bottom_three_col_button - 5px, $width-datepicker_presetPanel_top_and_bottom_three_col_button));
+            align-content: flex-start;
+            padding: $spacing-datepicker_quick_control_item-margin $spacing-datepicker_quick_control_top_and_bottom_content-paddingX;
+
+            &-item {
+                max-width: $width-datepicker_presetPanel_top_and_bottom_three_col_button;
+                &-ellipsis {
+                    width: $width-datepicker_presetPanel_top_and_bottom_three_col_button - ($spacing-datepicker_quick_control_item-margin * 2);
+                    color: $color-datepicker_quick_button-text-default;
+                }
+            }
+        }
+
+        &-top-range-content,
+        &-bottom-range-content {
+            box-sizing: border-box;
+            display: grid;
+            align-content: flex-start;
+            // 3px = $width-datepicker_presetPanel_scroll_bar / 5
+            grid-template-columns: repeat(5, minmax($width-datepicker_presetPanel_top_and_bottom_five_col_button - 3px, $width-datepicker_presetPanel_top_and_bottom_five_col_button));
+            grid-gap: $spacing-datepicker_quick_control_item-margin;
+            padding: $spacing-datepicker_quick_control_item-margin $spacing-datepicker_quick_control_top_and_bottom_content-paddingX;
+
+            &-item {
+                max-width: $width-datepicker_presetPanel_top_and_bottom_five_col_button;
+                &-ellipsis {
+                    width: $width-datepicker_presetPanel_top_and_bottom_five_col_button - ($spacing-datepicker_quick_control_item-margin * 2);
+                    color: $color-datepicker_quick_button-text-default;
+                }
+            }
+        }
+
+        &-top-month-content,
+        &-bottom-month-content {
+            box-sizing: border-box;
+            display: grid;
+            grid-template-columns: repeat(2, minmax($width-datepicker_presetPanel_top_and_bottom_two_col_button - $width-datepicker_presetPanel_scroll_bar * 0.5, $width-datepicker_presetPanel_top_and_bottom_two_col_button));
+            grid-gap: $spacing-datepicker_quick_control_item-margin;
+            align-content: flex-start;
+            padding: $spacing-datepicker_quick_control_item-margin $spacing-datepicker_quick_control_top_and_bottom_content-paddingX;
+
+            &-item {
+                max-width: $width-datepicker_presetPanel_top_and_bottom_two_col_button;
+                &-ellipsis {
+                    max-width: $width-datepicker_presetPanel_top_and_bottom_two_col_button - ($spacing-datepicker_quick_control_item-margin * 2);
+                    color: $color-datepicker_quick_button-text-default;
+                }
+            }
+        }
+
+
+        &-month {
+            max-width: $width-datepicker_monthPanel_max;
+            &[x-insetinput=true] {
+                .#{$module}-quick-control-right-content-wrapper,
+                .#{$module}-quick-control-left-content-wrapper {
+                    max-height: $height-datepicker_panel_yam_scrolllist - $height-datepicker_presetPanel_left_and_right_except_content + $height-datepicker_inset_input;
+                }
+            }
+
+            .#{$module}-quick-control-right-content-wrapper,
+            .#{$module}-quick-control-left-content-wrapper {
+                max-height: $height-datepicker_panel_yam_scrolllist - $height-datepicker_presetPanel_left_and_right_except_content;
+            }
+        }
+
+        &-date {
+            max-width: $width-datepicker_monthPanel_max;
+            &[x-insetinput=true] {
+                .#{$module}-quick-control-right-content-wrapper,
+                .#{$module}-quick-control-left-content-wrapper {
+                    max-height: $height-datepicker_preset_panel_inset_input - $height-datepicker_presetPanel_left_and_right_except_content;
+                }
+            }
+            .#{$module}-quick-control-right-content-wrapper,
+            .#{$module}-quick-control-left-content-wrapper {
+                max-height: $height-datepicker_date_panel - $height-datepicker_presetPanel_left_and_right_except_content;
+            }
         }
 
-        &-month,
-        &-date,
         &-dateTime {
             max-width: $width-datepicker_monthPanel_max;
+            &[x-insetinput=true] {
+                .#{$module}-quick-control-right-content-wrapper,
+                .#{$module}-quick-control-left-content-wrapper {
+                    max-height: $height-datepicker_preset_panel_inset_input - $height-datepicker_presetPanel_left_and_right_except_content;
+                }
+            }
+            .#{$module}-quick-control-right-content-wrapper,
+            .#{$module}-quick-control-left-content-wrapper {
+                max-height: $height-datepicker_date_time_panel - $height-datepicker_presetPanel_left_and_right_except_content;
+            }
+        }
+
+        &-dateRange {
+            max-width: $width-datepicker_monthPanel_max * 2;
+            &[x-insetinput=true] {
+                .#{$module}-quick-control-right-content-wrapper,
+                .#{$module}-quick-control-left-content-wrapper {
+                    max-height: $height-datepicker_preset_panel_inset_input - $height-datepicker_presetPanel_left_and_right_except_content;
+                }
+            }
+            .#{$module}-quick-control-right-content-wrapper,
+            .#{$module}-quick-control-left-content-wrapper {
+                max-height: $height-datepicker_date_panel - $height-datepicker_presetPanel_left_and_right_except_content;
+            }
         }
 
-        &-dateRange,
         &-dateTimeRange {
             max-width: $width-datepicker_monthPanel_max * 2;
+            &[x-insetinput=true] {
+                .#{$module}-quick-control-right-content-wrapper,
+                .#{$module}-quick-control-left-content-wrapper {
+                    max-height: $height-datepicker_preset_panel_inset_input - $height-datepicker_presetPanel_left_and_right_except_content;
+                }
+            }
+            .#{$module}-quick-control-right-content-wrapper,
+            .#{$module}-quick-control-left-content-wrapper {
+                max-height: $height-datepicker_date_time_panel - $height-datepicker_presetPanel_left_and_right_except_content;
+            }
         }
     }
 
@@ -879,6 +1046,15 @@ $module: #{$prefix}-datepicker;
             min-height: $height-datepicker_yam_panel_compact;
         }
 
+        // when preset postion is left or right, and insetinput is false, let the month panel get the fixed height
+        &[x-preset-position=left][x-insetinput=false],
+        &[x-preset-position=right][x-insetinput=false] { 
+
+            .#{$module}-month {
+                height: $height-datepicker_month_max_compact;
+            }
+        }
+
         .#{$module}-yearmonth-header {
             box-sizing: border-box;
             height: $height-datepicker_yam_panel_header_compact;
@@ -1112,17 +1288,159 @@ $module: #{$prefix}-datepicker;
     }
 
     .#{$module}-quick-control {
-        padding: $spacing-datepicker_compact-padding;
 
-        &-month,
-        &-date,
+        &-header {
+            padding: $spacing-datepicker_quick_control_header_compact-paddingTop $spacing-datepicker_quick_control_left_and_right_content_compact-paddingX 0;
+            font-weight: $font-datepicker_preset_header-fontWeight;
+        }
+
+        &-left-content-wrapper,
+        &-right-content-wrapper {
+            margin-top: $spacing-datepicker_quick_control_content_compact-marginTop;
+        }
+
+        &-left-content,
+        &-right-content {
+            box-sizing: border-box;
+            display: grid;
+            align-content: flex-start;
+            grid-gap: $spacing-datepicker_quick_control_item-margin;
+            grid-template-columns: repeat(2, minmax($width-datepicker_presetPanel_left_and_right_two_col_button - $width-datepicker_presetPanel_scroll_bar * 0.5, $width-datepicker_presetPanel_left_and_right_two_col_button));
+            padding: 0 $spacing-datepicker_quick_control_left_and_right_content_compact-paddingX $spacing-datepicker_quick_control_left_and_right_content_compact-paddingX;
+
+            &-item {
+                max-width: $width-datepicker_presetPanel_left_and_right_two_col_button;
+                &-ellipsis {
+                    width: $width-datepicker_presetPanel_left_and_right_two_col_button - ($spacing-datepicker_quick_control_item-margin * 2);
+                    font-size: $fontSize-datepicker_insetInput_compact-fontSize;
+                    color: $color-datepicker_quick_button-text-default;
+                }
+            }
+        }
+
+        &-top-content,
+        &-bottom-content {
+            box-sizing: border-box;
+            display: grid;
+            grid-gap: $spacing-datepicker_quick_control_item-margin;
+            grid-template-columns: repeat(3, minmax($width-datepicker_presetPanel_top_and_bottom_three_col_button_compact - 5px, $width-datepicker_presetPanel_top_and_bottom_three_col_button_compact));
+            align-content: flex-start;
+            padding: $spacing-datepicker_quick_control_item-margin $spacing-datepicker_quick_control_top_and_bottom_content_compact-paddingX;
+
+            &-item {
+                max-width: $width-datepicker_presetPanel_top_and_bottom_three_col_button_compact;
+                &-ellipsis {
+                    width: $width-datepicker_presetPanel_top_and_bottom_three_col_button_compact - ($spacing-datepicker_quick_control_item-margin * 2);
+                    font-size: $fontSize-datepicker_insetInput_compact-fontSize;
+                    color: $color-datepicker_quick_button-text-default;
+                }
+            }
+        }
+
+        &-top-range-content,
+        &-bottom-range-content {
+            display: grid;
+            grid-template-columns: repeat(5, minmax($width-datepicker_presetPanel_top_and_bottom_five_col_button_compact - 3px, $width-datepicker_presetPanel_top_and_bottom_five_col_button_compact));
+            grid-gap: $spacing-datepicker_quick_control_item-margin;
+            align-content: flex-start;
+            padding: $spacing-datepicker_quick_control_item-margin $spacing-datepicker_quick_control_top_and_bottom_content_compact-paddingX;
+
+            &-item {
+                max-width: $width-datepicker_presetPanel_top_and_bottom_five_col_button_compact;
+                &-ellipsis {
+                    width: $width-datepicker_presetPanel_top_and_bottom_five_col_button_compact - ($spacing-datepicker_quick_control_item-margin * 2);
+                    font-size: $fontSize-datepicker_insetInput_compact-fontSize;
+                    color: $color-datepicker_quick_button-text-default;
+                }
+            }
+        }
+
+        &-top-month-content,
+        &-bottom-month-content {
+            display: grid;
+            grid-template-columns: repeat(2, minmax($width-datepicker_presetPanel_top_and_bottom_two_col_button_compact -  $width-datepicker_presetPanel_scroll_bar * 0.5, $width-datepicker_presetPanel_top_and_bottom_two_col_button_compact));
+            grid-gap: $spacing-datepicker_quick_control_item-margin;
+            align-content: flex-start;
+            padding: $spacing-datepicker_quick_control_item-margin $spacing-datepicker_quick_control_top_and_bottom_content_compact-paddingX;
+
+            &-item {
+                max-width: $width-datepicker_presetPanel_top_and_bottom_two_col_button_compact;
+                &-ellipsis {
+                    max-width: $width-datepicker_presetPanel_top_and_bottom_two_col_button_compact - ($spacing-datepicker_quick_control_item-margin * 2);
+                    font-size: $fontSize-datepicker_insetInput_compact-fontSize;
+                    color: $color-datepicker_quick_button-text-default;
+                }
+            }
+        }
+
+        &-month {
+            max-width: $width-datepicker_panel_compact;
+            &[x-insetinput=true] {
+                .#{$module}-quick-control-right-content-wrapper,
+                .#{$module}-quick-control-left-content-wrapper {
+                    max-height: $height-datepicker_panel_yam_scrolllist - $height-datepicker_presetPanel_left_and_right_except_content_compact + $height-datepicker_inset_input_compact;
+                }
+            }
+            .#{$module}-quick-control-right-content-wrapper,
+            .#{$module}-quick-control-left-content-wrapper {
+                max-height: $height-datepicker_panel_yam_scrolllist - $height-datepicker_presetPanel_left_and_right_except_content_compact;
+            }
+        }
+
+        &-date {
+            max-width: $width-datepicker_panel_compact;
+            &[x-insetinput=true] {
+                .#{$module}-quick-control-right-content-wrapper,
+                .#{$module}-quick-control-left-content-wrapper {
+                    max-height: $height-datepicker_preset_panel_inset_input_compact - $height-datepicker_presetPanel_left_and_right_except_content_compact;
+                }
+            }
+            .#{$module}-quick-control-right-content-wrapper,
+            .#{$module}-quick-control-left-content-wrapper {
+                max-height: $height-datepicker_date_panel_compact - $height-datepicker_presetPanel_left_and_right_except_content_compact;
+            }
+        }
+
         &-dateTime {
             max-width: $width-datepicker_panel_compact;
+            &[x-insetinput=true] {
+                .#{$module}-quick-control-right-content-wrapper,
+                .#{$module}-quick-control-left-content-wrapper {
+                    max-height: $height-datepicker_preset_panel_inset_input_compact - $height-datepicker_presetPanel_left_and_right_except_content_compact;
+                }
+            }
+            .#{$module}-quick-control-right-content-wrapper,
+            .#{$module}-quick-control-left-content-wrapper {
+                max-height: $height-datepicker_date_time_panel_compact - $height-datepicker_presetPanel_left_and_right_except_content_compact;
+            }
+        }
+
+        &-dateRange {
+            max-width: $width-datepicker_panel_compact * 2;
+            &[x-insetinput=true] {
+                .#{$module}-quick-control-right-content-wrapper,
+                .#{$module}-quick-control-left-content-wrapper {
+                    max-height: $height-datepicker_preset_panel_inset_input_compact - $height-datepicker_presetPanel_left_and_right_except_content_compact;
+                }
+            }
+            .#{$module}-quick-control-right-content-wrapper,
+            .#{$module}-quick-control-left-content-wrapper {
+                max-height: $height-datepicker_date_panel_compact - $height-datepicker_presetPanel_left_and_right_except_content_compact;
+            }
         }
 
-        &-dateRange,
         &-dateTimeRange {
             max-width: $width-datepicker_panel_compact * 2;
+            &[x-insetinput=true] {
+                .#{$module}-quick-control-right-content-wrapper,
+                .#{$module}-quick-control-left-content-wrapper {
+                    max-height: $height-datepicker_preset_panel_inset_input_compact - $height-datepicker_presetPanel_left_and_right_except_content_compact;
+                }
+            }
+            .#{$module}-quick-control-right-content-wrapper,
+            .#{$module}-quick-control-left-content-wrapper {
+                max-height: $height-datepicker_date_time_panel_compact - $height-datepicker_presetPanel_left_and_right_except_content_compact;
+            }
         }
     }
 

+ 2 - 0
packages/semi-foundation/datePicker/foundation.ts

@@ -22,6 +22,7 @@ import getInsetInputValueFromInsetInputStr from './_utils/getInsetInputValueFrom
 export type ValidateStatus = ArrayElement<typeof strings.STATUS>;
 export type InputSize = ArrayElement<typeof strings.SIZE_SET>;
 export type Position = ArrayElement<typeof strings.POSITION_SET>;
+export type PresetPosition = ArrayElement<typeof strings.PRESET_POSITION_SET>;
 
 export type BaseValueType = string | number | Date;
 export type DayStatusType = {
@@ -141,6 +142,7 @@ export interface DatePickerFoundationProps extends ElementProps, RenderProps, Ev
     position?: Position;
     prefixCls?: string;
     presets?: PresetsType;
+    presetPosition?: PresetPosition;
     showClear?: boolean;
     size?: InputSize;
     spacing?: number;

+ 4 - 1
packages/semi-foundation/datePicker/monthsGridFoundation.ts

@@ -20,7 +20,7 @@ import { includes, isSet, isEqual, isFunction } from 'lodash';
 import { zonedTimeToUtc } from '../utils/date-fns-extra';
 import { getDefaultFormatTokenByType } from './_utils/getDefaultFormatToken';
 import isNullOrUndefined from '../utils/isNullOrUndefined';
-import { BaseValueType, ValueType } from './foundation';
+import { BaseValueType, PresetPosition, ValueType } from './foundation';
 import { MonthDayInfo } from './monthFoundation';
 import { ArrayElement } from '../utils/type';
 
@@ -91,6 +91,9 @@ export interface MonthsGridFoundationProps extends MonthsGridElementProps {
     focusRecordsRef?: any;
     triggerRender?: (props: Record<string, any>) => any;
     insetInput: boolean;
+    presetPosition?: PresetPosition;
+    renderQuickControls?: React.ReactNode;
+    renderDateInput?: React.ReactNode;
 }
 
 export interface MonthInfo {

+ 6 - 6
packages/semi-foundation/datePicker/rtl.scss

@@ -66,13 +66,13 @@ $module: #{$prefix}-datepicker;
             }
         }
 
-        &-quick-control {
+        // &-quick-control {
 
-            &-item {
-                margin-left: 0;
-                margin-right: $spacing-datepicker_quick_control_item-marginRight;
-            }
-        }
+        //     &-item {
+        //         margin-left: 0;
+        //         margin-right: $spacing-datepicker_quick_control_item-marginRight;
+        //     }
+        // }
 
         &-navigation,
         &-yam {

+ 58 - 6
packages/semi-foundation/datePicker/variables.scss

@@ -15,6 +15,9 @@ $height-datepicker_yamShowing_min: 378px; // 日期时间选择器菜单最小
 $width-datepicker_yamShowing_min: 284px; // 选择器菜单最小宽度
 $height-datepicker_dateType_yamShowing_min: 325px; // 日期选择器菜单最小高度
 $width-datepicker_panel_yam_scrolllist_li_min: 64px; // 年月、时间滚动菜单项最小高度
+$width-datepicker_presetPanel_left_and_right: 200px; // 左右方位快捷选择面板宽度
+$height-datepicker_presetPanel_top_and_bottom_max: 100px; // 上下方位快捷选择面板最大高度
+$width-datepicker_presetPanel_scroll_bar: 15px; // 快捷选择面板scrollbar宽度
 
 $width-datepicker-border: $border-thickness-control; // 年月选择 header 底部分割线宽度
 $width-datepicker_yam_header-borderRadius: var(--semi-border-radius-medium); // 年月选择 header 按钮圆角
@@ -29,8 +32,8 @@ $width-datepicker_insetInput_date_type_wrapper: 284px; // 日期类型内嵌输
 $width-datepicker_insetInput_date_range_type_wrapper: $width-datepicker_insetInput_date_type_wrapper * 2; // 范围选择内嵌输入框宽度
 $width-datepicker_insetInput_month_type_wrapper: 204px; // 月份类型内嵌输入框宽度
 $height-datepicker_insetInput_separator: 32px;
-$height-datepicker_month_grid_yearType_insetInput: 312px;
-$height-datepicker_month_grid_timeType_insetInput: 314px;
+$height-datepicker_month_grid_yearType_insetInput: 317px;
+$height-datepicker_month_grid_timeType_insetInput: 317px;
 
 // Spacing
 $spacing-datepicker_day-marginX: ($width-datepicker_day - $width-datepicker_day_main) / 2; // 日期格子水平外边距
@@ -49,8 +52,11 @@ $spacing-datepicker_month-padding: $spacing-base;
 $spacing-datepicker_switch_datetime-paddingTop: $spacing-base; // 日期时间切换顶部内边距
 $spacing-datepicker_switch_datetime-paddingBottom: $spacing-base; // 日期时间切换底部内边距
 $spacing-datepicker_switch_text-paddingLeft: $spacing-tight; // 日期时间切换左侧内边距
-$spacing-datepicker_quick_control_item-marginRight: $spacing-tight; // 快捷操作按钮右侧外边距
-$spacing-datepicker_quick_control_padding: $spacing-base; // 快捷操作内边距
+$spacing-datepicker_quick_control_header-paddingTop: 18px;  // 快捷面板标题,上内边距
+$spacing-datepicker_quick_control_top_and_bottom_content-paddingX: $spacing-base-loose; // 上下方位快捷操作面板, 左右内边距,默认20px
+$spacing-datepicker_quick_control_content-paddingX: 12px; // 快捷面板内容,左右内边距
+$spacing-datepicker_quick_control_content-marginTop: 14px; // 快捷面板内容,上边距
+$spacing-datepicker_quick_control_item-margin: $spacing-tight; // 快捷操作面板按钮间距
 $spacing-datepicker_range_input-paddingX: 8px;
 $spacing-datepicker_range_input-paddingY: 3px;
 $spacing-datepicker_range_input_inputWrapper_input-paddingY: 2px;
@@ -77,6 +83,7 @@ $color-datepicker_list-bg-default: var(--semi-color-bg-3); // 日期选择器滚
 $color-datepicker_border-bg-default: var(--semi-color-border); // 日期选择器描边颜色
 $color-datepicker_footer-bg-default: var(--semi-color-fill-0); // 日期选择器确认选择 footer 背景颜色
 $color-datepicker_quick-bg-default: transparent; // 日期选择器快捷操作背景颜色
+$color-datepicker_quick_button-text-default: var(--semi-color-primary); // 日期选择器快捷操作按钮文字颜色
 
 $color-datepicker_day-text-default: var(--semi-color-text-2); // 日期时间切换文字颜色 - 默认
 $color-datepicker_day-text-hover: var(--semi-color-fill-1); // 日期时间切换文字颜色 - 悬浮
@@ -138,6 +145,7 @@ $color-datepicker_insetInput_separator: var(--semi-color-text-3);
 
 // Font
 $font-datepicker_range_input_prefix_suffix_clearbtn-fontWeight: 600;
+$font-datepicker_preset_header-fontWeight: 600;
 $font-datepicker_range_input_prefix_suffix_clearbtn-fontSize: 14px;
 $font-datepicker_range_input_prefix_suffix_clearbtn-lineHeight: 20px;
 $font-datepicker_range_input_large-fontSize: 16px;
@@ -153,8 +161,8 @@ $width-datepicker_month_compact: $width-datepicker_day_compact * 7 + $spacing-da
 $width-datepicker_nav_compact: 24px;
 $width-datepicker_panel_compact: 216px;
 $height-datepicker_switch_compact: 32px;
-$height-datepicker_tpk_compact: 236px;
-$height-datepicker_yam_panel_compact: 284px;
+$height-datepicker_tpk_compact: 256px;
+$height-datepicker_yam_panel_compact: 256px;
 $height-datepicker_yam_li_compact: 32px;
 $height-datepicker_yam_panel_header_compact: 48px;
 $radius-datepicker_range_input_inputWrapper: var(--semi-border-radius-small);
@@ -193,9 +201,53 @@ $spacing-datepicker_insetInput_wrapper_compact-paddingY: 8px;
 $spacing-datepicker_insetInput_wrapper_compact-paddingX: 8px;
 $spacing-datepicker_insetInput_wrapper_compact-paddingBottom: 0;
 $spacing-datepicker_insetInput_wrapper_rangeType_compact-paddingTop: 0;
+$spacing-datepicker_quick_control_header_compact-paddingTop: 16px;  // compact, 快捷面板标题,上内边距
+$spacing-datepicker_quick_control_content_compact-marginTop: 12px; // compact, 快捷面板内容,上边距
+$spacing-datepicker_quick_control_left_and_right_content_compact-paddingX: 12px; // compact,左右方位,快捷面板内容,左右内边距
+$spacing-datepicker_quick_control_top_and_bottom_content_compact-paddingX: 10px; // compact,上下方位,快捷面板内容,左右内边距
 
 // Radius
 $radius-datepicker_range_input: var(--semi-border-radius-small);
 
 // Other
 $transition-datepicker_range_input: background-color .16s ease-in-out;
+
+// preset cacl
+$width-datepicker_presetPanel_left_and_right_content: $width-datepicker_presetPanel_left_and_right - $spacing-datepicker_quick_control_content-paddingX * 2; // 左右方位快捷选择面板,内容宽度
+$width-datepicker_presetPanel_top_and_bottom_content_date: $width-datepicker_day * 7 + $spacing-datepicker_month-padding * 2 - $spacing-datepicker_quick_control_top_and_bottom_content-paddingX * 2; // date/dateTime下, 上下方位快捷选择面板内容宽度, 默认(284 - 40)px
+$width-datepicker_presetPanel_top_and_bottom_content_range: ($width-datepicker_day * 7 + $spacing-datepicker_month-padding * 2) * 2 - $spacing-datepicker_quick_control_top_and_bottom_content-paddingX * 2; // dateRange/dateTimeRange下, 上下方位快捷选择内容面板宽度,默认528px
+$width-datepicker_presetPanel_top_and_bottom_content_month: 194px - $spacing-datepicker_quick_control_top_and_bottom_content-paddingX * 2; // month下,上下方位快捷选择内容面板宽度, 默认154px
+
+$height-datepicker_month_max: $width-datepicker_day * 7 + 1px; // 年月面板最大高度, 最多6 + 1行,再加上一个border宽度, 默认253px
+$height-datepicker_month_max_compact: $width-datepicker_day_compact * 7 + $spacing-datepicker_weeks_compact-paddingTop + $spacing-datepicker_weeks_compact-padding + $spacing-datepicker_weekday_compact-paddingBottom; // 年月面板最大高度, 最多6 + 1行,再加上padding,默认220px
+$height-datepicker_date_panel: $height-datepicker_month_max + $spacing-datepicker_month-padding + $width-datepicker_nav + $spacing-datepicker_navigation-paddingY * 2; // date/dateRange,面板渲染最大高度,默认325px
+$height-datepicker_date_time_panel: $height-datepicker_date_panel + $height-datepicker_switch - 1px; // dateTime/dateTImeRange 面板渲染最大高度. 默认378px
+$height-datepicker_presetPanel_left_and_right_except_content: 20px + $spacing-datepicker_quick_control_header-paddingTop + $spacing-datepicker_quick_control_content-marginTop; // 除去content以外的高度,默认52px
+
+// compact
+$width-datepicker_presetPanel_top_and_bottom_content_date_compact: $width-datepicker_day_compact * 7 + $spacing-datepicker_weeks_compact-padding * 2 - $spacing-datepicker_quick_control_top_and_bottom_content_compact-paddingX * 2; // date/dateTime下, 上下方位快捷选择面板内容宽度, 默认(216 - 20)px
+$width-datepicker_presetPanel_top_and_bottom_content_range_compact: ($width-datepicker_day_compact * 7 + $spacing-datepicker_weeks_compact-padding * 2) * 2 - $spacing-datepicker_quick_control_top_and_bottom_content_compact-paddingX * 2; // dateRange/dateTimeRange下, 上下方位快捷选择内容面板宽度,默认412px
+$width-datepicker_presetPanel_top_and_bottom_content_month_compact: 194px - $spacing-datepicker_quick_control_top_and_bottom_content_compact-paddingX  * 2; // month下,上下方位快捷选择内容面板宽度, 默认174px
+
+$height-datepicker_date_panel_compact: $height-datepicker_month_max_compact + $width-datepicker_nav_compact + $spacing-datepicker_nav_compact-padding; // compact,date/dateRange,面板渲染最大高度,默认256px
+$height-datepicker_date_time_panel_compact: $height-datepicker_date_panel_compact + $height-datepicker_switch_compact; // compact,dateTime/dateTImeRange,面板渲染最大高度,默认288px
+$height-datepicker_presetPanel_left_and_right_except_content_compact: 20px + $spacing-datepicker_quick_control_header_compact-paddingTop + $spacing-datepicker_quick_control_content_compact-marginTop; // compact,除去content以外的高度,默认48px
+
+$width-datepicker_presetPanel_left_and_right_two_col_button: ($width-datepicker_presetPanel_left_and_right_content - $spacing-datepicker_quick_control_item-margin) * 0.5; // 左右方位快捷选择面板,固定两列,按钮宽度
+$width-datepicker_presetPanel_top_and_bottom_three_col_button: ($width-datepicker_presetPanel_top_and_bottom_content_date - $spacing-datepicker_quick_control_item-margin * 2) / 3; // 上下方位快捷选择面板,固定三列,按钮宽度
+$width-datepicker_presetPanel_top_and_bottom_five_col_button: ($width-datepicker_presetPanel_top_and_bottom_content_range - $spacing-datepicker_quick_control_item-margin * 4) * 0.2; // 上下方位快捷选择面板,固定五列,按钮宽度
+$width-datepicker_presetPanel_top_and_bottom_two_col_button:($width-datepicker_presetPanel_top_and_bottom_content_month - $spacing-datepicker_quick_control_item-margin) * 0.5; // 上下方位快捷选择面板,固定两列,按钮宽度
+
+// compact
+$width-datepicker_presetPanel_top_and_bottom_three_col_button_compact: ($width-datepicker_presetPanel_top_and_bottom_content_date_compact - $spacing-datepicker_quick_control_item-margin * 2) / 3; // 上下方位快捷选择面板,固定三列,按钮宽度
+$width-datepicker_presetPanel_top_and_bottom_five_col_button_compact: ($width-datepicker_presetPanel_top_and_bottom_content_range_compact - $spacing-datepicker_quick_control_item-margin * 4) * 0.2; // 上下方位快捷选择面板,固定五列,按钮宽度
+$width-datepicker_presetPanel_top_and_bottom_two_col_button_compact: ($width-datepicker_presetPanel_top_and_bottom_content_month_compact - $spacing-datepicker_quick_control_item-margin) * 0.5; // 上下方位快捷选择面板,固定两列,按钮宽度
+
+// insetinput
+$height-datepicker_inset_input: 32px + $spacing-datepicker_insetInput_wrapper-paddingY; // 默认尺寸,insetInput高度
+$height-datepicker_preset_panel_inset_input: $height-datepicker_month_max + $spacing-datepicker_month-padding + $width-datepicker_nav + $spacing-datepicker_navigation_insetInput-paddingY * 2 + $height-datepicker_inset_input; // inset_input下,非month面板渲染最大高度,默认361px
+
+
+// insetinput compact
+$height-datepicker_inset_input_compact: 28px + $spacing-datepicker_insetInput_wrapper_compact-paddingY; // compact,insetInput高度, 默认36px
+$height-datepicker_preset_panel_inset_input_compact: $height-datepicker_month_max_compact + $width-datepicker_nav_compact + $spacing-datepicker_insetInput_wrapper_compact-paddingY * 2 + $height-datepicker_inset_input_compact; // inset_input下,非month面板渲染最大高度,默认296px

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

@@ -1,4 +1,5 @@
 import BaseFoundation, { DefaultAdapter } from '../base/foundation';
+import { PresetPosition } from './foundation';
 
 export interface YearAndMonthFoundationProps {
     currentYear?: number;
@@ -12,6 +13,9 @@ export interface YearAndMonthFoundationProps {
     noBackBtn?: boolean;
     disabledDate?: (date: Date) => boolean;
     density?: string;
+    presetPosition?: PresetPosition;
+    renderQuickControls?: React.ReactNode;
+    renderDateInput?: React.ReactNode;
 }
 
 export interface YearAndMonthFoundationState {

+ 138 - 22
packages/semi-ui/datePicker/_story/datePicker.stories.js

@@ -10,7 +10,7 @@ import {
   startOfWeek,
   endOfWeek,
 } from 'date-fns';
-import { Space, ConfigProvider, InputGroup, InputNumber, Form, withField, Button } from '../../index';
+import { Space, ConfigProvider, InputGroup, InputNumber, Form, withField, Button, RadioGroup, Radio } from '../../index';
 
 // stores
 import NeedConfirmDemo from './NeedConfirm';
@@ -36,6 +36,7 @@ import DatePickerSlot from './DatePickerSlot';
 import DatePickerTimeZone from './DatePickerTimeZone';
 import BetterRangePicker from './BetterRangePicker';
 import SyncSwitchMonth from './SyncSwitchMonth';
+import { Checkbox } from '../../checkbox';
 export * from './v2';
 
 export default {
@@ -189,6 +190,11 @@ const presets = [
     start: addDays(new Date(), 1),
     end: addDays(new Date(), 1),
   },
+  {
+    text: 'Today After Tomorrow',
+    start: addDays(new Date(), 2),
+    end: addDays(new Date(), 2),
+  },
   {
     text: 'Next Week',
     start: addWeeks(new Date(), 1),
@@ -200,24 +206,14 @@ const presets = [
     end: endOfMonth(addMonths(new Date(), 1)),
   },
   {
-    text: 'Today',
-    start: new Date(),
-    end: new Date(),
-  },
-  {
-    text: 'Tomorrow',
-    start: addDays(new Date(), 1),
-    end: addDays(new Date(), 1),
-  },
-  {
-    text: 'Next Week',
-    start: addWeeks(new Date(), 1),
-    end: addWeeks(new Date(), 2),
+    text: 'Next Bimonthly',
+    start: startOfMonth(addMonths(new Date(), 1)),
+    end: endOfMonth(addMonths(new Date(), 2)),
   },
   {
-    text: 'Next Month',
-    start: startOfMonth(addMonths(new Date(), 1)),
-    end: endOfMonth(addMonths(new Date(), 1)),
+    text: 'Next Quarter',
+    start: startOfMonth(addMonths(new Date(), 3)),
+    end: endOfMonth(addMonths(new Date(), 3)),
   },
 ];
 
@@ -225,40 +221,160 @@ export const DatePickerWithPresets = () => {
   const onPresetClick = (item, e) => {
     console.log('preset click', item, e);
   };
+  const [presetPosition, setPresetPosition] = useState('right');
+  const [insetInput, setInsetInput] = useState(false);
+  const [presetArr, setPresetArr] = useState(presets);
+
+   const BottomSlot = function(props) {
+        const { style } = props;
+        return (
+            <div style={{ padding: '12px 20px', ...style }}>
+                <div strong style={{ color: 'var(--semi-color-text-2)' }}>
+                    定版前请阅读
+                </div>
+                <div link={{ href: 'https://semi.design/', target: '_blank' }}>发版须知</div>
+            </div>
+        );
+    };
   return (
     <div style={demoDiv}>
       <span>带快捷选择的DatePicker</span>
+      <br/>
+      <br/>
+       <RadioGroup onChange={e=>setPresetPosition(e.target.value)} value={presetPosition} aria-label="选择快捷选择面板位置" name="preset-radio-group">
+            <Radio value={'left'}>left</Radio>
+            <Radio value={'right'}>right</Radio>
+            <Radio value={'top'}>top</Radio>
+            <Radio value={'bottom'}>bottom</Radio>
+        </RadioGroup>
+        <Checkbox value={insetInput} onChange={e=>setInsetInput(e.target.checked)}>insetInput</Checkbox>
+        <Checkbox value={presetArr} onChange={e=>setPresetArr(e.target.checked ? [...presets, ...presets, ...presets]:presets)}>more presets</Checkbox>
+       <br/>
+      <div>type="date"</div>
       <DatePicker
-        type="dateRange"
-        presets={presets}
+        type="date"
+        presets={presetArr}
+        insetInput={insetInput}
+        presetPosition={presetPosition}
         onPresetClick={onPresetClick}
         onChange={(...args) => console.log(...args)}
       />
+      <br/>
+      <br/>
+      <div>type="dateTime"</div>
       <DatePicker
         type="dateTime"
-        presets={presets.map(preset => ({
+        insetInput={insetInput}
+        needConfirm
+        bottomSlot={<BottomSlot/>}
+        presetPosition={presetPosition}
+        presets={presetArr.map(preset => ({
+          text: preset.text,
+          start: preset.start,
+        }))}
+        onPresetClick={onPresetClick}
+        onChange={(...args) => console.log(...args)}
+      />
+      <br/>
+      <br/>
+      <div>type="dateRange"</div>
+      <DatePicker
+        type="dateRange"
+        presets={presetArr}
+        insetInput={insetInput}
+        presetPosition={presetPosition}
+        onPresetClick={onPresetClick}
+        onChange={(...args) => console.log(...args)}
+      />
+      <br/>
+      <br/>
+      <div>type="dateTimeRange"</div>
+      <DatePicker
+        type="dateTimeRange"
+        presets={presetArr}
+        insetInput={insetInput}
+        presetPosition={presetPosition}
+        onPresetClick={onPresetClick}
+        onChange={(...args) => console.log(...args)}
+      />
+      <br/>
+      <br/>
+      <div>type="month"</div>
+      <DatePicker
+        type="month"
+        insetInput={insetInput}
+        presetPosition={presetPosition}
+        presets={presetArr.map(preset => ({
           text: preset.text,
           start: preset.start,
         }))}
         onPresetClick={onPresetClick}
         onChange={(...args) => console.log(...args)}
       />
+      <br/>
+      <br/>
+      <div>type="date" density="compact"</div>
+      <DatePicker
+        type="date"
+        presets={presetArr}
+        insetInput={insetInput}
+        density="compact"
+        presetPosition={presetPosition}
+        onPresetClick={onPresetClick}
+        onChange={(...args) => console.log(...args)}
+      />
+      <br/>
+      <br/>
+      <div>type="dateTime" density="compact"</div>
       <DatePicker
         type="dateTime"
+        insetInput={insetInput}
         needConfirm
-        presets={presets.map(preset => ({
+        density="compact"
+        presetPosition={presetPosition}
+        presets={presetArr.map(preset => ({
           text: preset.text,
           start: preset.start,
         }))}
         onPresetClick={onPresetClick}
         onChange={(...args) => console.log(...args)}
       />
+      <br/>
+      <br/>
+      <div>type="dateRange" density="compact"</div>
+      <DatePicker
+        type="dateRange"
+        presets={presetArr}
+        density="compact"
+        insetInput={insetInput}
+        presetPosition={presetPosition}
+        onPresetClick={onPresetClick}
+        onChange={(...args) => console.log(...args)}
+      />
+      <br/>
+      <br/>
+      <div>type="dateTimeRange"  density="compact"</div>
+      <DatePicker
+        type="dateTimeRange"
+        density="compact"
+        presets={presetArr}
+        insetInput={insetInput}
+        presetPosition={presetPosition}
+        onPresetClick={onPresetClick}
+        onChange={(...args) => console.log(...args)}
+      />
+      <br/>
+      <br/>
+      <div>type="month" density="compact"</div>
       <DatePicker
         type="month"
-        presets={presets.map(preset => ({
+        insetInput={insetInput}
+        presetPosition={presetPosition}
+        presets={presetArr.map(preset => ({
           text: preset.text,
           start: preset.start,
         }))}
+        density="compact"
         onPresetClick={onPresetClick}
         onChange={(...args) => console.log(...args)}
       />

+ 42 - 7
packages/semi-ui/datePicker/datePicker.tsx

@@ -76,6 +76,7 @@ export default class DatePicker extends BaseComponent<DatePickerProps, DatePicke
         max: PropTypes.number, // only work when multiple is true
         placeholder: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
         presets: PropTypes.array,
+        presetPosition: PropTypes.oneOf(strings.PRESET_POSITION_SET),
         onChange: PropTypes.func,
         onChangeWithDateFirst: PropTypes.bool,
         weekStartsOn: PropTypes.number,
@@ -134,6 +135,7 @@ export default class DatePicker extends BaseComponent<DatePickerProps, DatePicke
         stopPropagation: true,
         motion: true,
         prefixCls: cssClasses.PREFIX,
+        presetPosition: 'bottom',
         // position: 'bottomLeft',
         zIndex: popoverNumbers.DEFAULT_Z_INDEX,
         type: 'date',
@@ -415,7 +417,8 @@ export default class DatePicker extends BaseComponent<DatePickerProps, DatePicke
             onPanelChange,
             timeZone,
             triggerRender,
-            insetInput
+            insetInput,
+            presetPosition
         } = this.props;
         const { cachedSelectedValue, motionEnd, rangeInputFocus } = this.state;
 
@@ -456,21 +459,49 @@ export default class DatePicker extends BaseComponent<DatePickerProps, DatePicke
                 focusRecordsRef={this.focusRecordsRef}
                 triggerRender={triggerRender}
                 insetInput={insetInput}
+                presetPosition={presetPosition}
+                renderQuickControls={this.renderQuickControls()}
+                renderDateInput={this.renderDateInput()}
             />
         );
     }
 
     renderQuickControls() {
-        const { presets, type } = this.props;
+        const { presets, type, presetPosition, insetInput } = this.props;
         return (
             <QuickControl
                 type={type}
                 presets={presets}
+                insetInput={insetInput}
+                presetPosition={presetPosition}
                 onPresetClick={(item, e) => this.foundation.handlePresetClick(item, e)}
             />
         );
     }
 
+    renderDateInput() {
+        const { insetInput, dateFnsLocale, density, type, format, rangeSeparator, defaultPickerValue } = this.props;
+        const { insetInputValue, value } = this.state;
+
+        const insetInputProps = {
+            dateFnsLocale,
+            format,
+            insetInputValue,
+            rangeSeparator,
+            type,
+            value: value as Date[],
+            handleInsetDateFocus: this.handleInsetDateFocus,
+            handleInsetTimeFocus: this.handleInsetTimeFocus,
+            onInsetInputChange: this.handleInsetInputChange,
+            rangeInputStartRef: this.rangeInputStartRef,
+            rangeInputEndRef: this.rangeInputEndRef,
+            density,
+            defaultPickerValue
+        };
+
+        return insetInput ? <DateInput {...insetInputProps} insetInput={true} /> : null;
+    }
+
     handleOpenPanel = () => this.foundation.openPanel();
     handleInputChange: DatePickerFoundation['handleInputChange'] = (...args) => this.foundation.handleInputChange(...args);
     handleInsetInputChange = (options: InsetInputChangeProps) => this.foundation.handleInsetInputChange(options);
@@ -626,7 +657,7 @@ export default class DatePicker extends BaseComponent<DatePickerProps, DatePicke
     };
 
     renderPanel = (locale: Locale['DatePicker'], localeCode: string, dateFnsLocale: Locale['dateFnsLocale']) => {
-        const { dropdownClassName, dropdownStyle, density, topSlot, bottomSlot, insetInput, type, format, rangeSeparator, defaultPickerValue } = this.props;
+        const { dropdownClassName, dropdownStyle, density, topSlot, bottomSlot, insetInput, type, format, rangeSeparator, defaultPickerValue, presetPosition } = this.props;
         const { insetInputValue, value } = this.state;
         const wrapCls = classnames(
             cssClasses.PREFIX,
@@ -654,17 +685,18 @@ export default class DatePicker extends BaseComponent<DatePickerProps, DatePicke
         };
 
         return (
-            <div ref={this.panelRef} className={wrapCls} style={dropdownStyle}>
+            <div ref={this.panelRef} className={wrapCls} style={dropdownStyle} >
                 {topSlot && (
                     <div className={`${cssClasses.PREFIX}-topSlot`} x-semi-prop="topSlot">
                         {topSlot}
                     </div>
                 )}
-                {insetInput && <DateInput {...insetInputProps} insetInput={true} />}
+                {presetPosition === "top" && this.renderQuickControls()}
+                {/* {insetInput && <DateInput {...insetInputProps} insetInput={true} />} */}
                 {this.adapter.typeIsYearOrMonth()
                     ? this.renderYearMonthPanel(locale, localeCode)
                     : this.renderMonthGrid(locale, localeCode, dateFnsLocale)}
-                {this.renderQuickControls()}
+                {presetPosition === "bottom" && this.renderQuickControls()}
                 {bottomSlot && (
                     <div className={`${cssClasses.PREFIX}-bottomSlot`} x-semi-prop="bottomSlot">
                         {bottomSlot}
@@ -676,7 +708,7 @@ export default class DatePicker extends BaseComponent<DatePickerProps, DatePicke
     };
 
     renderYearMonthPanel = (locale: Locale['DatePicker'], localeCode: string) => {
-        const { density } = this.props;
+        const { density, presetPosition } = this.props;
 
         const date = this.state.value[0];
         let year = 0;
@@ -698,6 +730,9 @@ export default class DatePicker extends BaseComponent<DatePickerProps, DatePicke
                 currentYear={year}
                 currentMonth={month}
                 density={density}
+                presetPosition={presetPosition}
+                renderQuickControls={this.renderQuickControls()}
+                renderDateInput={this.renderDateInput()}
             />
         );
     };

+ 22 - 10
packages/semi-ui/datePicker/monthsGrid.tsx

@@ -76,6 +76,9 @@ export default class MonthsGrid extends BaseComponent<MonthsGridProps, MonthsGri
         onPanelChange: PropTypes.func,
         focusRecordsRef: PropTypes.object,
         triggerRender: PropTypes.func,
+        presetPosition: PropTypes.oneOf(strings.PRESET_POSITION_SET),
+        renderQuickControls: PropTypes.node,
+        renderDateInput: PropTypes.node
     };
 
     static defaultProps = {
@@ -600,9 +603,10 @@ export default class MonthsGrid extends BaseComponent<MonthsGridProps, MonthsGri
         );
     }
 
+
     render() {
         const { monthLeft, monthRight } = this.state;
-        const { type, insetInput } = this.props;
+        const { type, insetInput, presetPosition, renderQuickControls, renderDateInput } = this.props;
         const monthGridCls = classnames({
             [`${prefixCls }-month-grid`]: true,
         });
@@ -622,15 +626,23 @@ export default class MonthsGrid extends BaseComponent<MonthsGridProps, MonthsGri
         const yearOpenType = this.getYAMOpenType();
 
         return (
-            <div
-                className={monthGridCls}
-                x-type={type}
-                x-panel-yearandmonth-open-type={yearOpenType}
-                // FIXME:
-                x-insetinput={insetInput ? "true" : "false"}
-                ref={current => this.cacheRefCurrent('monthGrid', current)}
-            >
-                {content}
+            <div style={{ display: 'flex' }}>
+                {presetPosition === "left" && renderQuickControls}
+                <div>
+                    {renderDateInput}
+                    <div
+                        className={monthGridCls}
+                        x-type={type}
+                        x-panel-yearandmonth-open-type={yearOpenType}
+                        // FIXME:
+                        x-insetinput={insetInput ? "true" : "false"}
+                        x-preset-position={renderQuickControls === null ? 'null' : presetPosition}
+                        ref={current => this.cacheRefCurrent('monthGrid', current)}
+                    >
+                        {content}
+                    </div>
+                </div>
+                {presetPosition === "right" && renderQuickControls}
             </div>
         );
     }

+ 62 - 16
packages/semi-ui/datePicker/quickControl.tsx

@@ -2,52 +2,98 @@
 import React, { PureComponent } from 'react';
 import classNames from 'classnames';
 import PropTypes from 'prop-types';
-import { cssClasses } from '@douyinfe/semi-foundation/datePicker/constants';
+import { cssClasses, strings } from '@douyinfe/semi-foundation/datePicker/constants';
 import Button from '../button/index';
+import Typography from '../typography/index';
 import { noop } from '@douyinfe/semi-foundation/utils/function';
 import { PresetsType, PresetType } from '@douyinfe/semi-foundation/datePicker/foundation';
 
 const prefixCls = cssClasses.PREFIX;
+const { Text } = Typography;
 
 export interface QuickControlProps {
     presets: PresetsType;
+    presetPosition: typeof strings.PRESET_POSITION_SET[number];
     onPresetClick: (preset: PresetType, e: React.MouseEvent) => void;
     type: string;
+    insetInput: boolean;
 }
 
 class QuickControl extends PureComponent<QuickControlProps> {
     static propTypes = {
         presets: PropTypes.array,
+        presetPosition: PropTypes.oneOf(strings.PRESET_POSITION_SET),
         onPresetClick: PropTypes.func,
-        type: PropTypes.string
+        type: PropTypes.string,
+        insetInput: PropTypes.bool
     };
 
     static defaultProps = {
         presets: [] as PresetsType,
+        presetPosition: 'bottom',
         onPresetClick: noop,
     };
 
     render() {
-        const { presets, onPresetClick, type } = this.props;
+        const { presets, onPresetClick, type, presetPosition, insetInput } = this.props;
+        const isTypeRange = type === 'dateRange' || type === 'dateTimeRange';
+        const isPanelTopAndBottom = presetPosition === 'top' || presetPosition === 'bottom';
+        const isMonth =  type === 'month';
+        const isTopAndBottomRange = isPanelTopAndBottom && isTypeRange;
+        const isTopAndBottomMonth = isPanelTopAndBottom && isMonth;
+
         const wrapperCls = classNames(`${prefixCls}-quick-control`, {
-            [`${prefixCls}-quick-control-${type}`]: type
+            [`${prefixCls}-quick-control-${type}`]: type,
+            [`${prefixCls}-quick-control-${presetPosition}`]: true,
+        });
+        const headerCls = classNames({
+            [`${prefixCls}-quick-control-header`]: true,
+        });
+        const contentWrapperCls = classNames({
+            [`${prefixCls}-quick-control-${presetPosition}-content-wrapper`]: true,
+        });
+        const contentCls = classNames({
+            [`${prefixCls}-quick-control-${presetPosition}-content`]: !isTopAndBottomRange && !isTopAndBottomMonth,
+            [`${prefixCls}-quick-control-${presetPosition}-range-content`]: isTopAndBottomRange,
+            [`${prefixCls}-quick-control-${presetPosition}-month-content`]: isTopAndBottomMonth,
         });
-        const itemCls = classNames(`${prefixCls}-quick-control-item`);
+        const itemCls = classNames({
+            [`${prefixCls}-quick-control-${presetPosition}-content-item`]: !isTopAndBottomRange && !isTopAndBottomMonth,
+            [`${prefixCls}-quick-control-${presetPosition}-range-content-item`]: isTopAndBottomRange,
+            [`${prefixCls}-quick-control-${presetPosition}-month-content-item`]: isTopAndBottomMonth,
+        });
+        const ellipsisCls = classNames({
+            [`${prefixCls}-quick-control-${presetPosition}-content-item-ellipsis`]: !isTopAndBottomRange && !isTopAndBottomMonth,
+            [`${prefixCls}-quick-control-${presetPosition}-range-content-item-ellipsis`]: isTopAndBottomRange,
+            [`${prefixCls}-quick-control-${presetPosition}-month-content-item-ellipsis`]: isTopAndBottomMonth,
+        });
+
+
         if (!presets.length) {
             return null;
         }
         return (
-            <div className={wrapperCls}>
-                {presets.map((item, index) => {
-                    const _item: PresetType = typeof item === 'function' ? item() : item;
-                    return (
-                        <div className={itemCls} onClick={e => onPresetClick(_item, e)} key={index}>
-                            <Button size="small" theme="borderless" type="primary">
-                                <span>{_item.text}</span>
-                            </Button>
-                        </div>
-                    );
-                })}
+            <div className={wrapperCls} x-insetinput={insetInput ? "true" : "false"}>
+                { !isPanelTopAndBottom && <div className={headerCls}>快捷选择</div>}
+                <div className={contentWrapperCls}>
+                    <div className={contentCls}>
+                        {presets.map((item, index) => {
+                            const _item: PresetType = typeof item === 'function' ? item() : item;
+                            return (
+                                <Button size="small" type="primary" onClick={e => onPresetClick(_item, e)} key={index}>         
+                                    <div className={itemCls}>
+                                        <Text 
+                                            ellipsis={{ showTooltip: true }}
+                                            className={ellipsisCls}
+                                        >
+                                            {_item.text}
+                                        </Text>
+                                    </div>
+                                </Button>
+                            );
+                        })}
+                    </div>
+                </div>
             </div>
         );
     }

+ 31 - 5
packages/semi-ui/datePicker/yearAndMonth.tsx

@@ -15,6 +15,8 @@ import { BASE_CLASS_PREFIX } from '@douyinfe/semi-foundation/base/constants';
 import { noop, stubFalse } from 'lodash';
 import { setYear, setMonth } from 'date-fns';
 import { Locale } from '../locale/interface';
+import { strings } from '@douyinfe/semi-foundation/datePicker/constants';
+
 
 const prefixCls = `${BASE_CLASS_PREFIX}-datepicker`;
 
@@ -36,6 +38,9 @@ class YearAndMonth extends BaseComponent<YearAndMonthProps, YearAndMonthState> {
         noBackBtn: PropTypes.bool,
         disabledDate: PropTypes.func,
         density: PropTypes.string,
+        presetPosition: PropTypes.oneOf(strings.PRESET_POSITION_SET),
+        renderQuickControls: PropTypes.node,
+        renderDateInput: PropTypes.node
     };
 
     static defaultProps = {
@@ -195,7 +200,7 @@ class YearAndMonth extends BaseComponent<YearAndMonthProps, YearAndMonthState> {
     };
 
     render() {
-        const { locale, noBackBtn, density } = this.props;
+        const { locale, noBackBtn, density, presetPosition, renderQuickControls, renderDateInput } = this.props;
         const prefix = `${prefixCls}-yearmonth-header`;
         // i18n
         const selectDateText = locale.selectDate;
@@ -216,10 +221,31 @@ class YearAndMonth extends BaseComponent<YearAndMonthProps, YearAndMonthState> {
                         </IconButton>
                     </div>
                 )}
-                <ScrollList>
-                    {this.renderColYear()}
-                    {this.renderColMonth()}
-                </ScrollList>
+                {
+                    presetPosition ? (
+                        <div style={{ display: 'flex' }}> 
+                            {presetPosition === "left" && renderQuickControls}
+                            <div>
+                                {renderDateInput}
+                                <ScrollList>
+                                    {this.renderColYear()}
+                                    {this.renderColMonth()}
+                                </ScrollList>
+                            </div>
+                            {presetPosition === "right" && renderQuickControls}
+                        </div>
+                    ) : 
+                        <>
+                            {renderDateInput}
+                            <ScrollList>
+                                {this.renderColYear()}
+                                {this.renderColMonth()}
+                            </ScrollList>
+                        </>
+
+                    
+                }
+             
             </React.Fragment>
         );
     }