Added a clear/reset button in InternalDateInput to reset/unschedule events (#3154)

* Added a clear/reset button in InternalDateInput to reset/unschedule events

* Added clearable prop to <InternalDateInput /> and fixed some design mistakes

* Removed unnecessary code that was used during debugging

* Replaced button with <MenuItem /> component

* Fixed null date in ObjectFilterDropdownDateSearchInput

* Moved clear context call from DateInput to DateFieldInput

* Removed useless props

---------

Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
Atharv Parlikar
2023-12-29 19:45:08 +05:30
committed by GitHub
parent fd607789f4
commit 97f83b55b0
11 changed files with 89 additions and 36 deletions

View File

@ -90,6 +90,7 @@ export const ActivityEditor = ({
objectRecordId: activity.id,
fieldMetadataName: 'dueAt',
fieldPosition: 0,
clearable: true,
});
const { FieldContextProvider: AssigneeFieldContextProvider } =
@ -98,6 +99,7 @@ export const ActivityEditor = ({
objectRecordId: activity.id,
fieldMetadataName: 'assignee',
fieldPosition: 1,
clearable: true,
});
const updateTitle = useCallback(

View File

@ -91,8 +91,6 @@ export const FieldInput = ({
onEnter={onEnter}
onEscape={onEscape}
onClickOutside={onClickOutside}
onTab={onTab}
onShiftTab={onShiftTab}
/>
) : isFieldNumber(fieldDefinition) ? (
<NumberFieldInput

View File

@ -27,6 +27,7 @@ export type GenericFieldContextType = {
hotkeyScope: string;
isLabelIdentifier: boolean;
basePathToShowPage?: string;
clearable?: boolean;
};
export const FieldContext = createContext<GenericFieldContextType>(

View File

@ -7,7 +7,8 @@ import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata';
import { isFieldDateTime } from '../../types/guards/isFieldDateTime';
export const useDateTimeField = () => {
const { entityId, fieldDefinition, hotkeyScope } = useContext(FieldContext);
const { entityId, fieldDefinition, hotkeyScope, clearable } =
useContext(FieldContext);
assertFieldMetadata('DATE_TIME', isFieldDateTime, fieldDefinition);
@ -25,5 +26,6 @@ export const useDateTimeField = () => {
fieldValue,
setFieldValue,
hotkeyScope,
clearable,
};
};

View File

@ -10,8 +10,6 @@ export type DateFieldInputProps = {
onClickOutside?: FieldInputEvent;
onEnter?: FieldInputEvent;
onEscape?: FieldInputEvent;
onTab?: FieldInputEvent;
onShiftTab?: FieldInputEvent;
};
export const DateFieldInput = ({
@ -19,13 +17,13 @@ export const DateFieldInput = ({
onEscape,
onClickOutside,
}: DateFieldInputProps) => {
const { fieldValue, hotkeyScope } = useDateTimeField();
const { fieldValue, hotkeyScope, clearable } = useDateTimeField();
const persistField = usePersistField();
const persistDate = (newDate: Nullable<Date>) => {
if (!newDate) {
persistField('');
persistField(null);
} else {
const newDateISO = newDate?.toISOString();
@ -57,6 +55,7 @@ export const DateFieldInput = ({
onEnter={handleEnter}
onEscape={handleEscape}
value={dateValue}
clearable={clearable}
/>
);
};

View File

@ -1,4 +1,4 @@
import { isString } from '@sniptt/guards';
import { isNull, isString } from '@sniptt/guards';
import { FieldDateTimeValue } from '../FieldMetadata';
@ -6,4 +6,5 @@ import { FieldDateTimeValue } from '../FieldMetadata';
export const isFieldDateTimeValue = (
fieldValue: unknown,
): fieldValue is FieldDateTimeValue =>
isString(fieldValue) && !isNaN(Date.parse(fieldValue));
(isString(fieldValue) && !isNaN(Date.parse(fieldValue))) ||
isNull(fieldValue);

View File

@ -15,11 +15,13 @@ export const useFieldContext = ({
fieldMetadataName,
objectRecordId,
fieldPosition,
clearable,
}: {
objectNameSingular: string;
objectRecordId: string;
fieldMetadataName: string;
fieldPosition: number;
clearable?: boolean;
}) => {
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
@ -60,6 +62,7 @@ export const useFieldContext = ({
}),
useUpdateRecord: useUpdateOneObjectMutation,
hotkeyScope: InlineCellHotkeyScope.InlineCell,
clearable,
}}
>
{children}

View File

@ -1,5 +1,6 @@
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
import { InternalDatePicker } from '@/ui/input/components/internal/date/components/InternalDatePicker';
import { isDefined } from '~/utils/isDefined';
export const ObjectFilterDropdownDateSearchInput = () => {
const {
@ -9,14 +10,14 @@ export const ObjectFilterDropdownDateSearchInput = () => {
selectFilter,
} = useFilterDropdown();
const handleChange = (date: Date) => {
const handleChange = (date: Date | null) => {
if (!filterDefinitionUsedInDropdown || !selectedOperandInDropdown) return;
selectFilter?.({
fieldMetadataId: filterDefinitionUsedInDropdown.fieldMetadataId,
value: date.toISOString(),
value: isDefined(date) ? date.toISOString() : '',
operand: selectedOperandInDropdown,
displayValue: date.toLocaleDateString(),
displayValue: isDefined(date) ? date.toLocaleString() : '',
definition: filterDefinitionUsedInDropdown,
});

View File

@ -24,6 +24,7 @@ export {
IconBuildingSkyscraper,
IconCalendar,
IconCalendarEvent,
IconCalendarX,
IconCheck,
IconCheckbox,
IconChevronDown,

View File

@ -37,6 +37,7 @@ export type DateInputProps = {
newDate: Nullable<Date>,
) => void;
hotkeyScope: string;
clearable?: boolean;
};
export const DateInput = ({
@ -45,6 +46,7 @@ export const DateInput = ({
onEnter,
onEscape,
onClickOutside,
clearable,
}: DateInputProps) => {
const theme = useTheme();
@ -91,9 +93,10 @@ export const DateInput = ({
<InternalDatePicker
date={internalValue ?? new Date()}
onChange={handleChange}
onMouseSelect={(newDate: Date) => {
onMouseSelect={(newDate: Date | null) => {
onEnter(newDate);
}}
clearable={clearable ? clearable : false}
/>
</StyledCalendarContainer>
</div>

View File

@ -2,6 +2,8 @@ import React from 'react';
import ReactDatePicker from 'react-datepicker';
import styled from '@emotion/styled';
import { IconCalendarX } from '@/ui/display/icon';
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { overlayBackground } from '@/ui/theme/constants/effects';
import 'react-datepicker/dist/react-datepicker.css';
@ -218,36 +220,76 @@ const StyledContainer = styled.div`
& .react-datepicker__day:hover {
color: ${({ theme }) => theme.font.color.tertiary};
}
& .clearable {
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
}
`;
const StyledButtonContainer = styled.div`
align-items: center;
align-self: stretch;
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
padding-left: ${({ theme }) => theme.spacing(2)};
& .menu-item {
height: ${({ theme }) => theme.spacing(8)};
margin-bottom: ${({ theme }) => theme.spacing(1)};
margin-top: ${({ theme }) => theme.spacing(1)};
padding: 0 ${({ theme }) => theme.spacing(2)};
width: fit-content;
}
`;
export type InternalDatePickerProps = {
date: Date;
onMouseSelect?: (date: Date) => void;
date: Date | null;
onMouseSelect?: (date: Date | null) => void;
onChange?: (date: Date) => void;
clearable?: boolean;
};
export const InternalDatePicker = ({
date,
onChange,
onMouseSelect,
}: InternalDatePickerProps) => (
<StyledContainer>
<ReactDatePicker
open={true}
selected={date}
showMonthDropdown
showYearDropdown
onChange={() => {
// We need to use onSelect here but onChange is almost redundant with onSelect but is required
}}
customInput={<></>}
onSelect={(date: Date, event) => {
if (event?.type === 'click') {
onMouseSelect?.(date);
} else {
onChange?.(date);
}
}}
/>
</StyledContainer>
);
clearable = true,
}: InternalDatePickerProps) => {
const handleClear = () => {
onMouseSelect?.(null);
};
return (
<StyledContainer>
<div className={clearable ? 'clearable ' : ''}>
<ReactDatePicker
open={true}
selected={date}
showMonthDropdown
showYearDropdown
onChange={() => {
// We need to use onSelect here but onChange is almost redundant with onSelect but is require
}}
customInput={<></>}
onSelect={(date: Date, event) => {
if (event?.type === 'click') {
onMouseSelect?.(date);
} else {
onChange?.(date);
}
}}
></ReactDatePicker>
</div>
{clearable && (
<StyledButtonContainer>
<MenuItem
text="Clear"
LeftIcon={IconCalendarX}
onClick={handleClear}
className="menu-item"
/>
</StyledButtonContainer>
)}
</StyledContainer>
);
};