diff --git a/front/src/modules/activities/components/ActivityEditor.tsx b/front/src/modules/activities/components/ActivityEditor.tsx
index 326233c72..ffc17e2b6 100644
--- a/front/src/modules/activities/components/ActivityEditor.tsx
+++ b/front/src/modules/activities/components/ActivityEditor.tsx
@@ -8,6 +8,7 @@ import { ActivityComments } from '@/activities/components/ActivityComments';
import { ActivityTypeDropdown } from '@/activities/components/ActivityTypeDropdown';
import { GET_ACTIVITIES } from '@/activities/graphql/queries/getActivities';
import { PropertyBox } from '@/ui/editable-field/property-box/components/PropertyBox';
+import { EditableFieldHotkeyScope } from '@/ui/editable-field/types/EditableFieldHotkeyScope';
import { DateEditableField } from '@/ui/editable-field/variants/components/DateEditableField';
import { IconCalendar } from '@/ui/icon/index';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
@@ -201,6 +202,7 @@ export function ActivityEditor({
refetchQueries: [getOperationName(GET_ACTIVITIES) ?? ''],
});
}}
+ hotkeyScope={EditableFieldHotkeyScope.EditableField}
/>
>
diff --git a/front/src/modules/ui/input/components/DateInputDisplay.tsx b/front/src/modules/ui/content-display/components/DateDisplay.tsx
similarity index 60%
rename from front/src/modules/ui/input/components/DateInputDisplay.tsx
rename to front/src/modules/ui/content-display/components/DateDisplay.tsx
index 0f200a439..fb83ccdf2 100644
--- a/front/src/modules/ui/input/components/DateInputDisplay.tsx
+++ b/front/src/modules/ui/content-display/components/DateDisplay.tsx
@@ -1,9 +1,9 @@
import { formatToHumanReadableDate } from '~/utils';
type OwnProps = {
- value: Date | string | null;
+ value: Date | string | null | undefined;
};
-export function DateInputDisplay({ value }: OwnProps) {
+export function DateDisplay({ value }: OwnProps) {
return
{value && formatToHumanReadableDate(value)}
;
}
diff --git a/front/src/modules/ui/input/components/EmailInputDisplay.tsx b/front/src/modules/ui/content-display/components/EmailDisplay.tsx
similarity index 90%
rename from front/src/modules/ui/input/components/EmailInputDisplay.tsx
rename to front/src/modules/ui/content-display/components/EmailDisplay.tsx
index be508ca1a..dbf7b455a 100644
--- a/front/src/modules/ui/input/components/EmailInputDisplay.tsx
+++ b/front/src/modules/ui/content-display/components/EmailDisplay.tsx
@@ -11,7 +11,7 @@ type OwnProps = {
value: string | null;
};
-export function EmailInputDisplay({ value }: OwnProps) {
+export function EmailDisplay({ value }: OwnProps) {
return value && validateEmail(value) ? (
+ {value ? `$${formatNumber(value)}` : ''}
+
+ );
+}
diff --git a/front/src/modules/ui/dropdown/hooks/useDropdownButton.ts b/front/src/modules/ui/dropdown/hooks/useDropdownButton.ts
index c921c3dbd..cecb886f5 100644
--- a/front/src/modules/ui/dropdown/hooks/useDropdownButton.ts
+++ b/front/src/modules/ui/dropdown/hooks/useDropdownButton.ts
@@ -5,6 +5,7 @@ import { dropdownButtonCustomHotkeyScopeScopedFamilyState } from '../states/drop
import { isDropdownButtonOpenScopedFamilyState } from '../states/isDropdownButtonOpenScopedFamilyState';
import { DropdownRecoilScopeContext } from '../states/recoil-scope-contexts/DropdownRecoilScopeContext';
+// TODO: have a more explicit name than key
export function useDropdownButton({ key }: { key: string }) {
const {
setHotkeyScopeAndMemorizePreviousScope,
diff --git a/front/src/modules/ui/editable-field/components/GenericEditableDateField.tsx b/front/src/modules/ui/editable-field/components/GenericEditableDateField.tsx
index 727a9faf0..460db14e6 100644
--- a/front/src/modules/ui/editable-field/components/GenericEditableDateField.tsx
+++ b/front/src/modules/ui/editable-field/components/GenericEditableDateField.tsx
@@ -1,6 +1,7 @@
import { useContext } from 'react';
import { useRecoilValue } from 'recoil';
+import { DateDisplay } from '@/ui/content-display/components/DateDisplay';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { EditableFieldDefinitionContext } from '../contexts/EditableFieldDefinitionContext';
@@ -11,7 +12,6 @@ import { FieldDefinition } from '../types/FieldDefinition';
import { FieldDateMetadata } from '../types/FieldMetadata';
import { EditableField } from './EditableField';
-import { GenericEditableDateFieldDisplayMode } from './GenericEditableDateFieldDisplayMode';
import { GenericEditableDateFieldEditMode } from './GenericEditableDateFieldEditMode';
export function GenericEditableDateField() {
@@ -34,7 +34,7 @@ export function GenericEditableDateField() {
}
- displayModeContent={}
+ displayModeContent={}
isDisplayModeContentEmpty={!fieldValue}
/>
diff --git a/front/src/modules/ui/editable-field/components/GenericEditableDateFieldDisplayMode.tsx b/front/src/modules/ui/editable-field/components/GenericEditableDateFieldDisplayMode.tsx
deleted file mode 100644
index 1e1cb3f96..000000000
--- a/front/src/modules/ui/editable-field/components/GenericEditableDateFieldDisplayMode.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import { useContext } from 'react';
-import { useRecoilValue } from 'recoil';
-
-import { DateInputDisplay } from '@/ui/input/components/DateInputDisplay';
-import { parseDate } from '~/utils/date-utils';
-
-import { EditableFieldDefinitionContext } from '../contexts/EditableFieldDefinitionContext';
-import { EditableFieldEntityIdContext } from '../contexts/EditableFieldEntityIdContext';
-import { genericEntityFieldFamilySelector } from '../states/selectors/genericEntityFieldFamilySelector';
-import { FieldDefinition } from '../types/FieldDefinition';
-import { FieldDateMetadata } from '../types/FieldMetadata';
-
-export function GenericEditableDateFieldDisplayMode() {
- const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext);
- const currentEditableFieldDefinition = useContext(
- EditableFieldDefinitionContext,
- ) as FieldDefinition;
-
- const fieldValue = useRecoilValue(
- genericEntityFieldFamilySelector({
- entityId: currentEditableFieldEntityId ?? '',
- fieldName: currentEditableFieldDefinition
- ? currentEditableFieldDefinition.metadata.fieldName
- : '',
- }),
- );
-
- const internalDateValue = fieldValue
- ? parseDate(fieldValue).toJSDate()
- : null;
-
- return ;
-}
diff --git a/front/src/modules/ui/editable-field/components/GenericEditableDateFieldEditMode.tsx b/front/src/modules/ui/editable-field/components/GenericEditableDateFieldEditMode.tsx
index 65dd1b38f..886d05892 100644
--- a/front/src/modules/ui/editable-field/components/GenericEditableDateFieldEditMode.tsx
+++ b/front/src/modules/ui/editable-field/components/GenericEditableDateFieldEditMode.tsx
@@ -1,13 +1,17 @@
import { useContext } from 'react';
import { useRecoilState } from 'recoil';
+import { DateInput } from '@/ui/input/components/DateInput';
+import { Nullable } from '~/types/Nullable';
+
import { EditableFieldDefinitionContext } from '../contexts/EditableFieldDefinitionContext';
import { EditableFieldEntityIdContext } from '../contexts/EditableFieldEntityIdContext';
+import { useFieldInputEventHandlers } from '../hooks/useFieldInputEventHandlers';
import { useUpdateGenericEntityField } from '../hooks/useUpdateGenericEntityField';
import { genericEntityFieldFamilySelector } from '../states/selectors/genericEntityFieldFamilySelector';
+import { EditableFieldHotkeyScope } from '../types/EditableFieldHotkeyScope';
import { FieldDefinition } from '../types/FieldDefinition';
import { FieldDateMetadata } from '../types/FieldMetadata';
-import { EditableFieldEditModeDate } from '../variants/components/EditableFieldEditModeDate';
export function GenericEditableDateFieldEditMode() {
const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext);
@@ -27,7 +31,21 @@ export function GenericEditableDateFieldEditMode() {
const updateField = useUpdateGenericEntityField();
- function handleSubmit(newDateISO: string) {
+ function handleSubmit(newDate: Nullable) {
+ if (!newDate) {
+ setFieldValue('');
+
+ if (currentEditableFieldEntityId && updateField) {
+ updateField(
+ currentEditableFieldEntityId,
+ currentEditableFieldDefinition,
+ '',
+ );
+ }
+ }
+
+ const newDateISO = newDate?.toISOString();
+
if (newDateISO === fieldValue || !newDateISO) return;
setFieldValue(newDateISO);
@@ -41,7 +59,18 @@ export function GenericEditableDateFieldEditMode() {
}
}
+ const { handleEnter, handleEscape, handleClickOutside } =
+ useFieldInputEventHandlers({
+ onSubmit: handleSubmit,
+ });
+
return (
-
+
);
}
diff --git a/front/src/modules/ui/editable-field/components/GenericEditableNumberField.tsx b/front/src/modules/ui/editable-field/components/GenericEditableNumberField.tsx
index 4ac725cf2..f74cfdf05 100644
--- a/front/src/modules/ui/editable-field/components/GenericEditableNumberField.tsx
+++ b/front/src/modules/ui/editable-field/components/GenericEditableNumberField.tsx
@@ -1,6 +1,7 @@
import { useContext } from 'react';
import { useRecoilValue } from 'recoil';
+import { TextDisplay } from '@/ui/content-display/components/TextDisplay';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { EditableFieldDefinitionContext } from '../contexts/EditableFieldDefinitionContext';
@@ -33,7 +34,7 @@ export function GenericEditableNumberField() {
}
- displayModeContent={fieldValue}
+ displayModeContent={}
isDisplayModeContentEmpty={!fieldValue}
/>
diff --git a/front/src/modules/ui/editable-field/components/GenericEditableNumberFieldEditMode.tsx b/front/src/modules/ui/editable-field/components/GenericEditableNumberFieldEditMode.tsx
index 44f6079be..fd97324f3 100644
--- a/front/src/modules/ui/editable-field/components/GenericEditableNumberFieldEditMode.tsx
+++ b/front/src/modules/ui/editable-field/components/GenericEditableNumberFieldEditMode.tsx
@@ -1,7 +1,7 @@
-import { useContext, useRef, useState } from 'react';
+import { useContext } from 'react';
import { useRecoilState } from 'recoil';
-import { TextInputEdit } from '@/ui/input/text/components/TextInputEdit';
+import { TextInput } from '@/ui/input/components/TextInput';
import {
canBeCastAsIntegerOrNull,
castAsIntegerOrNull,
@@ -9,9 +9,10 @@ import {
import { EditableFieldDefinitionContext } from '../contexts/EditableFieldDefinitionContext';
import { EditableFieldEntityIdContext } from '../contexts/EditableFieldEntityIdContext';
-import { useRegisterCloseFieldHandlers } from '../hooks/useRegisterCloseFieldHandlers';
+import { useFieldInputEventHandlers } from '../hooks/useFieldInputEventHandlers';
import { useUpdateGenericEntityField } from '../hooks/useUpdateGenericEntityField';
import { genericEntityFieldFamilySelector } from '../states/selectors/genericEntityFieldFamilySelector';
+import { EditableFieldHotkeyScope } from '../types/EditableFieldHotkeyScope';
import { FieldDefinition } from '../types/FieldDefinition';
import { FieldNumberMetadata } from '../types/FieldMetadata';
@@ -30,51 +31,43 @@ export function GenericEditableNumberFieldEditMode() {
: '',
}),
);
- const [internalValue, setInternalValue] = useState(
- fieldValue ? fieldValue.toString() : '',
- );
const updateField = useUpdateGenericEntityField();
- const wrapperRef = useRef(null);
-
- useRegisterCloseFieldHandlers(wrapperRef, handleSubmit, onCancel);
-
- function handleSubmit() {
- if (!canBeCastAsIntegerOrNull(internalValue)) {
+ function handleSubmit(newValue: string) {
+ if (!canBeCastAsIntegerOrNull(newValue)) {
return;
}
- if (internalValue === fieldValue) return;
- setFieldValue(castAsIntegerOrNull(internalValue));
+ if (newValue === fieldValue) return;
+
+ const castedValue = castAsIntegerOrNull(newValue);
+
+ setFieldValue(castedValue);
if (currentEditableFieldEntityId && updateField) {
updateField(
currentEditableFieldEntityId,
currentEditableFieldDefinition,
- castAsIntegerOrNull(internalValue),
+ castedValue,
);
}
}
- function onCancel() {
- setFieldValue(fieldValue);
- }
-
- function handleChange(newValue: string) {
- setInternalValue(newValue);
- }
+ const { handleEnter, handleEscape, handleClickOutside } =
+ useFieldInputEventHandlers({
+ onSubmit: handleSubmit,
+ });
return (
-
- {
- handleChange(newValue);
- }}
- />
-
+
);
}
diff --git a/front/src/modules/ui/editable-field/variants/components/DateEditableField.tsx b/front/src/modules/ui/editable-field/variants/components/DateEditableField.tsx
index 8842c9a32..fbbf94362 100644
--- a/front/src/modules/ui/editable-field/variants/components/DateEditableField.tsx
+++ b/front/src/modules/ui/editable-field/variants/components/DateEditableField.tsx
@@ -1,7 +1,7 @@
+import { DateDisplay } from '@/ui/content-display/components/DateDisplay';
import { EditableField } from '@/ui/editable-field/components/EditableField';
import { FieldRecoilScopeContext } from '@/ui/editable-field/states/recoil-scope-contexts/FieldRecoilScopeContext';
import { IconComponent } from '@/ui/icon/types/IconComponent';
-import { DateInputDisplay } from '@/ui/input/components/DateInputDisplay';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { parseDate } from '~/utils/date-utils';
@@ -12,9 +12,16 @@ type OwnProps = {
label?: string;
value: string | null | undefined;
onSubmit?: (newValue: string) => void;
+ hotkeyScope: string;
};
-export function DateEditableField({ Icon, value, label, onSubmit }: OwnProps) {
+export function DateEditableField({
+ Icon,
+ value,
+ label,
+ onSubmit,
+ hotkeyScope,
+}: OwnProps) {
async function handleChange(newValue: string) {
onSubmit?.(newValue);
}
@@ -24,8 +31,6 @@ export function DateEditableField({ Icon, value, label, onSubmit }: OwnProps) {
return (
{
handleChange(newValue);
}}
+ parentHotkeyScope={hotkeyScope}
/>
}
- displayModeContent={}
+ displayModeContent={}
isDisplayModeContentEmpty={!value}
/>
diff --git a/front/src/modules/ui/editable-field/variants/components/EditableFieldEditModeDate.tsx b/front/src/modules/ui/editable-field/variants/components/EditableFieldEditModeDate.tsx
index ade7fa343..ff396720e 100644
--- a/front/src/modules/ui/editable-field/variants/components/EditableFieldEditModeDate.tsx
+++ b/front/src/modules/ui/editable-field/variants/components/EditableFieldEditModeDate.tsx
@@ -1,7 +1,7 @@
import { useEffect, useState } from 'react';
-import { DateInputEdit } from '@/ui/input/components/DateInputEdit';
-import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
+import { DateInput } from '@/ui/input/components/DateInput';
+import { Nullable } from '~/types/Nullable';
import { parseDate } from '~/utils/date-utils';
import { useEditableField } from '../../hooks/useEditableField';
@@ -9,10 +9,15 @@ import { useEditableField } from '../../hooks/useEditableField';
type OwnProps = {
value: string;
onChange?: (newValue: string) => void;
- parentHotkeyScope?: HotkeyScope;
+ parentHotkeyScope: string;
};
-export function EditableFieldEditModeDate({ value, onChange }: OwnProps) {
+// TODO: refactor this component to use the same logic as the GenericDateField component
+export function EditableFieldEditModeDate({
+ value,
+ onChange,
+ parentHotkeyScope,
+}: OwnProps) {
const [internalValue, setInternalValue] = useState(value);
useEffect(() => {
@@ -21,17 +26,26 @@ export function EditableFieldEditModeDate({ value, onChange }: OwnProps) {
const { closeEditableField } = useEditableField();
- function handleChange(newValue: string) {
- onChange?.(newValue);
+ function handleClickOutside() {
+ closeEditableField();
+ }
+
+ function handleEnter(newValue: Nullable) {
+ onChange?.(newValue?.toISOString() ?? '');
+ closeEditableField();
+ }
+
+ function handleEscape() {
closeEditableField();
}
return (
- {
- handleChange(newDate.toISOString());
- }}
+ hotkeyScope={parentHotkeyScope}
+ onClickOutside={handleClickOutside}
+ onEnter={handleEnter}
+ onEscape={handleEscape}
/>
);
}
diff --git a/front/src/modules/ui/input/components/DateInput.tsx b/front/src/modules/ui/input/components/DateInput.tsx
new file mode 100644
index 000000000..a9f68e8e0
--- /dev/null
+++ b/front/src/modules/ui/input/components/DateInput.tsx
@@ -0,0 +1,103 @@
+import { useEffect, useRef, useState } from 'react';
+import { useTheme } from '@emotion/react';
+import styled from '@emotion/styled';
+import { flip, offset, useFloating } from '@floating-ui/react';
+
+import { DateDisplay } from '@/ui/content-display/components/DateDisplay';
+import { Nullable } from '~/types/Nullable';
+
+import { useRegisterInputEvents } from '../hooks/useRegisterInputEvents';
+
+import { DatePicker } from './DatePicker';
+
+const StyledCalendarContainer = styled.div`
+ background: ${({ theme }) => theme.background.secondary};
+ border: 1px solid ${({ theme }) => theme.border.color.light};
+ border-radius: ${({ theme }) => theme.border.radius.md};
+ box-shadow: ${({ theme }) => theme.boxShadow.strong};
+
+ margin-top: 1px;
+
+ position: absolute;
+
+ z-index: 1;
+`;
+
+const StyledInputContainer = styled.div`
+ padding: ${({ theme }) => theme.spacing(0)} ${({ theme }) => theme.spacing(2)};
+
+ width: 100%;
+`;
+
+export type DateInputEditProps = {
+ value: Nullable;
+ onEnter: (newDate: Nullable) => void;
+ onEscape: (newDate: Nullable) => void;
+ onClickOutside: (
+ event: MouseEvent | TouchEvent,
+ newDate: Nullable,
+ ) => void;
+ hotkeyScope: string;
+};
+
+export function DateInput({
+ value,
+ hotkeyScope,
+ onEnter,
+ onEscape,
+ onClickOutside,
+}: DateInputEditProps) {
+ const theme = useTheme();
+
+ const [internalValue, setInternalValue] = useState(value);
+
+ const wrapperRef = useRef(null);
+
+ const { refs, floatingStyles } = useFloating({
+ placement: 'bottom-start',
+ middleware: [
+ flip(),
+ offset({
+ mainAxis: theme.spacingMultiplicator * 2,
+ }),
+ ],
+ });
+
+ function handleChange(newDate: Date) {
+ setInternalValue(newDate);
+ }
+
+ useEffect(() => {
+ setInternalValue(value);
+ }, [value]);
+
+ useRegisterInputEvents({
+ inputRef: wrapperRef,
+ inputValue: internalValue,
+ onEnter,
+ onEscape,
+ onClickOutside,
+ hotkeyScope,
+ });
+
+ return (
+
+
+
+
+
+
+
+
+ {
+ onEnter(newDate);
+ }}
+ />
+
+
+
+ );
+}
diff --git a/front/src/modules/ui/input/components/DateInputEdit.tsx b/front/src/modules/ui/input/components/DateInputEdit.tsx
deleted file mode 100644
index dacf8eb3e..000000000
--- a/front/src/modules/ui/input/components/DateInputEdit.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-import { forwardRef } from 'react';
-import styled from '@emotion/styled';
-
-import { formatToHumanReadableDate } from '~/utils';
-
-import DatePicker from './DatePicker';
-
-type StyledCalendarContainerProps = {
- editModeHorizontalAlign?: 'left' | 'right';
-};
-
-const StyledInputContainer = styled.div`
- color: ${({ theme }) => theme.font.color.primary};
- display: flex;
-
- padding: ${({ theme }) => theme.spacing(2)};
-`;
-
-const StyledCalendarContainer = styled.div`
- background: ${({ theme }) => theme.background.secondary};
- border: 1px solid ${({ theme }) => theme.border.color.light};
- border-radius: ${({ theme }) => theme.border.radius.md};
- box-shadow: ${({ theme }) => theme.boxShadow.strong};
-
- margin-top: 1px;
-
- position: absolute;
-
- z-index: 1;
-`;
-
-type DivProps = React.HTMLProps;
-
-const DateDisplay = forwardRef(
- ({ value, onClick }, ref) => (
-
- {value && formatToHumanReadableDate(new Date(value as string))}
-
- ),
-);
-
-type DatePickerContainerProps = {
- children: React.ReactNode;
-};
-
-const DatePickerContainer = ({ children }: DatePickerContainerProps) => {
- return {children};
-};
-
-export type DateInputEditProps = {
- value: Date | null | undefined;
- onChange: (newDate: Date) => void;
-};
-
-export function DateInputEdit({ onChange, value }: DateInputEditProps) {
- return (
- }
- customCalendarContainer={DatePickerContainer}
- />
- );
-}
diff --git a/front/src/modules/ui/input/components/DatePicker.tsx b/front/src/modules/ui/input/components/DatePicker.tsx
index 14e826e40..5683555ad 100644
--- a/front/src/modules/ui/input/components/DatePicker.tsx
+++ b/front/src/modules/ui/input/components/DatePicker.tsx
@@ -1,18 +1,11 @@
-import React, { forwardRef, ReactElement, useState } from 'react';
-import ReactDatePicker, { CalendarContainerProps } from 'react-datepicker';
+import React from 'react';
+import ReactDatePicker from 'react-datepicker';
import styled from '@emotion/styled';
import { overlayBackground } from '@/ui/theme/constants/effects';
import 'react-datepicker/dist/react-datepicker.css';
-export type DatePickerProps = {
- date: Date;
- onChangeHandler: (date: Date) => void;
- customInput?: ReactElement;
- customCalendarContainer?(props: CalendarContainerProps): React.ReactNode;
-};
-
const StyledContainer = styled.div`
& .react-datepicker {
border-color: ${({ theme }) => theme.border.color.light};
@@ -39,6 +32,10 @@ const StyledContainer = styled.div`
display: none;
}
+ & .react-datepicker-wrapper {
+ display: none;
+ }
+
// Header
& .react-datepicker__header {
@@ -223,47 +220,32 @@ const StyledContainer = styled.div`
}
`;
-function DatePicker({
- date,
- onChangeHandler,
- customInput,
- customCalendarContainer,
-}: DatePickerProps) {
- const [startDate, setStartDate] = useState(date);
-
- type DivProps = React.HTMLProps;
-
- const DefaultDateDisplay = forwardRef(
- ({ value, onClick }, ref) => (
-
- {value &&
- new Intl.DateTimeFormat(undefined, {
- month: 'short',
- day: 'numeric',
- year: 'numeric',
- }).format(new Date(value as string))}
-
- ),
- );
+export type DatePickerProps = {
+ date: Date;
+ onMouseSelect?: (date: Date) => void;
+ onChange?: (date: Date) => void;
+};
+export function DatePicker({ date, onChange, onMouseSelect }: DatePickerProps) {
return (
{
- setStartDate(date);
- onChangeHandler(date);
+ 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);
+ }
}}
- customInput={customInput ? customInput : }
- calendarContainer={
- customCalendarContainer ? customCalendarContainer : undefined
- }
/>
);
}
-
-export default DatePicker;
diff --git a/front/src/modules/ui/input/components/__stories__/DatePicker.stories.tsx b/front/src/modules/ui/input/components/__stories__/DatePicker.stories.tsx
index 83d506dd9..3beed7fd6 100644
--- a/front/src/modules/ui/input/components/__stories__/DatePicker.stories.tsx
+++ b/front/src/modules/ui/input/components/__stories__/DatePicker.stories.tsx
@@ -4,14 +4,13 @@ import { userEvent, within } from '@storybook/testing-library';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
-import DatePicker from '../DatePicker';
+import { DatePicker } from '../DatePicker';
const meta: Meta = {
title: 'UI/Input/DatePicker',
component: DatePicker,
decorators: [ComponentDecorator],
argTypes: {
- customInput: { control: false },
date: { control: 'date' },
},
args: { date: new Date('January 1, 2023 00:00:00') },
diff --git a/front/src/modules/ui/input/components/__stories__/EmailInputDisplay.stories.tsx b/front/src/modules/ui/input/components/__stories__/EmailInputDisplay.stories.tsx
index a84153d2f..fe2e26339 100644
--- a/front/src/modules/ui/input/components/__stories__/EmailInputDisplay.stories.tsx
+++ b/front/src/modules/ui/input/components/__stories__/EmailInputDisplay.stories.tsx
@@ -2,11 +2,11 @@ import { Meta, StoryObj } from '@storybook/react';
import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator';
-import { EmailInputDisplay } from '../EmailInputDisplay';
+import { EmailDisplay } from '../../../content-display/components/EmailDisplay';
const meta: Meta = {
title: 'UI/Input/EmailInputDisplay',
- component: EmailInputDisplay,
+ component: EmailDisplay,
decorators: [ComponentWithRouterDecorator],
args: {
value: 'mustajab.ikram@google.com',
@@ -15,6 +15,6 @@ const meta: Meta = {
export default meta;
-type Story = StoryObj;
+type Story = StoryObj;
export const Default: Story = {};
diff --git a/front/src/modules/ui/table/editable-cell/type/components/DateCellEdit.tsx b/front/src/modules/ui/table/editable-cell/type/components/DateCellEdit.tsx
deleted file mode 100644
index 80d43e16b..000000000
--- a/front/src/modules/ui/table/editable-cell/type/components/DateCellEdit.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import { useRef } from 'react';
-import styled from '@emotion/styled';
-import { Key } from 'ts-key-enum';
-
-import { DateInputEdit } from '@/ui/input/components/DateInputEdit';
-import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope';
-import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
-import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
-
-import { useEditableCell } from '../../hooks/useEditableCell';
-
-const StyledEditableCellDateEditModeContainer = styled.div`
- margin-top: -1px;
- width: 100%;
-`;
-
-export type DateCellEditProps = {
- value: Date;
- onSubmit: (date: Date) => void;
-};
-
-export function DateCellEdit({ value, onSubmit }: DateCellEditProps) {
- const { closeEditableCell } = useEditableCell();
-
- function handleDateChange(newDate: Date) {
- onSubmit(newDate);
-
- closeEditableCell();
- }
-
- useScopedHotkeys(
- Key.Escape,
- () => {
- closeEditableCell();
- },
- TableHotkeyScope.CellDateEditMode,
- [closeEditableCell],
- );
-
- const containerRef = useRef(null);
-
- useListenClickOutside({
- refs: [containerRef],
- callback: (event) => {
- event.stopImmediatePropagation();
- event.stopPropagation();
- event.preventDefault();
-
- closeEditableCell();
- },
- });
-
- return (
-
-
-
- );
-}
diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDateCell.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDateCell.tsx
index 33dc1ee51..b30be9d39 100644
--- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDateCell.tsx
+++ b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDateCell.tsx
@@ -1,7 +1,7 @@
import { useRecoilValue } from 'recoil';
+import { DateDisplay } from '@/ui/content-display/components/DateDisplay';
import type { ViewFieldDateMetadata } from '@/ui/editable-field/types/ViewField';
-import { DateInputDisplay } from '@/ui/input/components/DateInputDisplay';
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector';
@@ -34,7 +34,7 @@ export function GenericEditableDateCell({
editModeContent={
}
- nonEditModeContent={}
+ nonEditModeContent={}
>
);
}
diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDateCellEditMode.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDateCellEditMode.tsx
index c914da6a0..f5cc5b089 100644
--- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDateCellEditMode.tsx
+++ b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableDateCellEditMode.tsx
@@ -2,14 +2,16 @@ import { DateTime } from 'luxon';
import { useRecoilState } from 'recoil';
import type { ViewFieldDateMetadata } from '@/ui/editable-field/types/ViewField';
+import { DateInput } from '@/ui/input/components/DateInput';
+import { useCellInputEventHandlers } from '@/ui/table/hooks/useCellInputEventHandlers';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector';
+import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope';
+import { Nullable } from '~/types/Nullable';
import type { ColumnDefinition } from '../../../types/ColumnDefinition';
-import { DateCellEdit } from './DateCellEdit';
-
type OwnProps = {
columnDefinition: ColumnDefinition;
};
@@ -29,12 +31,13 @@ export function GenericEditableDateCellEditMode({
const updateField = useUpdateEntityField();
- function handleSubmit(newDate: Date) {
+ // Wrap this into a hook
+ function handleSubmit(newDate: Nullable) {
const fieldValueDate = fieldValue
? DateTime.fromISO(fieldValue).toJSDate()
: null;
- const newDateISO = DateTime.fromJSDate(newDate).toISO();
+ const newDateISO = newDate ? DateTime.fromJSDate(newDate).toISO() : null;
if (newDate === fieldValueDate || !newDateISO) return;
@@ -45,10 +48,18 @@ export function GenericEditableDateCellEditMode({
}
}
+ const { handleEnter, handleEscape, handleClickOutside } =
+ useCellInputEventHandlers({
+ onSubmit: handleSubmit,
+ });
+
return (
-
);
}
diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableEmailCell.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableEmailCell.tsx
index 7d2ce4541..2a9dc5ff0 100644
--- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableEmailCell.tsx
+++ b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableEmailCell.tsx
@@ -1,7 +1,7 @@
import { useRecoilValue } from 'recoil';
+import { EmailDisplay } from '@/ui/content-display/components/EmailDisplay';
import type { ViewFieldEmailMetadata } from '@/ui/editable-field/types/ViewField';
-import { EmailInputDisplay } from '@/ui/input/components/EmailInputDisplay';
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector';
@@ -34,7 +34,7 @@ export function GenericEditableEmailCell({
editModeContent={
}
- nonEditModeContent={}
+ nonEditModeContent={}
>
);
}
diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableMoneyCell.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableMoneyCell.tsx
index cc9c4c1c6..06cf57c5b 100644
--- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableMoneyCell.tsx
+++ b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableMoneyCell.tsx
@@ -1,5 +1,6 @@
import { useRecoilValue } from 'recoil';
+import { MoneyDisplay } from '@/ui/content-display/components/MoneyDisplay';
import type { ViewFieldMoneyMetadata } from '@/ui/editable-field/types/ViewField';
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
@@ -14,11 +15,6 @@ type OwnProps = {
editModeHorizontalAlign?: 'left' | 'right';
};
-function formatNumber(value: number) {
- // Formats the value to a string and add commas to it ex: 50,000 | 500,000
- return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
-}
-
export function GenericEditableMoneyCell({
columnDefinition,
editModeHorizontalAlign,
@@ -38,9 +34,7 @@ export function GenericEditableMoneyCell({
editModeContent={
}
- nonEditModeContent={
- <>{fieldValue ? `$${formatNumber(fieldValue)}` : ''}>
- }
+ nonEditModeContent={}
>
);
}
diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableMoneyCellEditMode.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableMoneyCellEditMode.tsx
index e523bcbfe..c24b134e6 100644
--- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableMoneyCellEditMode.tsx
+++ b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableMoneyCellEditMode.tsx
@@ -28,6 +28,7 @@ export function GenericEditableMoneyCellEditMode({
const updateField = useUpdateEntityField();
+ // TODO: handle this logic in a number input
function handleSubmit(newText: string) {
if (newText === fieldValue) return;
@@ -64,6 +65,7 @@ export function GenericEditableMoneyCellEditMode({
onSubmit: handleSubmit,
});
+ // TODO: use a number input
return (
}
- nonEditModeContent={<>{fieldValue}>}
+ nonEditModeContent={}
>
);
}
diff --git a/front/src/modules/ui/theme/constants/theme.ts b/front/src/modules/ui/theme/constants/theme.ts
index 4867e524c..2b67f1e20 100644
--- a/front/src/modules/ui/theme/constants/theme.ts
+++ b/front/src/modules/ui/theme/constants/theme.ts
@@ -33,6 +33,7 @@ const common = {
color: grayScale.gray0,
},
},
+ spacingMultiplicator: 4,
spacing: (multiplicator: number) => `${multiplicator * 4}px`,
betweenSiblingsGap: `2px`,
table: {
diff --git a/front/src/modules/ui/view-bar/components/DropdownButton.tsx b/front/src/modules/ui/view-bar/components/DropdownButton.tsx
index 102df877f..5ede64927 100644
--- a/front/src/modules/ui/view-bar/components/DropdownButton.tsx
+++ b/front/src/modules/ui/view-bar/components/DropdownButton.tsx
@@ -59,6 +59,10 @@ const StyledDropdownButton = styled.div`
}
`;
+/**
+ *
+ * @deprecated use ui/dropdown/components/DropdownButton.tsx instead
+ */
function DropdownButton({
anchor,
label,
diff --git a/front/src/modules/ui/view-bar/components/FilterDropdownDateSearchInput.tsx b/front/src/modules/ui/view-bar/components/FilterDropdownDateSearchInput.tsx
index 88682ffd2..a023560a1 100644
--- a/front/src/modules/ui/view-bar/components/FilterDropdownDateSearchInput.tsx
+++ b/front/src/modules/ui/view-bar/components/FilterDropdownDateSearchInput.tsx
@@ -1,12 +1,14 @@
import { Context } from 'react';
-import styled from '@emotion/styled';
-import DatePicker from '@/ui/input/components/DatePicker';
+import { DropdownRecoilScopeContext } from '@/ui/dropdown/states/recoil-scope-contexts/DropdownRecoilScopeContext';
+import { DatePicker } from '@/ui/input/components/DatePicker';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { useUpsertFilter } from '@/ui/view-bar/hooks/useUpsertFilter';
import { filterDefinitionUsedInDropdownScopedState } from '@/ui/view-bar/states/filterDefinitionUsedInDropdownScopedState';
import { selectedOperandInDropdownScopedState } from '@/ui/view-bar/states/selectedOperandInDropdownScopedState';
+import { isFilterDropdownUnfoldedScopedState } from '../states/isFilterDropdownUnfoldedScopedState';
+
export function FilterDropdownDateSearchInput({
context,
}: {
@@ -22,6 +24,11 @@ export function FilterDropdownDateSearchInput({
context,
);
+ const [, setIsFilterDropdownUnfolded] = useRecoilScopedState(
+ isFilterDropdownUnfoldedScopedState,
+ DropdownRecoilScopeContext,
+ );
+
const upsertFilter = useUpsertFilter(context);
function handleChange(date: Date) {
@@ -34,16 +41,15 @@ export function FilterDropdownDateSearchInput({
operand: selectedOperandInDropdown,
displayValue: date.toLocaleDateString(),
});
+
+ setIsFilterDropdownUnfolded(false);
}
return (
>}
- customCalendarContainer={styled.div`
- top: -10px;
- `}
+ onChange={handleChange}
+ onMouseSelect={handleChange}
/>
);
}
diff --git a/front/src/modules/ui/view-bar/components/MultipleFiltersDropdownButton.tsx b/front/src/modules/ui/view-bar/components/MultipleFiltersDropdownButton.tsx
index f66b1bafc..e1d1ff34f 100644
--- a/front/src/modules/ui/view-bar/components/MultipleFiltersDropdownButton.tsx
+++ b/front/src/modules/ui/view-bar/components/MultipleFiltersDropdownButton.tsx
@@ -1,6 +1,7 @@
-import { Context, useCallback, useState } from 'react';
+import { Context, useCallback } from 'react';
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
+import { DropdownRecoilScopeContext } from '@/ui/dropdown/states/recoil-scope-contexts/DropdownRecoilScopeContext';
import { IconComponent } from '@/ui/icon/types/IconComponent';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
@@ -10,6 +11,7 @@ import { filtersScopedState } from '@/ui/view-bar/states/filtersScopedState';
import { isFilterDropdownOperandSelectUnfoldedScopedState } from '@/ui/view-bar/states/isFilterDropdownOperandSelectUnfoldedScopedState';
import { selectedOperandInDropdownScopedState } from '@/ui/view-bar/states/selectedOperandInDropdownScopedState';
+import { isFilterDropdownUnfoldedScopedState } from '../states/isFilterDropdownUnfoldedScopedState';
import { isViewBarExpandedScopedState } from '../states/isViewBarExpandedScopedState';
import DropdownButton from './DropdownButton';
@@ -39,7 +41,11 @@ export function MultipleFiltersDropdownButton({
Icon,
label,
}: MultipleFiltersDropdownButtonProps) {
- const [isUnfolded, setIsUnfolded] = useState(false);
+ const [isFilterDropdownUnfolded, setIsFilterDropdownUnfolded] =
+ useRecoilScopedState(
+ isFilterDropdownUnfoldedScopedState,
+ DropdownRecoilScopeContext,
+ );
const [
isFilterDropdownOperandSelectUnfolded,
@@ -93,7 +99,7 @@ export function MultipleFiltersDropdownButton({
((isPrimaryButton && !isFilterSelected) || !isPrimaryButton)
) {
setHotkeyScope(hotkeyScope);
- setIsUnfolded(true);
+ setIsFilterDropdownUnfolded(true);
return;
}
@@ -101,7 +107,7 @@ export function MultipleFiltersDropdownButton({
setHotkeyScope(hotkeyScope);
}
- setIsUnfolded(false);
+ setIsFilterDropdownUnfolded(false);
resetState();
}
@@ -109,7 +115,7 @@ export function MultipleFiltersDropdownButton({
handleIsUnfoldedChange(!isUnfolded)}
+ isUnfolded={isFilterDropdownUnfolded}
+ onClick={() => handleIsUnfoldedChange(!isFilterDropdownUnfolded)}
>
{filters[0] ? (
@@ -96,7 +102,7 @@ export function SingleEntityFilterDropdownButton({
)}
- {isUnfolded && (
+ {isFilterDropdownUnfolded && (
handleIsUnfoldedChange(false)}>
diff --git a/front/src/modules/ui/view-bar/states/isFilterDropdownUnfoldedScopedState.ts b/front/src/modules/ui/view-bar/states/isFilterDropdownUnfoldedScopedState.ts
new file mode 100644
index 000000000..477cad02e
--- /dev/null
+++ b/front/src/modules/ui/view-bar/states/isFilterDropdownUnfoldedScopedState.ts
@@ -0,0 +1,6 @@
+import { atomFamily } from 'recoil';
+
+export const isFilterDropdownUnfoldedScopedState = atomFamily({
+ key: 'isFilterDropdownUnfoldedScopedState',
+ default: false,
+});
diff --git a/front/src/types/Nullable.ts b/front/src/types/Nullable.ts
new file mode 100644
index 000000000..164a7056d
--- /dev/null
+++ b/front/src/types/Nullable.ts
@@ -0,0 +1 @@
+export type Nullable = T | null | undefined;
diff --git a/front/src/utils/cast-as-integer-or-null.ts b/front/src/utils/cast-as-integer-or-null.ts
index ee706f257..0f95db43b 100644
--- a/front/src/utils/cast-as-integer-or-null.ts
+++ b/front/src/utils/cast-as-integer-or-null.ts
@@ -1,19 +1,29 @@
+const DEBUG_MODE = false;
+
export function canBeCastAsIntegerOrNull(
probableNumberOrNull: string | undefined | number | null,
): probableNumberOrNull is number | null {
if (probableNumberOrNull === undefined) {
+ if (DEBUG_MODE) console.log('probableNumberOrNull === undefined');
+
return false;
}
if (typeof probableNumberOrNull === 'number') {
+ if (DEBUG_MODE) console.log('typeof probableNumberOrNull === "number"');
+
return Number.isInteger(probableNumberOrNull);
}
if (probableNumberOrNull === null) {
+ if (DEBUG_MODE) console.log('probableNumberOrNull === null');
+
return true;
}
if (probableNumberOrNull === '') {
+ if (DEBUG_MODE) console.log('probableNumberOrNull === ""');
+
return true;
}
@@ -21,9 +31,13 @@ export function canBeCastAsIntegerOrNull(
const stringAsNumber = +probableNumberOrNull;
if (isNaN(stringAsNumber)) {
+ if (DEBUG_MODE) console.log('isNaN(stringAsNumber)');
+
return false;
}
if (Number.isInteger(stringAsNumber)) {
+ if (DEBUG_MODE) console.log('Number.isInteger(stringAsNumber)');
+
return true;
}
}
diff --git a/front/src/utils/formatNumber.ts b/front/src/utils/formatNumber.ts
new file mode 100644
index 000000000..dc318a437
--- /dev/null
+++ b/front/src/utils/formatNumber.ts
@@ -0,0 +1,4 @@
+export function formatNumber(value: number) {
+ // Formats the value to a string and add commas to it ex: 50,000 | 500,000
+ return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
+}