Feat/generic editable board card (#1089)

* Fixed BoardColumnMenu

* Fixed naming

* Optimized board loading

* Added GenericEditableField

* Introduce GenericEditableField for BoardCards

* remove logs

* delete unused files

* fix stories

---------

Co-authored-by: corentin <corentin@twenty.com>
This commit is contained in:
Lucas Bordeau
2023-08-09 05:08:37 +02:00
committed by GitHub
parent 77d356f78a
commit 3666980ccc
103 changed files with 1551 additions and 922 deletions

View File

@ -1,5 +1,3 @@
import { useEffect, useState } from 'react';
import { EditableField } from '@/ui/editable-field/components/EditableField';
import { FieldContext } from '@/ui/editable-field/states/FieldContext';
import { DateInputDisplay } from '@/ui/input/date/components/DateInputDisplay';
@ -16,49 +14,29 @@ type OwnProps = {
};
export function DateEditableField({ icon, value, label, onSubmit }: OwnProps) {
const [internalValue, setInternalValue] = useState(value);
useEffect(() => {
setInternalValue(value);
}, [value]);
async function handleChange(newValue: string) {
setInternalValue(newValue);
onSubmit?.(newValue);
}
async function handleSubmit() {
if (!internalValue) return;
onSubmit?.(internalValue);
}
async function handleCancel() {
setInternalValue(value);
}
const internalDateValue = internalValue
? parseDate(internalValue).toJSDate()
: null;
const internalDateValue = value ? parseDate(value).toJSDate() : null;
return (
<RecoilScope SpecificContext={FieldContext}>
<EditableField
onSubmit={handleSubmit}
onCancel={handleCancel}
// onSubmit={handleSubmit}
// onCancel={handleCancel}
iconLabel={icon}
label={label}
editModeContent={
<EditableFieldEditModeDate
value={internalValue || new Date().toISOString()}
value={value || new Date().toISOString()}
onChange={(newValue: string) => {
handleChange(newValue);
}}
/>
}
displayModeContent={<DateInputDisplay value={internalDateValue} />}
isDisplayModeContentEmpty={!internalValue}
isDisplayModeContentEmpty={!value}
/>
</RecoilScope>
);

View File

@ -1,3 +1,5 @@
import { useEffect, useState } from 'react';
import { DateInputEdit } from '@/ui/input/date/components/DateInputEdit';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { parseDate } from '~/utils/date-utils';
@ -11,6 +13,12 @@ type OwnProps = {
};
export function EditableFieldEditModeDate({ value, onChange }: OwnProps) {
const [internalValue, setInternalValue] = useState(value);
useEffect(() => {
setInternalValue(value);
}, [value]);
const { closeEditableField } = useEditableField();
function handleChange(newValue: string) {
@ -20,7 +28,7 @@ export function EditableFieldEditModeDate({ value, onChange }: OwnProps) {
return (
<DateInputEdit
value={parseDate(value).toJSDate()}
value={internalValue ? parseDate(internalValue).toJSDate() : new Date()}
onChange={(newDate: Date) => {
handleChange(newDate.toISOString());
}}

View File

@ -1,73 +0,0 @@
import { useEffect, useState } from 'react';
import { EditableField } from '@/ui/editable-field/components/EditableField';
import { FieldContext } from '@/ui/editable-field/states/FieldContext';
import { TextInputEdit } from '@/ui/input/text/components/TextInputEdit';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import {
canBeCastAsIntegerOrNull,
castAsIntegerOrNull,
} from '~/utils/cast-as-integer-or-null';
type OwnProps = {
icon?: React.ReactNode;
placeholder?: string;
value: number | null | undefined;
onSubmit?: (newValue: number | null) => void;
};
export function NumberEditableField({
icon,
placeholder,
value,
onSubmit,
}: OwnProps) {
const [internalValue, setInternalValue] = useState(value?.toString());
useEffect(() => {
setInternalValue(value?.toString());
}, [value]);
async function handleChange(newValue: string) {
setInternalValue(newValue);
}
async function handleSubmit() {
if (!canBeCastAsIntegerOrNull(internalValue)) {
handleCancel();
return;
}
const valueCastedAsNumberOrNull = castAsIntegerOrNull(internalValue);
onSubmit?.(valueCastedAsNumberOrNull);
setInternalValue(valueCastedAsNumberOrNull?.toString());
}
async function handleCancel() {
setInternalValue(value?.toString());
}
return (
<RecoilScope SpecificContext={FieldContext}>
<EditableField
onSubmit={handleSubmit}
onCancel={handleCancel}
iconLabel={icon}
editModeContent={
<TextInputEdit
placeholder={placeholder ?? ''}
autoFocus
value={internalValue ?? ''}
onChange={(newValue: string) => {
handleChange(newValue);
}}
/>
}
displayModeContent={internalValue}
isDisplayModeContentEmpty={!(internalValue !== '' && internalValue)}
/>
</RecoilScope>
);
}

View File

@ -1,32 +0,0 @@
import type { Meta, StoryObj } from '@storybook/react';
import { IconCurrencyDollar } from '@tabler/icons-react';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { NumberEditableField } from '../NumberEditableField';
const meta: Meta<typeof NumberEditableField> = {
title: 'UI/EditableField/NumberEditableField',
component: NumberEditableField,
decorators: [ComponentDecorator],
argTypes: {
icon: {
type: 'boolean',
mapping: {
true: <IconCurrencyDollar />,
false: undefined,
},
},
value: { control: { type: 'number' } },
},
args: {
value: 10,
icon: true,
placeholder: 'Number',
},
};
export default meta;
type Story = StoryObj<typeof NumberEditableField>;
export const Default: Story = {};