Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update date components #405

Merged
merged 8 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Change Log

# Unreleased

### Added

- New additons to Date components #405 by @AnnMarieW
- `DatePicker` - The `debounce` prop can now be `True` `False` or `number` of ms delay before updating. When True, the value updates on blur.
- Added missing `highlightToday` prop on all Date components with calendars.
- `DateInput` updates properly now when clearable=True


# 0.14.7

### Added
Expand Down
41 changes: 27 additions & 14 deletions src/ts/components/dates/DateInput.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CalendarLevel, DateInput as MantineDateInput } from "@mantine/dates";
import { useDebouncedValue, useDidUpdate } from "@mantine/hooks";
import { useDebouncedValue, useDidUpdate, useFocusWithin } from "@mantine/hooks";
import { BoxProps } from "props/box";
import { DashBaseProps, PersistenceProps, DebounceProps } from "props/dash";
import {
Expand Down Expand Up @@ -50,6 +50,8 @@ interface Props
level?: CalendarLevel;
/** Specifies days that should be disabled */
disabledDates?: string[];
/** Determines whether today should be highlighted with a border, false by default */
highlightToday?: boolean;
}

/** DateInput */
Expand All @@ -75,12 +77,21 @@ const DateInput = (props: Props) => {
const debounceValue = typeof debounce === 'number' ? debounce : 0;
const [debounced] = useDebouncedValue(date, debounceValue);

const { ref, focused } = useFocusWithin();

useDidUpdate(() => {
if (typeof debounce === 'number' || debounce === false) {
setProps({ value: dateToString(date) });
}

// Ensure the value prop is updated when the date is cleared by clicking the "X" button,
// even if the input does not have focus.
if (!focused && debounce === true) {
setProps({ value: dateToString(date)})
}
}, [debounced]);


useDidUpdate(() => {
setDate(stringToDate(value));
}, [value]);
Expand All @@ -107,19 +118,21 @@ const DateInput = (props: Props) => {
};

return (
<MantineDateInput
data-dash-is-loading={
(loading_state && loading_state.is_loading) || undefined
}
onBlur={handleBlur}
onKeyDown={handleKeyDown}
onChange={setDate}
value={date}
minDate={stringToDate(minDate)}
maxDate={stringToDate(maxDate)}
excludeDate={isExcluded}
{...others}
/>
<div ref={ref}>
<MantineDateInput
data-dash-is-loading={
(loading_state && loading_state.is_loading) || undefined
}
onBlur={handleBlur}
onKeyDown={handleKeyDown}
onChange={setDate}
value={date}
minDate={stringToDate(minDate)}
maxDate={stringToDate(maxDate)}
excludeDate={isExcluded}
{...others}
/>
</div>
);
};

Expand Down
104 changes: 64 additions & 40 deletions src/ts/components/dates/DatePicker.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
import { DatePickerInput } from "@mantine/dates";
import { useDebouncedValue, useDidUpdate } from "@mantine/hooks";
import { BoxProps } from "props/box";
import { DashBaseProps, PersistenceProps } from "props/dash";
import { DateInputSharedProps, DatePickerBaseProps } from "props/dates";
import { StylesApiProps } from "props/styles";
import React, { useState } from "react";
import { DatePickerInput } from '@mantine/dates';
import { useDebouncedValue, useDidUpdate, useFocusWithin } from '@mantine/hooks';
import { BoxProps } from 'props/box';
import { DashBaseProps, PersistenceProps } from 'props/dash';
import { DateInputSharedProps, DatePickerBaseProps } from 'props/dates';
import { StylesApiProps } from 'props/styles';
import React, { useState } from 'react';
import {
isDisabled,
stringToDate,
toDates,
toStrings,
} from "../../utils/dates";
} from '../../utils/dates';

