Skip to content

Commit

Permalink
feat: Update DateRanger interaction
Browse files Browse the repository at this point in the history
  • Loading branch information
linhf123 committed Mar 1, 2024
1 parent 130eb6b commit b9ca320
Show file tree
Hide file tree
Showing 10 changed files with 299 additions and 49 deletions.
1 change: 1 addition & 0 deletions packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"moment": "^2.29.4",
"path-to-regexp": "^6.2.1",
"randexp": "^0.5.3",
"rc-picker": "^4.1.5",
"rc-util": "^5.38.1",
"react-copy-to-clipboard": "^5.1.0",
"react-json-view": "^1.21.3",
Expand Down
193 changes: 193 additions & 0 deletions packages/ui/src/DateRanger/PickerPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import type { Dayjs } from 'dayjs';
import type { Moment } from 'moment';
import React, { useEffect } from 'react';
import dayjsGenerateConfig from 'rc-picker/lib/generate/dayjs';
import momentGenerateConfig from 'rc-picker/lib/generate/moment';
import useCSSVarCls from 'antd/es/config-provider/hooks/useCSSVarCls';
import useStyle from 'antd/es/date-picker/style/index';
import { PickerPanel } from 'rc-picker';
import classNames from 'classnames';
import { Button, Col, Divider, Form, Input, Row, Space } from '@oceanbase/design';
import { noop } from 'lodash';

type RangeValue = [Moment, Moment] | [Dayjs, Dayjs];

export interface PickerPanelProps {
value?: RangeValue;
defaultValue?: RangeValue;
onCancel: () => void;
onOk: (v: RangeValue) => void;
isMoment: boolean;
locale: any;
}

const prefixCls = 'ant-picker';

const InternalPickerPanel = (props: PickerPanelProps) => {
const { isMoment, locale, onOk = noop, onCancel = noop } = props;
const rootCls = useCSSVarCls(prefixCls);
const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls, rootCls);

const [calendarValue, setCalendarValue] = React.useState([]);
const [internalHoverValues, setInternalHoverValues] = React.useState(null);

const hoverValues = React.useMemo(() => {
return internalHoverValues || calendarValue;
}, [internalHoverValues, calendarValue]);

const [activeIndex, setActiveIndex] = React.useState(0);

// ======================== Change ========================
const fillCalendarValue = (date, index: number) =>
// Trigger change only when date changed
fillIndex(hoverValues, index, date);

function fillIndex<T extends any[]>(ori: T, index: number, value: T[number]): T {
const clone = [...ori] as T;
clone[index] = value;
return clone;
}

const onPanelHover = date => {
setInternalHoverValues(date ? fillCalendarValue(date, activeIndex) : null);
};

const formatValues = [...hoverValues]
.sort((a, b) => {
return a?.valueOf() - b?.valueOf();
})
.map(item => {
return item.format('YYYY-MM-DD');
});

const [form] = Form.useForm();
useEffect(() => {
const [s, e] = formatValues;
form.setFieldsValue({
startDate: s,
endDate: e,
});
}, [...formatValues]);

