mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
DateTimePicker: Add min date support to calendar (#64632)
This commit is contained in:
parent
ff96cd1342
commit
15b76808b7
@ -16,6 +16,26 @@ const [date, setDate] = useState<DateTime>(dateTime('2021-05-05 12:00:00'));
|
|||||||
return <DateTimePicker label="Date" date={date} onChange={setDate} />;
|
return <DateTimePicker label="Date" date={date} onChange={setDate} />;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### With disbled hours, minutes or seconds
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { DateTime, dateTime } from '@grafana/data';
|
||||||
|
import { DateTimePicker } from '@grafana/ui';
|
||||||
|
|
||||||
|
const [date, setDate] = useState<DateTime>(dateTime('2021-05-05 12:00:00'));
|
||||||
|
return (
|
||||||
|
<DateTimePicker
|
||||||
|
label="Date"
|
||||||
|
date={date}
|
||||||
|
onChange={setDate}
|
||||||
|
disabledHours={() => [0, 1, 2]}
|
||||||
|
disabledMinutes={() => [10, 15, 30]}
|
||||||
|
disabledSeconds={() => [5, 10, 15, 20]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
### Props
|
### Props
|
||||||
|
|
||||||
<ArgsTable of={DateTimePicker} />
|
<ArgsTable of={DateTimePicker} />
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { action } from '@storybook/addon-actions';
|
||||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
@ -8,6 +9,13 @@ import { withCenteredStory } from '../../../utils/storybook/withCenteredStory';
|
|||||||
import { DateTimePicker } from './DateTimePicker';
|
import { DateTimePicker } from './DateTimePicker';
|
||||||
import mdx from './DateTimePicker.mdx';
|
import mdx from './DateTimePicker.mdx';
|
||||||
|
|
||||||
|
const today = new Date();
|
||||||
|
|
||||||
|
// minimum date is initially set to 7 days before to allow the user
|
||||||
|
// to quickly see its effects
|
||||||
|
const minimumDate = new Date();
|
||||||
|
minimumDate.setDate(minimumDate.getDate() - 7);
|
||||||
|
|
||||||
const meta: ComponentMeta<typeof DateTimePicker> = {
|
const meta: ComponentMeta<typeof DateTimePicker> = {
|
||||||
title: 'Pickers and Editors/TimePickers/DateTimePicker',
|
title: 'Pickers and Editors/TimePickers/DateTimePicker',
|
||||||
decorators: [withCenteredStory],
|
decorators: [withCenteredStory],
|
||||||
@ -19,6 +27,14 @@ const meta: ComponentMeta<typeof DateTimePicker> = {
|
|||||||
onChange: {
|
onChange: {
|
||||||
table: { disable: true },
|
table: { disable: true },
|
||||||
},
|
},
|
||||||
|
minDate: { control: 'date' },
|
||||||
|
maxDate: { control: 'date' },
|
||||||
|
showSeconds: { control: 'boolean' },
|
||||||
|
},
|
||||||
|
args: {
|
||||||
|
minDate: minimumDate,
|
||||||
|
maxDate: today,
|
||||||
|
showSeconds: true,
|
||||||
},
|
},
|
||||||
parameters: {
|
parameters: {
|
||||||
docs: {
|
docs: {
|
||||||
@ -27,9 +43,54 @@ const meta: ComponentMeta<typeof DateTimePicker> = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Basic: ComponentStory<typeof DateTimePicker> = ({ label }) => {
|
export const OnlyWorkingHoursEnabled: ComponentStory<typeof DateTimePicker> = ({
|
||||||
const [date, setDate] = useState<DateTime>(dateTime('2021-05-05 12:00:00'));
|
label,
|
||||||
return <DateTimePicker label={label} date={date} onChange={setDate} />;
|
minDate,
|
||||||
|
maxDate,
|
||||||
|
showSeconds,
|
||||||
|
}) => {
|
||||||
|
const [date, setDate] = useState<DateTime>(dateTime(today));
|
||||||
|
// the minDate arg can change from Date object to number, we need to handle this
|
||||||
|
// scenario to avoid a crash in the component's story.
|
||||||
|
const minDateVal = typeof minDate === 'number' ? new Date(minDate) : minDate;
|
||||||
|
const maxDateVal = typeof maxDate === 'number' ? new Date(maxDate) : maxDate;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DateTimePicker
|
||||||
|
label={label}
|
||||||
|
disabledHours={() => [0, 1, 2, 3, 4, 5, 6, 19, 20, 21, 22, 23]}
|
||||||
|
minDate={minDateVal}
|
||||||
|
maxDate={maxDateVal}
|
||||||
|
date={date}
|
||||||
|
showSeconds={showSeconds}
|
||||||
|
onChange={(newValue) => {
|
||||||
|
action('on change')(newValue);
|
||||||
|
setDate(newValue);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Basic: ComponentStory<typeof DateTimePicker> = ({ label, minDate, maxDate, showSeconds }) => {
|
||||||
|
const [date, setDate] = useState<DateTime>(dateTime(today));
|
||||||
|
// the minDate arg can change from Date object to number, we need to handle this
|
||||||
|
// scenario to avoid a crash in the component's story.
|
||||||
|
const minDateVal = typeof minDate === 'number' ? new Date(minDate) : minDate;
|
||||||
|
const maxDateVal = typeof maxDate === 'number' ? new Date(maxDate) : maxDate;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DateTimePicker
|
||||||
|
label={label}
|
||||||
|
minDate={minDateVal}
|
||||||
|
maxDate={maxDateVal}
|
||||||
|
date={date}
|
||||||
|
showSeconds={showSeconds}
|
||||||
|
onChange={(newValue) => {
|
||||||
|
action('on change')(newValue);
|
||||||
|
setDate(newValue);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Basic.args = {
|
Basic.args = {
|
||||||
|
@ -25,9 +25,29 @@ export interface Props {
|
|||||||
label?: ReactNode;
|
label?: ReactNode;
|
||||||
/** Set the latest selectable date */
|
/** Set the latest selectable date */
|
||||||
maxDate?: Date;
|
maxDate?: Date;
|
||||||
|
/** Set the minimum selectable date */
|
||||||
|
minDate?: Date;
|
||||||
|
/** Display seconds on the time picker */
|
||||||
|
showSeconds?: boolean;
|
||||||
|
/** Set the hours that can't be selected */
|
||||||
|
disabledHours?: () => number[];
|
||||||
|
/** Set the minutes that can't be selected */
|
||||||
|
disabledMinutes?: () => number[];
|
||||||
|
/** Set the seconds that can't be selected */
|
||||||
|
disabledSeconds?: () => number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DateTimePicker = ({ date, maxDate, label, onChange }: Props) => {
|
export const DateTimePicker = ({
|
||||||
|
date,
|
||||||
|
maxDate,
|
||||||
|
minDate,
|
||||||
|
label,
|
||||||
|
onChange,
|
||||||
|
disabledHours,
|
||||||
|
disabledMinutes,
|
||||||
|
disabledSeconds,
|
||||||
|
showSeconds = true,
|
||||||
|
}: Props) => {
|
||||||
const [isOpen, setOpen] = useState(false);
|
const [isOpen, setOpen] = useState(false);
|
||||||
|
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
@ -82,6 +102,7 @@ export const DateTimePicker = ({ date, maxDate, label, onChange }: Props) => {
|
|||||||
onOpen={onOpen}
|
onOpen={onOpen}
|
||||||
label={label}
|
label={label}
|
||||||
ref={setMarkerElement}
|
ref={setMarkerElement}
|
||||||
|
showSeconds={showSeconds}
|
||||||
/>
|
/>
|
||||||
{isOpen ? (
|
{isOpen ? (
|
||||||
isFullscreen ? (
|
isFullscreen ? (
|
||||||
@ -94,8 +115,13 @@ export const DateTimePicker = ({ date, maxDate, label, onChange }: Props) => {
|
|||||||
isFullscreen={true}
|
isFullscreen={true}
|
||||||
onClose={() => setOpen(false)}
|
onClose={() => setOpen(false)}
|
||||||
maxDate={maxDate}
|
maxDate={maxDate}
|
||||||
|
minDate={minDate}
|
||||||
ref={setSelectorElement}
|
ref={setSelectorElement}
|
||||||
style={popper.styles.popper}
|
style={popper.styles.popper}
|
||||||
|
showSeconds={showSeconds}
|
||||||
|
disabledHours={disabledHours}
|
||||||
|
disabledMinutes={disabledMinutes}
|
||||||
|
disabledSeconds={disabledSeconds}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</FocusScope>
|
</FocusScope>
|
||||||
@ -108,9 +134,15 @@ export const DateTimePicker = ({ date, maxDate, label, onChange }: Props) => {
|
|||||||
<div className={styles.modal}>
|
<div className={styles.modal}>
|
||||||
<DateTimeCalendar
|
<DateTimeCalendar
|
||||||
date={date}
|
date={date}
|
||||||
|
maxDate={maxDate}
|
||||||
|
minDate={minDate}
|
||||||
onChange={onApply}
|
onChange={onApply}
|
||||||
isFullscreen={false}
|
isFullscreen={false}
|
||||||
onClose={() => setOpen(false)}
|
onClose={() => setOpen(false)}
|
||||||
|
showSeconds={showSeconds}
|
||||||
|
disabledHours={disabledHours}
|
||||||
|
disabledMinutes={disabledMinutes}
|
||||||
|
disabledSeconds={disabledSeconds}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -128,7 +160,12 @@ interface DateTimeCalendarProps {
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
isFullscreen: boolean;
|
isFullscreen: boolean;
|
||||||
maxDate?: Date;
|
maxDate?: Date;
|
||||||
|
minDate?: Date;
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
|
showSeconds?: boolean;
|
||||||
|
disabledHours?: () => number[];
|
||||||
|
disabledMinutes?: () => number[];
|
||||||
|
disabledSeconds?: () => number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface InputProps {
|
interface InputProps {
|
||||||
@ -137,6 +174,7 @@ interface InputProps {
|
|||||||
isFullscreen: boolean;
|
isFullscreen: boolean;
|
||||||
onChange: (date: DateTime) => void;
|
onChange: (date: DateTime) => void;
|
||||||
onOpen: (event: FormEvent<HTMLElement>) => void;
|
onOpen: (event: FormEvent<HTMLElement>) => void;
|
||||||
|
showSeconds?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
type InputState = {
|
type InputState = {
|
||||||
@ -145,7 +183,8 @@ type InputState = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const DateTimeInput = React.forwardRef<HTMLInputElement, InputProps>(
|
const DateTimeInput = React.forwardRef<HTMLInputElement, InputProps>(
|
||||||
({ date, label, onChange, isFullscreen, onOpen }, ref) => {
|
({ date, label, onChange, isFullscreen, onOpen, showSeconds = true }, ref) => {
|
||||||
|
const format = showSeconds ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm';
|
||||||
const [internalDate, setInternalDate] = useState<InputState>(() => {
|
const [internalDate, setInternalDate] = useState<InputState>(() => {
|
||||||
return { value: date ? dateTimeFormat(date) : dateTimeFormat(dateTime()), invalid: false };
|
return { value: date ? dateTimeFormat(date) : dateTimeFormat(dateTime()), invalid: false };
|
||||||
});
|
});
|
||||||
@ -153,11 +192,11 @@ const DateTimeInput = React.forwardRef<HTMLInputElement, InputProps>(
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (date) {
|
if (date) {
|
||||||
setInternalDate({
|
setInternalDate({
|
||||||
invalid: !isValid(dateTimeFormat(date)),
|
invalid: !isValid(dateTimeFormat(date, { format })),
|
||||||
value: isDateTime(date) ? dateTimeFormat(date) : date,
|
value: isDateTime(date) ? dateTimeFormat(date, { format }) : date,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [date]);
|
}, [date, format]);
|
||||||
|
|
||||||
const onChangeDate = useCallback((event: FormEvent<HTMLInputElement>) => {
|
const onChangeDate = useCallback((event: FormEvent<HTMLInputElement>) => {
|
||||||
const isInvalid = !isValid(event.currentTarget.value);
|
const isInvalid = !isValid(event.currentTarget.value);
|
||||||
@ -199,7 +238,22 @@ const DateTimeInput = React.forwardRef<HTMLInputElement, InputProps>(
|
|||||||
DateTimeInput.displayName = 'DateTimeInput';
|
DateTimeInput.displayName = 'DateTimeInput';
|
||||||
|
|
||||||
const DateTimeCalendar = React.forwardRef<HTMLDivElement, DateTimeCalendarProps>(
|
const DateTimeCalendar = React.forwardRef<HTMLDivElement, DateTimeCalendarProps>(
|
||||||
({ date, onClose, onChange, isFullscreen, maxDate, style }, ref) => {
|
(
|
||||||
|
{
|
||||||
|
date,
|
||||||
|
onClose,
|
||||||
|
onChange,
|
||||||
|
isFullscreen,
|
||||||
|
maxDate,
|
||||||
|
minDate,
|
||||||
|
style,
|
||||||
|
showSeconds = true,
|
||||||
|
disabledHours,
|
||||||
|
disabledMinutes,
|
||||||
|
disabledSeconds,
|
||||||
|
},
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
const calendarStyles = useStyles2(getBodyStyles);
|
const calendarStyles = useStyles2(getBodyStyles);
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
const [internalDate, setInternalDate] = useState<Date>(() => {
|
const [internalDate, setInternalDate] = useState<Date>(() => {
|
||||||
@ -243,9 +297,17 @@ const DateTimeCalendar = React.forwardRef<HTMLDivElement, DateTimeCalendarProps>
|
|||||||
className={calendarStyles.body}
|
className={calendarStyles.body}
|
||||||
tileClassName={calendarStyles.title}
|
tileClassName={calendarStyles.title}
|
||||||
maxDate={maxDate}
|
maxDate={maxDate}
|
||||||
|
minDate={minDate}
|
||||||
/>
|
/>
|
||||||
<div className={styles.time}>
|
<div className={styles.time}>
|
||||||
<TimeOfDayPicker showSeconds={true} onChange={onChangeTime} value={dateTime(internalDate)} />
|
<TimeOfDayPicker
|
||||||
|
showSeconds={showSeconds}
|
||||||
|
onChange={onChangeTime}
|
||||||
|
value={dateTime(internalDate)}
|
||||||
|
disabledHours={disabledHours}
|
||||||
|
disabledMinutes={disabledMinutes}
|
||||||
|
disabledSeconds={disabledSeconds}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<HorizontalGroup>
|
<HorizontalGroup>
|
||||||
<Button type="button" onClick={() => onChange(dateTime(internalDate))}>
|
<Button type="button" onClick={() => onChange(dateTime(internalDate))}>
|
||||||
|
@ -17,6 +17,9 @@ export interface Props {
|
|||||||
minuteStep?: number;
|
minuteStep?: number;
|
||||||
size?: FormInputSize;
|
size?: FormInputSize;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
disabledHours?: () => number[];
|
||||||
|
disabledMinutes?: () => number[];
|
||||||
|
disabledSeconds?: () => number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const POPUP_CLASS_NAME = 'time-of-day-picker-panel';
|
export const POPUP_CLASS_NAME = 'time-of-day-picker-panel';
|
||||||
@ -29,6 +32,9 @@ export const TimeOfDayPicker = ({
|
|||||||
value,
|
value,
|
||||||
size = 'auto',
|
size = 'auto',
|
||||||
disabled,
|
disabled,
|
||||||
|
disabledHours,
|
||||||
|
disabledMinutes,
|
||||||
|
disabledSeconds,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
@ -45,6 +51,9 @@ export const TimeOfDayPicker = ({
|
|||||||
minuteStep={minuteStep}
|
minuteStep={minuteStep}
|
||||||
inputIcon={<Caret wrapperStyle={styles.caretWrapper} />}
|
inputIcon={<Caret wrapperStyle={styles.caretWrapper} />}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
disabledHours={disabledHours}
|
||||||
|
disabledMinutes={disabledMinutes}
|
||||||
|
disabledSeconds={disabledSeconds}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -93,6 +102,10 @@ const getStyles = (theme: GrafanaTheme2) => {
|
|||||||
&:hover {
|
&:hover {
|
||||||
background: ${optionBgHover};
|
background: ${optionBgHover};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.rc-time-picker-panel-select-option-disabled {
|
||||||
|
color: ${theme.colors.action.disabledText};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,6 +78,10 @@ export const getBodyStyles = (theme: GrafanaTheme2) => {
|
|||||||
&:hover {
|
&:hover {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
color: ${theme.colors.action.disabledText};
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
body: css`
|
body: css`
|
||||||
z-index: ${theme.zIndex.modal};
|
z-index: ${theme.zIndex.modal};
|
||||||
|
Loading…
Reference in New Issue
Block a user