diff --git a/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.mdx b/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.mdx index 90ecbc0f84a..1302320f7a2 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.mdx +++ b/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.mdx @@ -16,6 +16,26 @@ const [date, setDate] = useState(dateTime('2021-05-05 12:00:00')); return ; ``` +### 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('2021-05-05 12:00:00')); +return ( + [0, 1, 2]} + disabledMinutes={() => [10, 15, 30]} + disabledSeconds={() => [5, 10, 15, 20]} + /> +); +``` + ### Props diff --git a/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.story.tsx b/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.story.tsx index 54ff4dcb5d4..197eff0c72c 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.story.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.story.tsx @@ -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 = { title: 'Pickers and Editors/TimePickers/DateTimePicker', decorators: [withCenteredStory], @@ -19,6 +27,14 @@ const meta: ComponentMeta = { 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 = { }, }; -export const Basic: ComponentStory = ({ label }) => { - const [date, setDate] = useState(dateTime('2021-05-05 12:00:00')); - return ; +export const OnlyWorkingHoursEnabled: ComponentStory = ({ + label, + minDate, + maxDate, + showSeconds, +}) => { + const [date, setDate] = useState(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 ( + [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 = ({ label, minDate, maxDate, showSeconds }) => { + const [date, setDate] = useState(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 ( + { + action('on change')(newValue); + setDate(newValue); + }} + /> + ); }; Basic.args = { diff --git a/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx b/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx index d16b1542619..87f58caa0cd 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx @@ -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(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} /> @@ -108,9 +134,15 @@ export const DateTimePicker = ({ date, maxDate, label, onChange }: Props) => {
setOpen(false)} + showSeconds={showSeconds} + disabledHours={disabledHours} + disabledMinutes={disabledMinutes} + disabledSeconds={disabledSeconds} />
@@ -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) => void; + showSeconds?: boolean; } type InputState = { @@ -145,7 +183,8 @@ type InputState = { }; const DateTimeInput = React.forwardRef( - ({ 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(() => { return { value: date ? dateTimeFormat(date) : dateTimeFormat(dateTime()), invalid: false }; }); @@ -153,11 +192,11 @@ const DateTimeInput = React.forwardRef( 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) => { const isInvalid = !isValid(event.currentTarget.value); @@ -199,7 +238,22 @@ const DateTimeInput = React.forwardRef( DateTimeInput.displayName = 'DateTimeInput'; const DateTimeCalendar = React.forwardRef( - ({ 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(() => { @@ -243,9 +297,17 @@ const DateTimeCalendar = React.forwardRef className={calendarStyles.body} tileClassName={calendarStyles.title} maxDate={maxDate} + minDate={minDate} />
- +