Skip to content

Commit

Permalink
feat(DateRangePicker): add new DateRangePicker
Browse files Browse the repository at this point in the history
  • Loading branch information
itiisennsinn committed Nov 5, 2021
1 parent 758f44a commit 4f0b523
Show file tree
Hide file tree
Showing 15 changed files with 236 additions and 34 deletions.
17 changes: 12 additions & 5 deletions src/date-picker/Picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import { DatePickerProps } from './interfaces';

export const DatePicker: React.FC<DatePickerProps> = (props: DatePickerProps) => {
const {
onVisibleChange: onDropdownVisibleChange,
onVisibleChange: onPopoverVisibleChange,
overlayClassName,
visible: dropdownVisible,
visible: popoverVisible,
trigger,
disabled,
value,
Expand All @@ -22,21 +22,25 @@ export const DatePicker: React.FC<DatePickerProps> = (props: DatePickerProps) =>
placeholder,
allowClear,
format: formatString,
prefix,
suffix,
hidePrefix,
size,
...restProps
} = props;

const prefixCls = usePrefixCls('date-picker-new');
const overlayCls = classnames(`${prefixCls}-overlay`, overlayClassName);

const [visible, setVisible] = useControlledState(dropdownVisible, false);
const [visible, setVisible] = useControlledState(popoverVisible, false);

const [controlledValue, setControlledValue] = useControlledState(value, defaultValue);

const formatDate = (date: Date) => format(formatString ?? 'yyyy/MM/dd', date);

const handleVisibleChange = (current: boolean) => {
setVisible(current);
onDropdownVisibleChange?.(current);
onPopoverVisibleChange?.(current);
};

const handleOnSelect = (currentValue: Date) => {
Expand All @@ -53,11 +57,14 @@ export const DatePicker: React.FC<DatePickerProps> = (props: DatePickerProps) =>
}
return (
<InputButton
prefix={<CalendarOutlined />}
prefix={prefix || <CalendarOutlined />}
placeholder={placeholder}
disabled={disabled}
allowClear={allowClear}
value={controlledValue && formatDate(controlledValue)}
size={size}
suffix={suffix}
hidePrefix={hidePrefix}
/>
);
}
Expand Down
101 changes: 101 additions & 0 deletions src/date-range-picker/Picker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import React from 'react';
import classnames from 'classnames';
import { usePrefixCls, useControlledState } from '@gio-design/utils';
import format from 'date-fns/format';
import { CalendarOutlined } from '@gio-design/icons';
import Popover from '../popover';
import { InputButton } from '../input';
import StaticDateRangePicker from '../static-date-range-picker';
import { DateRangePickerProps, NullableDate, NullableString } from './interfaces';

export const DateRangePicker: React.FC<DateRangePickerProps> = (props: DateRangePickerProps) => {
const {
onVisibleChange: onPopoverVisibleChange,
overlayClassName,
visible: popoverVisible,
trigger,
disabled,
value,
defaultValue,
onSelect,
disabledDate,
placeholder,
allowClear,
format: formatString,
prefix,
suffix,
hidePrefix,
size,
...restProps
} = props;

const prefixCls = usePrefixCls('date-range-picker-new');
const overlayCls = classnames(`${prefixCls}-overlay`, overlayClassName);

const formatDates = (dates: [NullableDate, NullableDate]): NullableString => {
const strongFormat = (date: NullableDate) => (date ? format(date, formatString ?? 'yyyy/MM/dd') : undefined);
return `${strongFormat(dates[0]) || ''} - ${strongFormat(dates[1]) || ''}`;
};

const [visible, setVisible] = useControlledState(popoverVisible, false);

const [controlledValue, setControlledValue] = useControlledState<[NullableDate, NullableDate] | undefined>(
value,
defaultValue
);

const handleVisibleChange = (current: boolean) => {
setVisible(current);
onPopoverVisibleChange?.(current);
};

const handleOnSelect = (currentValue: [Date, Date], index: number) => {
setControlledValue(currentValue);
if (index) {
setVisible(false);
onSelect?.(currentValue, formatDates(currentValue));
}
};

const content = (
<StaticDateRangePicker
onSelect={handleOnSelect}
value={controlledValue as [Date, Date]}
disabledDate={disabledDate}
/>
);

function renderTrigger() {
if (trigger) {
return <div>{trigger}</div>;
}
return (
<InputButton
prefix={prefix || <CalendarOutlined />}
placeholder={placeholder}
disabled={disabled}
allowClear={allowClear}
value={controlledValue && formatDates(controlledValue)}
size={size}
suffix={suffix}
hidePrefix={hidePrefix}
/>
);
}

return (
<Popover
content={content}
trigger={['click', 'focus']}
visible={visible}
placement="bottomLeft"
overlayClassName={overlayCls}
onVisibleChange={handleVisibleChange}
{...restProps}
>
{renderTrigger()}
</Popover>
);
};

