DateTimePicker: Add clearable prop (#88215)

* DateTimePicker: Add clearable prop

* Docs/story

* Separate func

* Cleanup

* Remove disabled style
This commit is contained in:
Alex Khomenko 2024-05-23 17:21:25 +02:00 committed by GitHub
parent 2aa67c7a47
commit 69108d3cac
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 53 additions and 12 deletions

View File

@ -16,7 +16,7 @@ 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
### With disabled hours, minutes or seconds
```tsx
import React, { useState } from 'react';
@ -36,6 +36,10 @@ return (
);
```
### Clearable
You can set the `clearable` prop to `true` to enable the clearing of selected values and allow the component to have an empty value instead of the current date/time.
### Props
<ArgTypes of={DateTimePicker} />

View File

@ -27,6 +27,7 @@ const meta: Meta<typeof DateTimePicker> = {
minDate: { control: 'date' },
maxDate: { control: 'date' },
showSeconds: { control: 'boolean' },
clearable: { control: 'boolean' },
},
args: {
minDate: minimumDate,
@ -63,7 +64,7 @@ export const OnlyWorkingHoursEnabled: StoryFn<typeof DateTimePicker> = ({ label,
);
};
export const Basic: StoryFn<typeof DateTimePicker> = ({ label, minDate, maxDate, showSeconds }) => {
export const Basic: StoryFn<typeof DateTimePicker> = ({ label, minDate, maxDate, showSeconds, clearable }) => {
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.
@ -77,6 +78,7 @@ export const Basic: StoryFn<typeof DateTimePicker> = ({ label, minDate, maxDate,
maxDate={maxDateVal}
date={date}
showSeconds={showSeconds}
clearable={clearable}
onChange={(newValue) => {
action('on change')(newValue);
setDate(newValue);
@ -89,4 +91,24 @@ Basic.args = {
label: 'Date',
};
export const Clearable: StoryFn<typeof DateTimePicker> = ({ label, showSeconds, clearable }) => {
const [date, setDate] = useState<DateTime>(dateTime(today));
return (
<DateTimePicker
label={label}
date={date}
showSeconds={showSeconds}
clearable={clearable}
onChange={(newValue) => {
action('on change')(newValue);
setDate(newValue);
}}
/>
);
};
Clearable.args = {
clearable: true,
};
export default meta;

View File

@ -50,6 +50,8 @@ export interface Props {
disabledMinutes?: () => number[];
/** Set the seconds that can't be selected */
disabledSeconds?: () => number[];
/** Can input be cleared/have empty values */
clearable?: boolean;
}
export const DateTimePicker = ({
@ -62,6 +64,7 @@ export const DateTimePicker = ({
disabledMinutes,
disabledSeconds,
showSeconds = true,
clearable = false,
}: Props) => {
const [isOpen, setOpen] = useState(false);
@ -131,6 +134,7 @@ export const DateTimePicker = ({
label={label}
ref={refs.setReference}
showSeconds={showSeconds}
clearable={clearable}
/>
{isOpen ? (
isFullscreen ? (
@ -203,6 +207,7 @@ interface InputProps {
onChange: (date: DateTime) => void;
onOpen: (event: FormEvent<HTMLElement>) => void;
showSeconds?: boolean;
clearable?: boolean;
}
type InputState = {
@ -211,10 +216,11 @@ type InputState = {
};
const DateTimeInput = React.forwardRef<HTMLInputElement, InputProps>(
({ date, label, onChange, onOpen, showSeconds = true }, ref) => {
({ date, label, onChange, onOpen, showSeconds = true, clearable = false }, ref) => {
const styles = useStyles2(getStyles);
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 };
return { value: date ? dateTimeFormat(date) : !clearable ? dateTimeFormat(dateTime()) : '', invalid: false };
});
useEffect(() => {
@ -235,21 +241,19 @@ const DateTimeInput = React.forwardRef<HTMLInputElement, InputProps>(
}, []);
const onBlur = useCallback(() => {
if (!internalDate.invalid) {
if (!internalDate.invalid && internalDate.value) {
const date = dateTimeForTimeZone(getTimeZone(), internalDate.value);
onChange(date);
}
}, [internalDate, onChange]);
const clearInternalDate = useCallback(() => {
setInternalDate({ value: '', invalid: false });
}, []);
const icon = <Button aria-label="Time picker" icon="calendar-alt" variant="secondary" onClick={onOpen} />;
return (
<InlineField
label={label}
invalid={!!(internalDate.value && internalDate.invalid)}
className={css({
marginBottom: 0,
})}
>
<InlineField label={label} invalid={!!(internalDate.value && internalDate.invalid)} className={styles.field}>
<Input
onChange={onChangeDate}
addonAfter={icon}
@ -258,6 +262,10 @@ const DateTimeInput = React.forwardRef<HTMLInputElement, InputProps>(
data-testid={Components.DateTimePicker.input}
placeholder="Select date/time"
ref={ref}
suffix={
clearable &&
internalDate.value && <Icon name="times" className={styles.clearIcon} onClick={clearInternalDate} />
}
/>
</InlineField>
);
@ -389,4 +397,11 @@ const getStyles = (theme: GrafanaTheme2) => ({
zIndex: theme.zIndex.modal,
maxWidth: '280px',
}),
clearIcon: css({
cursor: 'pointer',
}),
field: css({
marginBottom: 0,
width: '100%',
}),
});