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:
@ -1,7 +1,7 @@
|
||||
import { formatToHumanReadableDate } from '~/utils';
|
||||
|
||||
type OwnProps = {
|
||||
value: Date;
|
||||
value: Date | null;
|
||||
};
|
||||
|
||||
export function InplaceInputDateDisplayMode({ value }: OwnProps) {
|
||||
|
||||
@ -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"
|
||||
/>,
|
||||
),
|
||||
};
|
||||
@ -1,5 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { currentHotkeyScopeState } from '../states/internal/currentHotkeyScopeState';
|
||||
import { CustomHotkeyScopes } from '../types/CustomHotkeyScope';
|
||||
@ -13,8 +13,6 @@ export function usePreviousHotkeyScope() {
|
||||
|
||||
const setHotkeyScope = useSetHotkeyScope();
|
||||
|
||||
const currentHotkeyScope = useRecoilValue(currentHotkeyScopeState);
|
||||
|
||||
function goBackToPreviousHotkeyScope() {
|
||||
if (previousHotkeyScope) {
|
||||
setHotkeyScope(
|
||||
@ -24,13 +22,19 @@ export function usePreviousHotkeyScope() {
|
||||
}
|
||||
}
|
||||
|
||||
function setHotkeyScopeAndMemorizePreviousScope(
|
||||
scope: string,
|
||||
customScopes?: CustomHotkeyScopes,
|
||||
) {
|
||||
setPreviousHotkeyScope(currentHotkeyScope);
|
||||
setHotkeyScope(scope, customScopes);
|
||||
}
|
||||
const setHotkeyScopeAndMemorizePreviousScope = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
(scope: string, customScopes?: CustomHotkeyScopes) => {
|
||||
const currentHotkeyScope = snapshot
|
||||
.getLoadable(currentHotkeyScopeState)
|
||||
.valueOrThrow();
|
||||
|
||||
setHotkeyScope(scope, customScopes);
|
||||
|
||||
setPreviousHotkeyScope(currentHotkeyScope);
|
||||
},
|
||||
[setPreviousHotkeyScope],
|
||||
);
|
||||
|
||||
return {
|
||||
setHotkeyScopeAndMemorizePreviousScope,
|
||||
|
||||
@ -19,12 +19,12 @@ function isCustomScopesEqual(
|
||||
export function useSetHotkeyScope() {
|
||||
return useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
async (HotkeyScopeToSet: string, customScopes?: CustomHotkeyScopes) => {
|
||||
async (hotkeyScopeToSet: string, customScopes?: CustomHotkeyScopes) => {
|
||||
const currentHotkeyScope = await snapshot.getPromise(
|
||||
currentHotkeyScopeState,
|
||||
);
|
||||
|
||||
if (currentHotkeyScope.scope === HotkeyScopeToSet) {
|
||||
if (currentHotkeyScope.scope === hotkeyScopeToSet) {
|
||||
if (!isDefined(customScopes)) {
|
||||
if (
|
||||
isCustomScopesEqual(
|
||||
@ -47,7 +47,7 @@ export function useSetHotkeyScope() {
|
||||
}
|
||||
|
||||
set(currentHotkeyScopeState, {
|
||||
scope: HotkeyScopeToSet,
|
||||
scope: hotkeyScopeToSet,
|
||||
customScopes: {
|
||||
commandMenu: customScopes?.commandMenu ?? true,
|
||||
goto: customScopes?.goto ?? false,
|
||||
|
||||
@ -48,7 +48,7 @@ export const DatePickerContainer = ({ children }: DatePickerContainerProps) => {
|
||||
};
|
||||
|
||||
type OwnProps = {
|
||||
value: Date;
|
||||
value: Date | null | undefined;
|
||||
onChange: (newDate: Date) => void;
|
||||
};
|
||||
|
||||
@ -56,7 +56,7 @@ export function InplaceInputDate({ onChange, value }: OwnProps) {
|
||||
return (
|
||||
<InplaceInputContainer>
|
||||
<DatePicker
|
||||
date={value}
|
||||
date={value ?? new Date()}
|
||||
onChangeHandler={onChange}
|
||||
customInput={<DateDisplay />}
|
||||
customCalendarContainer={DatePickerContainer}
|
||||
|
||||
12
front/src/modules/ui/recoil-scope/hooks/useContextScopeId.ts
Normal file
12
front/src/modules/ui/recoil-scope/hooks/useContextScopeId.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { Context, useContext } from 'react';
|
||||
|
||||
export function useContextScopeId(SpecificContext: Context<string | null>) {
|
||||
const recoilScopeId = useContext(SpecificContext);
|
||||
|
||||
if (!recoilScopeId)
|
||||
throw new Error(
|
||||
`Using useContextScopedId outside of the specified context : ${SpecificContext.displayName}, verify that you are using a RecoilScope with the specific context you want to use.`,
|
||||
);
|
||||
|
||||
return recoilScopeId;
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
import { RecoilState, Snapshot } from 'recoil';
|
||||
|
||||
export function getSnapshotScopedState<T>({
|
||||
snapshot,
|
||||
state,
|
||||
contextScopeId,
|
||||
}: {
|
||||
snapshot: Snapshot;
|
||||
state: (scopeId: string) => RecoilState<T>;
|
||||
contextScopeId: string;
|
||||
}) {
|
||||
return snapshot.getLoadable(state(contextScopeId)).valueOrThrow();
|
||||
}
|
||||
11
front/src/modules/ui/recoil-scope/utils/getSnapshotState.ts
Normal file
11
front/src/modules/ui/recoil-scope/utils/getSnapshotState.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { RecoilState, Snapshot } from 'recoil';
|
||||
|
||||
export function getSnapshotState<T>({
|
||||
snapshot,
|
||||
state,
|
||||
}: {
|
||||
snapshot: Snapshot;
|
||||
state: RecoilState<T>;
|
||||
}) {
|
||||
return snapshot.getLoadable(state).valueOrThrow();
|
||||
}
|
||||
@ -25,6 +25,7 @@ export function EditableCellDateEditMode({
|
||||
|
||||
function handleDateChange(newDate: Date) {
|
||||
onChange(newDate);
|
||||
|
||||
closeEditableCell();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user