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:
@ -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(
|
||||
|
||||
@ -91,8 +91,6 @@ export const FieldInput = ({
|
||||
onEnter={onEnter}
|
||||
onEscape={onEscape}
|
||||
onClickOutside={onClickOutside}
|
||||
onTab={onTab}
|
||||
onShiftTab={onShiftTab}
|
||||
/>
|
||||
) : isFieldNumber(fieldDefinition) ? (
|
||||
<NumberFieldInput
|
||||
|
||||
@ -27,6 +27,7 @@ export type GenericFieldContextType = {
|
||||
hotkeyScope: string;
|
||||
isLabelIdentifier: boolean;
|
||||
basePathToShowPage?: string;
|
||||
clearable?: boolean;
|
||||
};
|
||||
|
||||
export const FieldContext = createContext<GenericFieldContextType>(
|
||||
|
||||
@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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,
|
||||
});
|
||||
|
||||
|
||||
@ -24,6 +24,7 @@ export {
|
||||
IconBuildingSkyscraper,
|
||||
IconCalendar,
|
||||
IconCalendarEvent,
|
||||
IconCalendarX,
|
||||
IconCheck,
|
||||
IconCheckbox,
|
||||
IconChevronDown,
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user