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:
@ -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;
|
||||
|
||||
@ -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]);
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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;
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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 />}
|
||||
/>,
|
||||
),
|
||||
};
|
||||
@ -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"
|
||||
/>,
|
||||
),
|
||||
};
|
||||
@ -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>,
|
||||
),
|
||||
};
|
||||
@ -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"
|
||||
/>,
|
||||
),
|
||||
};
|
||||
Reference in New Issue
Block a user