export default DateRangePicker;
39 changes: 39 additions & 0 deletions src/date-range-picker/demos/picker.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from 'react';
import { Meta, Story } from '@storybook/react/types-6-0';
import { action } from '@storybook/addon-actions';

import DateRangePicker from '../index';

import { DateRangePickerProps } from '../interfaces';

import '../style';

export default {
title: 'Upgraded/DateRangePicker',
component: DateRangePicker,
parameters: {
docs: {},
},
} as Meta;

const defaultPlaceholder = '选择日期范围';

const Template: Story<DateRangePickerProps> = (args) => (
<div style={{ width: 280 }}>
<DateRangePicker {...args} />
</div>
);

export const Basic = Template.bind({});
Basic.args = {
placeholder: defaultPlaceholder,
onSelect: action('selected:'),
onClear: action('onClear:'),
};

export const DisbaledDate = Template.bind({});
DisbaledDate.args = {
placeholder: defaultPlaceholder,
onSelect: action('selected:'),
disabledDate: (current: Date) => current.getTime() > new Date().getTime(),
};
5 changes: 5 additions & 0 deletions src/date-range-picker/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import DateRangePicker from './Picker';

export type { DateRangePickerProps } from './interfaces';

export default DateRangePicker;
35 changes: 35 additions & 0 deletions src/date-range-picker/interfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react';
import { CommonProps } from '@gio-design/utils';
import { PopoverProps } from '../popover';
import { StaticDateRangePickerProps } from '../static-date-range-picker';
import { InputButtonProps } from '../input';

export type NullableDate = Date | undefined;
export type NullableString = string | undefined;

export interface DateRangePickerProps
extends CommonProps,
Omit<InputButtonProps, 'value' | 'onSelect' | 'defaultValue'>,
Omit<StaticDateRangePickerProps, 'onSelect' | 'value' | 'defaultValue'>,
Omit<PopoverProps, 'trigger' | 'placement' | 'prefixCls' | 'children' | 'content'> {
/**
* 自定义的触发器
*/
trigger?: React.ReactNode;
/**
* 日期展示格式
*/
format?: string;
/**
* 选择日期时的回调
*
* @param dates - 选择的日期 `[Date, Date]`
* @param dateStrings - 格式化后的日期 `[string, string]`
*/
onSelect?: (dates: [NullableDate, NullableDate], dateStrings: NullableString) => void;
/**
* 选择的日期
*/
value?: [NullableDate, NullableDate];
defaultValue?: [NullableDate, NullableDate];
}
12 changes: 12 additions & 0 deletions src/date-range-picker/style/index.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
@import '../../stylesheet/index.less';
@import '../../stylesheet/mixin/index.less';
@import '../../stylesheet/variables/index.less';

@picker-prefix-cls: ~'@{component-prefix}-date-range-picker-new';

.@{picker-prefix-cls}-overlay {
height: fit-content;
background: @gray-0;
border-radius: 4px;
.elevation(2);
}
1 change: 1 addition & 0 deletions src/date-range-picker/style/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import './index.less';
4 changes: 2 additions & 2 deletions src/locales/zh-CN.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Locale } from '@gio-design/utils';
import datePickerLocale from '../date-picker/locales/zh-CN';
import dateRangePickerLocale from '../date-range-picker/locales/zh-CN';
import datePickerLocale from '../static-date-picker/locales/zh-CN';
import dateRangePickerLocale from '../static-date-range-picker/locales/zh-CN';
import dateRangeSelectorLocale from '../date-range-selector/locales/zh-CN';
import dateSelectorLocale from '../date-selector/locales/zh-CN';
import emptyLocale from '../empty/locales/zh-CN';
Expand Down
4 changes: 2 additions & 2 deletions src/static-date-picker/DatePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import { LeftDoubleOutlined, LeftOutlined, RightOutlined, RightDoubleOutlined }
import PickerPanel from 'rc-picker/lib/PickerPanel';
import generateDateFns from 'rc-picker/lib/generate/dateFns';
import defaultLocale from './locales/zh-CN';
import { DatePickerProps } from './interfaces';
import { StaticDatePickerProps } from './interfaces';

