Feat/editable fields update (#743)

* Removed console log

* Used current scope as default parent scope for fields

* Finished editable fields on people show page

* Added stories

* Console log

* Lint
This commit is contained in:
Lucas Bordeau
2023-07-19 02:43:16 +02:00
committed by GitHub
parent 5ee8eaa985
commit 7ecb098c55
30 changed files with 521 additions and 49 deletions

View File

@ -0,0 +1,65 @@
import { useEffect, useState } from 'react';
import { InplaceInputDateDisplayMode } from '@/ui/display/component/InplaceInputDateDisplayMode';
import { EditableField } from '@/ui/editable-field/components/EditableField';
import { FieldContext } from '@/ui/editable-field/states/FieldContext';
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
import { parseDate } from '~/utils/date-utils';
import { EditableFieldEditModeDate } from './EditableFieldEditModeDate';
type OwnProps = {
icon?: React.ReactNode;
value: string | null | undefined;
onSubmit?: (newValue: string) => void;
};
export function DateEditableField({ icon, value, 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;
return (
<RecoilScope SpecificContext={FieldContext}>
<EditableField
onSubmit={handleSubmit}
onCancel={handleCancel}
iconLabel={icon}
editModeContent={
<EditableFieldEditModeDate
value={internalValue ?? ''}
onChange={(newValue: string) => {
handleChange(newValue);
}}
/>
}
displayModeContent={
<InplaceInputDateDisplayMode value={internalDateValue} />
}
isDisplayModeContentEmpty={!(internalValue !== '')}
/>
</RecoilScope>
);
}

View File

@ -0,0 +1,29 @@
import { HotkeyScope } from '@/ui/hotkey/types/HotkeyScope';
import { InplaceInputDate } from '@/ui/inplace-input/components/InplaceInputDate';
import { parseDate } from '~/utils/date-utils';
import { useEditableField } from '../../hooks/useEditableField';
type OwnProps = {
value: string;
onChange?: (newValue: string) => void;
parentHotkeyScope?: HotkeyScope;
};
export function EditableFieldEditModeDate({ value, onChange }: OwnProps) {
const { closeEditableField } = useEditableField();
function handleChange(newValue: string) {
onChange?.(newValue);
closeEditableField();
}
return (
<InplaceInputDate
value={parseDate(value).toJSDate()}
onChange={(newDate: Date) => {
handleChange(newDate.toISOString());
}}
/>
);
}

View File

@ -0,0 +1,66 @@
import { useEffect, useState } from 'react';
import { InplaceInputPhoneDisplayMode } from '@/ui/display/component/InplaceInputPhoneDisplayMode';
import { EditableField } from '@/ui/editable-field/components/EditableField';
import { FieldContext } from '@/ui/editable-field/states/FieldContext';
import { InplaceInputText } from '@/ui/inplace-input/components/InplaceInputText';
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
type OwnProps = {
icon?: React.ReactNode;
placeholder?: string;
value: string | null | undefined;
onSubmit?: (newValue: string) => void;
};
export function PhoneEditableField({
icon,
placeholder,
value,
onSubmit,
}: OwnProps) {
const [internalValue, setInternalValue] = useState(value);
useEffect(() => {
setInternalValue(value);
}, [value]);
async function handleChange(newValue: string) {
setInternalValue(newValue);
}
async function handleSubmit() {
if (!internalValue) return;
onSubmit?.(internalValue);
}
async function handleCancel() {
setInternalValue(value);
}
return (
<RecoilScope SpecificContext={FieldContext}>
<EditableField
onSubmit={handleSubmit}
onCancel={handleCancel}
iconLabel={icon}
editModeContent={
<InplaceInputText
placeholder={placeholder ?? ''}
autoFocus
value={internalValue ?? ''}
onChange={(newValue: string) => {
handleChange(newValue);
}}
/>
}
displayModeContent={
<InplaceInputPhoneDisplayMode value={internalValue ?? ''} />
}
isDisplayModeContentEmpty={!(internalValue !== '')}
useEditButton
/>
</RecoilScope>
);
}

View File

@ -0,0 +1,23 @@
import type { Meta, StoryObj } from '@storybook/react';
import { IconCalendar } from '@tabler/icons-react';
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
import { DateEditableField } from '../DateEditableField';
const meta: Meta<typeof DateEditableField> = {
title: 'UI/EditableField/DateEditableField',
component: DateEditableField,
};
export default meta;
type Story = StoryObj<typeof DateEditableField>;
export const Default: Story = {
render: getRenderWrapperForComponent(
<DateEditableField
value={new Date().toISOString()}
icon={<IconCalendar />}
/>,
),
};

View File

@ -0,0 +1,24 @@
import type { Meta, StoryObj } from '@storybook/react';
import { IconCurrencyDollar } from '@tabler/icons-react';
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
import { NumberEditableField } from '../NumberEditableField';
const meta: Meta<typeof NumberEditableField> = {
title: 'UI/EditableField/NumberEditableField',
component: NumberEditableField,
};
export default meta;
type Story = StoryObj<typeof NumberEditableField>;
export const Default: Story = {
render: getRenderWrapperForComponent(
<NumberEditableField
value={10}
icon={<IconCurrencyDollar />}
placeholder="Number"
/>,
),
};

View File

@ -0,0 +1,27 @@
import { BrowserRouter } from 'react-router-dom';
import type { Meta, StoryObj } from '@storybook/react';
import { IconPhone } from '@tabler/icons-react';
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
import { PhoneEditableField } from '../PhoneEditableField';
const meta: Meta<typeof PhoneEditableField> = {
title: 'UI/EditableField/PhoneEditableField',
component: PhoneEditableField,
};
export default meta;
type Story = StoryObj<typeof PhoneEditableField>;
export const Default: Story = {
render: getRenderWrapperForComponent(
<BrowserRouter>
<PhoneEditableField
value={'+33714446494'}
icon={<IconPhone />}
placeholder="Phone"
/>
</BrowserRouter>,
),
};

View File

@ -0,0 +1,24 @@
import type { Meta, StoryObj } from '@storybook/react';
import { IconUser } from '@tabler/icons-react';
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
import { TextEditableField } from '../TextEditableField';
const meta: Meta<typeof TextEditableField> = {
title: 'UI/EditableField/TextEditableField',
component: TextEditableField,
};
export default meta;
type Story = StoryObj<typeof TextEditableField>;
export const Default: Story = {
render: getRenderWrapperForComponent(
<TextEditableField
value={'John Doe'}
icon={<IconUser />}
placeholder="Name"
/>,
),
};