interface Props
extends DashBaseProps,
PersistenceProps,
BoxProps,
DateInputSharedProps,
DatePickerBaseProps,
StylesApiProps {
/** Dayjs format to display input value, "MMMM D, YYYY" by default */
interface Props extends DashBaseProps, PersistenceProps, BoxProps, DateInputSharedProps, DatePickerBaseProps, StylesApiProps {
/** Dayjs format to display input value, "MMMM D, YYYY" by default */
valueFormat?: string;
/** Specifies days that should be disabled */
disabledDates?: string[];
/** Determines whether today should be highlighted with a border, false by default */
highlightToday?: boolean;
/** An integer that represents the number of times that this element has been submitted */
n_submit?: number;
/** Debounce time in ms */
debounce?: number;
/**
* (boolean | number; default False): If True, changes to input will be sent back to the Dash server only on enter or when losing focus.
* If it's False, it will send the value back on every change. If a number, it will not send anything back to the Dash server until
* the user has stopped typing for that number of milliseconds.
*/
debounce?: boolean | number;
}

/** DatePicker */
Expand All @@ -35,6 +35,7 @@ const DatePicker = (props: Props) => {
setProps,
loading_state,
n_submit,
type,
value,
debounce,
minDate,
Expand All @@ -47,48 +48,71 @@ const DatePicker = (props: Props) => {
} = props;

const [date, setDate] = useState(toDates(value));
const [debounced] = useDebouncedValue(date, debounce);

const debounceValue = typeof debounce === 'number' ? debounce : 0;
const [debounced] = useDebouncedValue(date, debounceValue);
const { ref, focused } = useFocusWithin();

useDidUpdate(() => {
setProps({ value: toStrings(date) });
if (typeof debounce === 'number' || debounce === false) {
setProps({ value: toStrings(date) });
}
}, [debounced]);

useDidUpdate(() => {
setDate(toDates(value));
// Clears value when X is clicked
if (focused) {
setProps({ value: toStrings(date) });
}
}, [date]);

useDidUpdate(() => {
// If type is multiple or range, sets default value to a list
setDate(type !== 'default' && !value ? [] : toDates(value));
}, [value]);

const handleKeyDown = (ev) => {
if (ev.key === "Enter") {
const handleKeyDown = (ev: React.KeyboardEvent) => {
// Enter key opens calendar, so don't call setProps
if (ev.key === 'Enter') {
setProps({ n_submit: n_submit + 1 });
}
};

const isExcluded = (date: Date) => {
return isDisabled(date, disabledDates || []);
const handleBlur = () => {
// Don't include n_blur counter because onBlur is called when the calendar is opened
if (debounce === true) {
setProps({ value: toStrings(date) });
}
};

const isExcluded = (date: Date) => isDisabled(date, disabledDates || []);

return (
<DatePickerInput
data-dash-is-loading={
(loading_state && loading_state.is_loading) || undefined
}
wrapperProps={{ autoComplete: "off" }}
onKeyDown={handleKeyDown}
onChange={setDate}
value={date}
minDate={stringToDate(minDate)}
maxDate={stringToDate(maxDate)}
excludeDate={isExcluded}
{...others}
/>
<div ref={ref}>
<DatePickerInput
data-dash-is-loading={
(loading_state && loading_state.is_loading) || undefined
}
onBlur={handleBlur}
onKeyDown={handleKeyDown}
onChange={setDate}
value={date}
type={type}
minDate={stringToDate(minDate)}
maxDate={stringToDate(maxDate)}
excludeDate={isExcluded}
{...others}
/>
</div>
);
};

DatePicker.defaultProps = {
persisted_props: ["value"],
persistence_type: "local",
persisted_props: ['value'],
persistence_type: 'local',
debounce: 0,
n_submit: 0,
type: 'default',
};

export default DatePicker;
2 changes: 2 additions & 0 deletions src/ts/components/dates/DateTimePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ interface Props
n_submit?: number;
/** Debounce time in ms */
debounce?: number;
/** Determines whether today should be highlighted with a border, false by default */
highlightToday?: boolean;
}

/** DateTimePicker */
Expand Down
Loading
Loading