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

@ -16,7 +16,6 @@ export const EditableFieldNormalModeOuterContainer = styled.div<
padding: ${({ theme }) => theme.spacing(1)};
${(props) => {
console.log(props.isDisplayModeContentEmpty);
if (props.isDisplayModeContentEmpty) {
return css`
min-width: 50px;

View File

@ -1,8 +1,13 @@
import { useEffect } from 'react';
import { useRecoilCallback } from 'recoil';
import { currentHotkeyScopeState } from '@/ui/hotkey/states/internal/currentHotkeyScopeState';
import { HotkeyScope } from '@/ui/hotkey/types/HotkeyScope';
import { isSameHotkeyScope } from '@/ui/hotkey/utils/isSameHotkeyScope';
import { useContextScopeId } from '@/ui/recoil-scope/hooks/useContextScopeId';
import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedState';
import { getSnapshotScopedState } from '@/ui/recoil-scope/utils/getSnapshotScopedState';
import { getSnapshotState } from '@/ui/recoil-scope/utils/getSnapshotState';
import { customEditHotkeyScopeForFieldScopedState } from '../states/customEditHotkeyScopeForFieldScopedState';
import { FieldContext } from '../states/FieldContext';
@ -21,8 +26,7 @@ export function useBindFieldHotkeyScope({
FieldContext,
);
const [parentHotkeyScopeForField, setParentHotkeyScopeForField] =
useRecoilScopedState(parentHotkeyScopeForFieldScopedState, FieldContext);
const fieldContextScopeId = useContextScopeId(FieldContext);
useEffect(() => {
if (
@ -37,16 +41,35 @@ export function useBindFieldHotkeyScope({
setCustomEditHotkeyScopeForField,
]);
const setParentHotkeyScopeForField = useRecoilCallback(
({ snapshot, set }) =>
(parentHotkeyScopeToSet: HotkeyScope | null | undefined) => {
const currentHotkeyScope = getSnapshotState({
snapshot,
state: currentHotkeyScopeState,
});
const parentHotkeyScopeForField = getSnapshotScopedState({
snapshot,
state: parentHotkeyScopeForFieldScopedState,
contextScopeId: fieldContextScopeId,
});
if (!parentHotkeyScopeToSet) {
set(
parentHotkeyScopeForFieldScopedState(fieldContextScopeId),
currentHotkeyScope,
);
} else if (
!isSameHotkeyScope(parentHotkeyScopeToSet, parentHotkeyScopeForField)
) {
setParentHotkeyScopeForField(parentHotkeyScopeToSet);
}
},
[fieldContextScopeId],
);
useEffect(() => {
if (
parentHotkeyScope &&
!isSameHotkeyScope(parentHotkeyScope, parentHotkeyScopeForField)
) {
setParentHotkeyScopeForField(parentHotkeyScope);
}
}, [
parentHotkeyScope,
parentHotkeyScopeForField,
setParentHotkeyScopeForField,
]);
setParentHotkeyScopeForField(parentHotkeyScope);
}, [parentHotkeyScope, setParentHotkeyScopeForField]);
}

View File

@ -7,7 +7,6 @@ import { isFieldInEditModeScopedState } from '../states/isFieldInEditModeScopedS
import { parentHotkeyScopeForFieldScopedState } from '../states/parentHotkeyScopeForFieldScopedState';
import { EditableFieldHotkeyScope } from '../types/EditableFieldHotkeyScope';
// TODO: use atoms for hotkey scopes
export function useEditableField() {
const [isFieldInEditMode, setIsFieldInEditMode] = useRecoilScopedState(
isFieldInEditModeScopedState,

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

@ -2,7 +2,7 @@ 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';
import { useEditableField } from '../../hooks/useEditableField';
type OwnProps = {
value: string;

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"
/>,
),
};