return (
<div>
{wrapCSSVar(
<div
className={classNames('ant-picker-dropdown', hashId, cssVarCls, rootCls)}
style={{
position: 'initial',
pointerEvents: 'auto',
}}
>
<PickerPanel
prefixCls={prefixCls}
// @ts-ignore
generateConfig={isMoment ? momentGenerateConfig : dayjsGenerateConfig}
onFocus={(...res) => {
// onSelectorFocus
console.log(res, 'onFocus');
}}
onBlur={(...res) => {
console.log(res, 'onBlur');
}}
onHover={(...res) => {
onPanelHover(res[0]);
}}
onNow={(...res) => {
console.log(res, 'onNow');
}}
onOk={(...res) => {
console.log(res, 'onOk');
}}
onPanelChange={(...res) => {
console.log(res, 'onPanelChange');
}}
onPickerValueChange={(...res) => {
console.log(res, 'onPickerValueChange');
}}
onPresetHover={(...res) => {
console.log(res, 'onPresetHover');
}}
onPresetSubmit={(...res) => {
console.log(res, 'onPresetSubmit');
}}
onSelect={(...res) => {
setCalendarValue(fillCalendarValue(res[0], activeIndex));
setActiveIndex(index => {
return index + 1 === 2 ? 0 : index + 1;
});
}}
hoverRangeValue={hoverValues}
activeOffset={0}
allowEmpty={[false, false]}
mode="date"
internalMode="date"
picker="date"
locale={locale}
presets={[]}
showNow={false}
range={true}
needConfirm={false}
/>
</div>
)}
<div>
<Form
layout="vertical"
autoComplete="off"
requiredMark={false}
style={{ width: 280, padding: '0 12px' }}
form={form}
>
<Row gutter={12}>
<Col span={14}>
<Form.Item name="startDate" label="开始日期" style={{ marginBottom: 8 }}>
{/* 输入日期时,先检测时间是否合理,合理就回显 */}
<Input size="middle" />
</Form.Item>
</Col>
<Col span={10}>
<Form.Item name="startTime" label="开始时间" style={{ marginBottom: 8 }}>
<Input size="middle" />
</Form.Item>
</Col>
</Row>
<Row gutter={12}>
<Col span={14}>
<Form.Item name="endDate" label="结束日期" style={{ marginBottom: 8 }}>
<Input />
</Form.Item>
</Col>
<Col span={10}>
<Form.Item name="endTime" label="结束时间" style={{ marginBottom: 8 }}>
<Input />
</Form.Item>
</Col>
</Row>
</Form>
</div>
<Divider style={{ margin: '12px 0' }}></Divider>
<Space style={{ width: '100%', justifyContent: 'flex-end' }}>
<Button
size="small"
onClick={() => {
onCancel();
}}
>
取消
</Button>
<Button
size="small"
type="primary"
onClick={() => {
onOk(formatValues);
}}
>
确定
</Button>
</Space>
</div>
);
};

export default InternalPickerPanel;
109 changes: 88 additions & 21 deletions packages/ui/src/DateRanger/Ranger.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Button, DatePicker, Dropdown, Radio, Space, theme } from '@oceanbase/design';
import { Button, DatePicker, Dropdown, Radio, Space, Tooltip, theme } from '@oceanbase/design';
import type { RangePickerProps } from '@oceanbase/design/es/date-picker';
import type { Dayjs } from 'dayjs';
import dayjs from 'dayjs';
Expand All @@ -7,8 +7,9 @@ import type { Moment } from 'moment';
import moment from 'moment';
import classNames from 'classnames';
import { useInterval } from 'ahooks';
import React, { useEffect, useMemo, useState } from 'react';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import LocaleWrapper from '../locale/LocaleWrapper';

import { getPrefix } from '../_util';
import {
CUSTOMIZE,
Expand All @@ -33,6 +34,7 @@ import {
RightOutlined,
ZoomOutOutlined,
} from '@oceanbase/icons';
import InternalPickerPanel from './PickerPanel';

export type RangeName = 'customize' | string;

Expand Down Expand Up @@ -107,6 +109,13 @@ const Ranger = (props: DateRangerProps) => {
value || defaultValue ? CUSTOMIZE : defaultQuickValue ?? selects?.[0]?.name
);

const [open, setOpen] = useState(false);
const [tooltipOpen, setTooltipOpen] = useState(false);
const refState = useRef({
tooltipOpen,
});
refState.current.tooltipOpen = tooltipOpen;

// 没有 selects 时,回退到普通 RangePicker, 当前时间选项为自定义时,应该显示 RangePicker
const [isPlay, setIsPlay] = useState(rangeName !== CUSTOMIZE);

Expand Down Expand Up @@ -142,9 +151,13 @@ const Ranger = (props: DateRangerProps) => {
}
}, []);