function DatePicker({ viewDate, ...restProps }: DatePickerProps) {
function DatePicker({ viewDate, ...restProps }: StaticDatePickerProps) {
const locale = useLocale('DatePicker') || defaultLocale;
return (
<PickerPanel<Date>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Subtitle, ArgsTable, Story, Canvas } from '@storybook/addon-docs';
import DateRangePicker from './index';
import StaticDateRangePicker from './index';

# DateRangePicker 日期范围选择器
# StaticDateRangePicker 日期范围选择器

<Subtitle>当用户需要一个时间段,可以在面板中进行选择。</Subtitle>

Expand All @@ -12,15 +12,15 @@ import DateRangePicker from './index';
最简单的用法,在面板中可以选择日期范围。

<Canvas>
<Story id="components-daterangepicker--basic" />
<Story id="components-StaticDateRangePicker--basic" />
</Canvas>

### 禁止选择部分日期

可用 `disabledDate` 禁止选择部分日期。

<Canvas>
<Story id="components-daterangepicker--disabled-date" />
<Story id="components-StaticDateRangePicker--disabled-date" />
</Canvas>

### 面板默认日期
Expand All @@ -33,4 +33,4 @@ import DateRangePicker from './index';

## 参数说明

<ArgsTable of={DateRangePicker} />
<ArgsTable of={StaticDateRangePicker} />
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import React from 'react';
import { Meta, Story } from '@storybook/react/types-6-0';
import { action } from '@storybook/addon-actions';
import { isBefore, startOfToday, subMonths } from 'date-fns';
import Docs from './DateRangePicker.mdx';
import { DateRangePicker, DateRangePickerProps } from './index';
import Docs from './StaticDateRangePicker.mdx';
import { StaticDateRangePicker, StaticDateRangePickerProps } from './index';

import './style';

export default {
component: DateRangePicker,
component: StaticDateRangePicker,
parameters: {
design: {
type: 'figma',
Expand All @@ -19,10 +19,12 @@ export default {
page: Docs,
},
},
title: 'Upgraded/StaticDateRangePicker',
title: 'Upgraded/StaticStaticDateRangePicker',
} as Meta;

const Template: Story<DateRangePickerProps> = (args) => <DateRangePicker onSelect={action('selected:')} {...args} />;
const Template: Story<StaticDateRangePickerProps> = (args) => (
<StaticDateRangePicker onSelect={action('selected:')} {...args} />
);

export const Basic = Template.bind({});
Basic.args = {};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import { RangeValue } from 'rc-picker/lib/interface';
import { useControlledState, usePrefixCls } from '@gio-design/utils';
import isBefore from 'date-fns/isBefore';
import StaticDatePicker, { StaticDatePickerContext } from '../static-date-picker';
import { DateRangePickerProps } from './interfaces';
import { StaticDateRangePickerProps } from './interfaces';
import { getDefaultViewDates, calcClosingViewDate, mergeDates } from './utils';

function DateRangePicker({
function StaticDateRangePicker({
className,
defaultValue,
defaultViewDates,
Expand All @@ -20,7 +20,7 @@ function DateRangePicker({
style,
value,
locale,
}: DateRangePickerProps) {
}: StaticDateRangePickerProps) {
const [viewDates, setViewDates] = React.useState<[Date, Date]>(defaultViewDates ?? getDefaultViewDates());
const [hoveredDates, setHoveredDates] = React.useState<RangeValue<Date>>();
const [dateIndex, setDateIndex] = React.useState<number>(0);
Expand All @@ -41,13 +41,13 @@ function DateRangePicker({
>
<StaticDatePicker
className={`${preficCls}__${position}`}
disabledDate={(currentDate) => {
disabledDate={(currentDate: Date) => {
const isBeforeStartDate =
selectedValue && selectedValue[0] && !selectedValue[1] ? isBefore(currentDate, selectedValue[0]) : false;
const isDisabledDate = disabledDate ? disabledDate(currentDate) : false;
return isBeforeStartDate || isDisabledDate;
}}
onPanelChange={(currentValue) => {
onPanelChange={(currentValue: Date) => {
if (index) {
setViewDates([calcClosingViewDate(currentValue, -1), currentValue]);
} else {
Expand Down Expand Up @@ -94,4 +94,4 @@ function DateRangePicker({
);
}

export default DateRangePicker;
export default StaticDateRangePicker;
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React from 'react';
import { fireEvent, render, screen } from '@testing-library/react';
import { add, startOfMonth, startOfToday, subMonths } from 'date-fns';
import { Basic, DefaultViewDates, DisabledDate } from '../DateRangePicker.stories';
import { Basic, DefaultViewDates, DisabledDate } from '../StaticDateRangePicker.stories';

describe('DateRangePicker', () => {
describe('StaticDateRangePicker', () => {
beforeAll(() => {
// mock now is 2021/05/20 00:00:00.000
jest.useFakeTimers('modern');
Expand Down
Loading

0 comments on commit 4f0b523

Please sign in to comment.