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

View File

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

View File

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

View File

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

View File

@ -10,8 +10,6 @@ export type DateFieldInputProps = {
onClickOutside?: FieldInputEvent; onClickOutside?: FieldInputEvent;
onEnter?: FieldInputEvent; onEnter?: FieldInputEvent;
onEscape?: FieldInputEvent; onEscape?: FieldInputEvent;
onTab?: FieldInputEvent;
onShiftTab?: FieldInputEvent;
}; };
export const DateFieldInput = ({ export const DateFieldInput = ({
@ -19,13 +17,13 @@ export const DateFieldInput = ({
onEscape, onEscape,
onClickOutside, onClickOutside,
}: DateFieldInputProps) => { }: DateFieldInputProps) => {
const { fieldValue, hotkeyScope } = useDateTimeField(); const { fieldValue, hotkeyScope, clearable } = useDateTimeField();
const persistField = usePersistField(); const persistField = usePersistField();
const persistDate = (newDate: Nullable<Date>) => { const persistDate = (newDate: Nullable<Date>) => {
if (!newDate) { if (!newDate) {
persistField(''); persistField(null);
} else { } else {
const newDateISO = newDate?.toISOString(); const newDateISO = newDate?.toISOString();
@ -57,6 +55,7 @@ export const DateFieldInput = ({
onEnter={handleEnter} onEnter={handleEnter}
onEscape={handleEscape} onEscape={handleEscape}
value={dateValue} 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'; import { FieldDateTimeValue } from '../FieldMetadata';
@ -6,4 +6,5 @@ import { FieldDateTimeValue } from '../FieldMetadata';
export const isFieldDateTimeValue = ( export const isFieldDateTimeValue = (
fieldValue: unknown, fieldValue: unknown,
): fieldValue is FieldDateTimeValue => ): 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, fieldMetadataName,
objectRecordId, objectRecordId,
fieldPosition, fieldPosition,
clearable,
}: { }: {
objectNameSingular: string; objectNameSingular: string;
objectRecordId: string; objectRecordId: string;
fieldMetadataName: string; fieldMetadataName: string;
fieldPosition: number; fieldPosition: number;
clearable?: boolean;
}) => { }) => {
const { objectMetadataItem } = useObjectMetadataItem({ const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular, objectNameSingular,
@ -60,6 +62,7 @@ export const useFieldContext = ({
}), }),
useUpdateRecord: useUpdateOneObjectMutation, useUpdateRecord: useUpdateOneObjectMutation,
hotkeyScope: InlineCellHotkeyScope.InlineCell, hotkeyScope: InlineCellHotkeyScope.InlineCell,
clearable,
}} }}
> >
{children} {children}

View File

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

View File

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

View File

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

View File

@ -2,6 +2,8 @@ import React from 'react';
import ReactDatePicker from 'react-datepicker'; import ReactDatePicker from 'react-datepicker';
import styled from '@emotion/styled'; 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 { overlayBackground } from '@/ui/theme/constants/effects';
import 'react-datepicker/dist/react-datepicker.css'; import 'react-datepicker/dist/react-datepicker.css';
@ -218,36 +220,76 @@ const StyledContainer = styled.div`
& .react-datepicker__day:hover { & .react-datepicker__day:hover {
color: ${({ theme }) => theme.font.color.tertiary}; 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 = { export type InternalDatePickerProps = {
date: Date; date: Date | null;
onMouseSelect?: (date: Date) => void; onMouseSelect?: (date: Date | null) => void;
onChange?: (date: Date) => void; onChange?: (date: Date) => void;
clearable?: boolean;
}; };
export const InternalDatePicker = ({ export const InternalDatePicker = ({
date, date,
onChange, onChange,
onMouseSelect, onMouseSelect,
}: InternalDatePickerProps) => ( clearable = true,
<StyledContainer> }: InternalDatePickerProps) => {
<ReactDatePicker const handleClear = () => {
open={true} onMouseSelect?.(null);
selected={date} };
showMonthDropdown
showYearDropdown return (
onChange={() => { <StyledContainer>
// We need to use onSelect here but onChange is almost redundant with onSelect but is required <div className={clearable ? 'clearable ' : ''}>
}} <ReactDatePicker
customInput={<></>} open={true}
onSelect={(date: Date, event) => { selected={date}
if (event?.type === 'click') { showMonthDropdown
onMouseSelect?.(date); showYearDropdown
} else { onChange={() => {
onChange?.(date); // We need to use onSelect here but onChange is almost redundant with onSelect but is require
} }}
}} customInput={<></>}
/> onSelect={(date: Date, event) => {
</StyledContainer> 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>
);
};