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} />;
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
<ArgsTable of={DateTimePicker} />
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
@ -8,6 +9,13 @@ import { withCenteredStory } from '../../../utils/storybook/withCenteredStory';
|
||||
import { DateTimePicker } from './DateTimePicker';
|
||||
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> = {
|
||||
title: 'Pickers and Editors/TimePickers/DateTimePicker',
|
||||
decorators: [withCenteredStory],
|
||||
@ -19,6 +27,14 @@ const meta: ComponentMeta<typeof DateTimePicker> = {
|
||||
onChange: {
|
||||
table: { disable: true },
|
||||
},
|
||||
minDate: { control: 'date' },
|
||||
maxDate: { control: 'date' },
|
||||
showSeconds: { control: 'boolean' },
|
||||
},
|
||||
args: {
|
||||
minDate: minimumDate,
|
||||
maxDate: today,
|
||||
showSeconds: true,
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
@ -27,9 +43,54 @@ const meta: ComponentMeta<typeof DateTimePicker> = {
|
||||
},
|
||||
};
|
||||
|
||||
export const Basic: ComponentStory<typeof DateTimePicker> = ({ label }) => {
|
||||
const [date, setDate] = useState<DateTime>(dateTime('2021-05-05 12:00:00'));
|
||||
return <DateTimePicker label={label} date={date} onChange={setDate} />;
|
||||
export const OnlyWorkingHoursEnabled: 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}
|
||||
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 = {
|
||||
|
@ -25,9 +25,29 @@ export interface Props {
|
||||
label?: ReactNode;
|
||||
/** Set the latest selectable 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 ref = useRef<HTMLDivElement>(null);
|
||||
@ -82,6 +102,7 @@ export const DateTimePicker = ({ date, maxDate, label, onChange }: Props) => {
|
||||
onOpen={onOpen}
|
||||
label={label}
|
||||
ref={setMarkerElement}
|
||||
showSeconds={showSeconds}
|
||||
/>
|
||||
{isOpen ? (
|
||||
isFullscreen ? (
|
||||
@ -94,8 +115,13 @@ export const DateTimePicker = ({ date, maxDate, label, onChange }: Props) => {
|
||||
isFullscreen={true}
|
||||
onClose={() => setOpen(false)}
|
||||
maxDate={maxDate}
|
||||
minDate={minDate}
|
||||
ref={setSelectorElement}
|
||||
style={popper.styles.popper}
|
||||
showSeconds={showSeconds}
|
||||
disabledHours={disabledHours}
|
||||
disabledMinutes={disabledMinutes}
|
||||
disabledSeconds={disabledSeconds}
|
||||
/>
|
||||
</div>
|
||||
</FocusScope>
|
||||
@ -108,9 +134,15 @@ export const DateTimePicker = ({ date, maxDate, label, onChange }: Props) => {
|
||||
<div className={styles.modal}>
|
||||
<DateTimeCalendar
|
||||
date={date}
|
||||
maxDate={maxDate}
|
||||
minDate={minDate}
|
||||
onChange={onApply}
|
||||
isFullscreen={false}
|
||||
onClose={() => setOpen(false)}
|
||||
showSeconds={showSeconds}
|
||||
disabledHours={disabledHours}
|
||||
disabledMinutes={disabledMinutes}
|
||||
disabledSeconds={disabledSeconds}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -128,7 +160,12 @@ interface DateTimeCalendarProps {
|
||||
onClose: () => void;
|
||||
isFullscreen: boolean;
|
||||
maxDate?: Date;
|
||||
minDate?: Date;
|
||||
style?: React.CSSProperties;
|
||||
showSeconds?: boolean;
|
||||
disabledHours?: () => number[];
|
||||
disabledMinutes?: () => number[];
|
||||
disabledSeconds?: () => number[];
|
||||
}
|
||||
|
||||
interface InputProps {
|
||||
@ -137,6 +174,7 @@ interface InputProps {
|
||||
isFullscreen: boolean;
|
||||
onChange: (date: DateTime) => void;
|
||||
onOpen: (event: FormEvent<HTMLElement>) => void;
|
||||
showSeconds?: boolean;
|
||||
}
|
||||
|
||||
type InputState = {
|
||||
@ -145,7 +183,8 @@ type InputState = {
|
||||
};
|
||||
|
||||
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>(() => {
|
||||
return { value: date ? dateTimeFormat(date) : dateTimeFormat(dateTime()), invalid: false };
|
||||
});
|
||||
@ -153,11 +192,11 @@ const DateTimeInput = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
useEffect(() => {
|
||||
if (date) {
|
||||
setInternalDate({
|
||||
invalid: !isValid(dateTimeFormat(date)),
|
||||
value: isDateTime(date) ? dateTimeFormat(date) : date,
|
||||
invalid: !isValid(dateTimeFormat(date, { format })),
|
||||
value: isDateTime(date) ? dateTimeFormat(date, { format }) : date,
|
||||
});
|
||||
}
|
||||
}, [date]);
|
||||
}, [date, format]);
|
||||
|
||||
const onChangeDate = useCallback((event: FormEvent<HTMLInputElement>) => {
|
||||
const isInvalid = !isValid(event.currentTarget.value);
|
||||
@ -199,7 +238,22 @@ const DateTimeInput = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
DateTimeInput.displayName = 'DateTimeInput';
|
||||
|
||||
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 styles = useStyles2(getStyles);
|
||||
const [internalDate, setInternalDate] = useState<Date>(() => {
|
||||
@ -243,9 +297,17 @@ const DateTimeCalendar = React.forwardRef<HTMLDivElement, DateTimeCalendarProps>
|
||||
className={calendarStyles.body}
|
||||
tileClassName={calendarStyles.title}
|
||||
maxDate={maxDate}
|
||||
minDate={minDate}
|
||||
/>
|
||||
<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>
|
||||
<HorizontalGroup>
|
||||
<Button type="button" onClick={() => onChange(dateTime(internalDate))}>
|
||||
|
@ -17,6 +17,9 @@ export interface Props {
|
||||
minuteStep?: number;
|
||||
size?: FormInputSize;
|
||||
disabled?: boolean;
|
||||
disabledHours?: () => number[];
|
||||
disabledMinutes?: () => number[];
|
||||
disabledSeconds?: () => number[];
|
||||
}
|
||||
|
||||
export const POPUP_CLASS_NAME = 'time-of-day-picker-panel';
|
||||
@ -29,6 +32,9 @@ export const TimeOfDayPicker = ({
|
||||
value,
|
||||
size = 'auto',
|
||||
disabled,
|
||||
disabledHours,
|
||||
disabledMinutes,
|
||||
disabledSeconds,
|
||||
}: Props) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
@ -45,6 +51,9 @@ export const TimeOfDayPicker = ({
|
||||
minuteStep={minuteStep}
|
||||
inputIcon={<Caret wrapperStyle={styles.caretWrapper} />}
|
||||
disabled={disabled}
|
||||
disabledHours={disabledHours}
|
||||
disabledMinutes={disabledMinutes}
|
||||
disabledSeconds={disabledSeconds}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -93,6 +102,10 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
&:hover {
|
||||
background: ${optionBgHover};
|
||||
}
|
||||
|
||||
&.rc-time-picker-panel-select-option-disabled {
|
||||
color: ${theme.colors.action.disabledText};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,6 +78,10 @@ export const getBodyStyles = (theme: GrafanaTheme2) => {
|
||||
&:hover {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
color: ${theme.colors.action.disabledText};
|
||||
}
|
||||
`,
|
||||
body: css`
|
||||
z-index: ${theme.zIndex.modal};
|
||||
|
Loading…
Reference in New Issue
Block a user