const closeTooltip = () => {
setOpen(false);
setTooltipOpen(false);
};
const handleNameChange = (name: string) => {
if (name === CUSTOMIZE) {
setIsPlay(false);
if (name !== CUSTOMIZE) {
closeTooltip();
}
setRangeName(name);
};
Expand Down Expand Up @@ -284,13 +297,56 @@ const Ranger = (props: DateRangerProps) => {
<div className={`${prefix}-wrapper`}>
<Dropdown
trigger={['click']}
open={open}
// 关闭后进行销毁,才可以将 Tooltip 进行同步关闭
destroyPopupOnHide={true}
// 存在缓存,会锁死里面的值
onOpenChange={o => {
if (o === false && refState.current.tooltipOpen) {
return;
}

setOpen(o);
}}
menu={{
items: [
...selects,
{
name: CUSTOMIZE,
rangeLabel: '自定义',
label: '自定义时间',
label: (
<Tooltip
open={tooltipOpen}
onOpenChange={o => {
if (o) {
setTooltipOpen(true);
}
}}
placement="right"
overlayStyle={{
maxWidth: 'none',
}}
overlayInnerStyle={{
background: '#fff',
}}
title={
<InternalPickerPanel
// @ts-ignore
locale={locale.rcPicker}
isMoment={isMoment}
// TODO: 代补充值相关逻辑
onOk={v => {
closeTooltip();
}}
onCancel={() => {
closeTooltip();
}}
/>
}
>
自定义时间
</Tooltip>
),
},
]
.filter(item => {
Expand All @@ -315,6 +371,7 @@ const Ranger = (props: DateRangerProps) => {
}}
>
<span className={`${prefix}-label`}>{item.rangeLabel}</span>
{/* @ts-ignore */}
{locale[item.label] || item.label}
</Space>
),
Expand All @@ -335,22 +392,32 @@ const Ranger = (props: DateRangerProps) => {
</Space>
</Dropdown>
{!isPlay && (
/* @ts-ignore */
<DatePicker.RangePicker
className={`${prefix}-range-picker`}
disabledDate={pastOnly ? disabledFuture : disabledDate}
format={v => {
// format 会影响布局,原先采用 v.year() === new Date().getFullYear() 进行判断,value 一共会传入三次(range0 range1 now), 会传入最新的时间导致判断异常
return isThisYear ? v.format(DATE_TIME_FORMAT) : v.format(YEAR_DATE_TIME_FORMAT);
<span
onClick={() => {
setOpen(true);
}}
defaultValue={defaultValue}
value={innerValue || defaultInternalValue}
onChange={datePickerChange}
showTime={true}
size={size}
// 透传 props 到 antd Ranger
{...omit(rest, 'value', 'onChange')}
/>
>
{/* @ts-ignore */}
<DatePicker.RangePicker
className={`${prefix}-range-picker`}
style={{
pointerEvents: 'none',
}}
disabledDate={pastOnly ? disabledFuture : disabledDate}
format={v => {
// format 会影响布局,原先采用 v.year() === new Date().getFullYear() 进行判断,value 一共会传入三次(range0 range1 now), 会传入最新的时间导致判断异常
return isThisYear ? v.format(DATE_TIME_FORMAT) : v.format(YEAR_DATE_TIME_FORMAT);
}}
defaultValue={defaultValue}
value={innerValue || defaultInternalValue}
onChange={datePickerChange}
showTime={true}
allowClear={false}
size={size}
// 透传 props 到 antd Ranger
{...omit(rest, 'value', 'onChange')}
/>
</span>
)}
</div>
<Radio.Group
Expand Down Expand Up @@ -431,6 +498,6 @@ const Ranger = (props: DateRangerProps) => {
};

export default LocaleWrapper({
componentName: 'Ranger',
componentName: 'DateRanger',
defaultLocale: zhCN,
})(Ranger);
4 changes: 2 additions & 2 deletions packages/ui/src/DateRanger/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ nav:

<code src="./demo/basic.tsx" title="基本"></code>

<code src="./demo/with-form.tsx" title="配合 Form 使用"></code>
<!-- <code src="./demo/with-form.tsx" title="配合 Form 使用"></code>
<code src="./demo/selected.tsx" title="时间范围快捷选项"></code>
<code src="./demo/default-value.tsx" title="指定默认值"></code>
<code src="./demo/default-value.tsx" title="指定默认值"></code> -->

## API

Expand Down
Loading

0 comments on commit b9ca320

Please sign in to comment.