Issue6335: RecordInlineCell tree refactor with RecordInlineCellContext (#6537)

Fixes [#6335](https://github.com/twentyhq/twenty/issues/6335)

This pull request is for issue
[#6335](https://github.com/twentyhq/twenty/issues/6335): Refactor
RecordInlineCell tree with a Context to avoid props drilling. For the
refactoring, this PR made changes as below:

- Created new script RecordInlineCellContext.tsx: Defining a context to
pass in useContext()
- Updated RecordInlineCell.tsx: Passing the necessary props as context
values, wrapping with RecordInlineCellContext.Provider
- Updated RecordInlineCellContainer.tsx: Passing the props to
RecordInlineContainer as RecordInlineCellContext
- Updated RecordInlineCellDisplayMode.tsx: retrieves values from
useRecordInlineCellContext instead of directly assigning them
- RecordInlineCellValue.tsx: Removed values passed through
<RecordInlineCellDisplayMode> as they are now retrieved through
useRecordInlineCellContext + Removed the null check for
RecordInlineCellContextProps.

Using RecordInlineCellContext, I believe the context goes to the top of
the hierarchy and passed to the required layers without going through
several layers. However, please let me know if I understood the issue
incorrectly or it is not solved properly.

Thank you in advance for your review!

---------

Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
Hansol Yang
2024-08-12 19:18:05 +09:00
committed by GitHub
parent b16437e6c8
commit b6202fe98c
6 changed files with 160 additions and 168 deletions

View File

@ -14,6 +14,7 @@ import { FieldContext } from '@/object-record/record-field/contexts/FieldContext
import { FieldFocusContextProvider } from '@/object-record/record-field/contexts/FieldFocusContextProvider'; import { FieldFocusContextProvider } from '@/object-record/record-field/contexts/FieldFocusContextProvider';
import { RecordFieldInputScope } from '@/object-record/record-field/scopes/RecordFieldInputScope'; import { RecordFieldInputScope } from '@/object-record/record-field/scopes/RecordFieldInputScope';
import { RecordInlineCellContainer } from '@/object-record/record-inline-cell/components/RecordInlineCellContainer'; import { RecordInlineCellContainer } from '@/object-record/record-inline-cell/components/RecordInlineCellContainer';
import { RecordInlineCellContext } from '@/object-record/record-inline-cell/components/RecordInlineCellContext';
import { useInlineCell } from '@/object-record/record-inline-cell/hooks/useInlineCell'; import { useInlineCell } from '@/object-record/record-inline-cell/hooks/useInlineCell';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
@ -65,30 +66,36 @@ export const ActivityTargetsInlineCell = ({
<FieldFocusContextProvider> <FieldFocusContextProvider>
{ActivityTargetsContextProvider && ( {ActivityTargetsContextProvider && (
<ActivityTargetsContextProvider> <ActivityTargetsContextProvider>
<RecordInlineCellContainer <RecordInlineCellContext.Provider
buttonIcon={IconPencil} value={{
customEditHotkeyScope={{ buttonIcon: IconPencil,
customEditHotkeyScope: {
scope: ActivityEditorHotkeyScope.ActivityTargets, scope: ActivityEditorHotkeyScope.ActivityTargets,
}} },
IconLabel={showLabel ? IconArrowUpRight : undefined} IconLabel: showLabel ? IconArrowUpRight : undefined,
showLabel={showLabel} showLabel: showLabel,
readonly={readonly} readonly: readonly,
labelWidth={fieldDefinition?.labelWidth} labelWidth: fieldDefinition?.labelWidth,
editModeContent={ editModeContent: (
<ActivityTargetInlineCellEditMode <ActivityTargetInlineCellEditMode
activity={activity} activity={activity}
activityTargetWithTargetRecords={activityTargetObjectRecords} activityTargetWithTargetRecords={
activityTargetObjectRecords
}
activityObjectNameSingular={activityObjectNameSingular} activityObjectNameSingular={activityObjectNameSingular}
/> />
} ),
label="Relations" label: 'Relations',
displayModeContent={ displayModeContent: (
<ActivityTargetChips <ActivityTargetChips
activityTargetObjectRecords={activityTargetObjectRecords} activityTargetObjectRecords={activityTargetObjectRecords}
maxWidth={maxWidth} maxWidth={maxWidth}
/> />
} ),
/> }}
>
<RecordInlineCellContainer />
</RecordInlineCellContext.Provider>
</ActivityTargetsContextProvider> </ActivityTargetsContextProvider>
)} )}
</FieldFocusContextProvider> </FieldFocusContextProvider>

View File

@ -16,6 +16,10 @@ import { useInlineCell } from '../hooks/useInlineCell';
import { useIsFieldReadOnly } from '@/object-record/record-field/hooks/useIsFieldReadOnly'; import { useIsFieldReadOnly } from '@/object-record/record-field/hooks/useIsFieldReadOnly';
import { getRecordFieldInputId } from '@/object-record/utils/getRecordFieldInputId'; import { getRecordFieldInputId } from '@/object-record/utils/getRecordFieldInputId';
import { RecordInlineCellContainer } from './RecordInlineCellContainer'; import { RecordInlineCellContainer } from './RecordInlineCellContainer';
import {
RecordInlineCellContext,
RecordInlineCellContextProps,
} from './RecordInlineCellContext';
type RecordInlineCellProps = { type RecordInlineCellProps = {
readonly?: boolean; readonly?: boolean;
@ -23,7 +27,6 @@ type RecordInlineCellProps = {
isCentered?: boolean; isCentered?: boolean;
}; };
// TODO: refactor props drilling with a RecordInlineCellContext
export const RecordInlineCell = ({ export const RecordInlineCell = ({
readonly, readonly,
loading, loading,
@ -75,28 +78,20 @@ export const RecordInlineCell = ({
const { getIcon } = useIcons(); const { getIcon } = useIcons();
return ( const RecordInlineCellContextValue: RecordInlineCellContextProps = {
<FieldFocusContextProvider> readonly: cellIsReadOnly,
<RecordInlineCellContainer buttonIcon: buttonIcon,
readonly={cellIsReadOnly} customEditHotkeyScope: isFieldRelation(fieldDefinition)
buttonIcon={buttonIcon} ? { scope: RelationPickerHotkeyScope.RelationPicker }
customEditHotkeyScope={ : undefined,
isFieldRelation(fieldDefinition) IconLabel: fieldDefinition.iconName
? {
scope: RelationPickerHotkeyScope.RelationPicker,
}
: undefined
}
IconLabel={
fieldDefinition.iconName
? getIcon(fieldDefinition.iconName) ? getIcon(fieldDefinition.iconName)
: undefined : undefined,
} label: fieldDefinition.label,
label={fieldDefinition.label} labelWidth: fieldDefinition.labelWidth,
labelWidth={fieldDefinition.labelWidth} showLabel: fieldDefinition.showLabel,
showLabel={fieldDefinition.showLabel} isCentered: isCentered,
isCentered={isCentered} editModeContent: (
editModeContent={
<FieldInput <FieldInput
recordFieldInputdId={getRecordFieldInputId( recordFieldInputdId={getRecordFieldInputId(
recordId, recordId,
@ -111,12 +106,18 @@ export const RecordInlineCell = ({
onClickOutside={handleClickOutside} onClickOutside={handleClickOutside}
isReadOnly={cellIsReadOnly} isReadOnly={cellIsReadOnly}
/> />
} ),
displayModeContent={<FieldDisplay />} displayModeContent: <FieldDisplay />,
isDisplayModeFixHeight isDisplayModeFixHeight: undefined,
editModeContentOnly={isFieldInputOnly} editModeContentOnly: isFieldInputOnly,
loading={loading} loading: loading,
/> };
return (
<FieldFocusContextProvider>
<RecordInlineCellContext.Provider value={RecordInlineCellContextValue}>
<RecordInlineCellContainer />
</RecordInlineCellContext.Provider>
</FieldFocusContextProvider> </FieldFocusContextProvider>
); );
}; };

View File

@ -10,6 +10,8 @@ import { getRecordFieldInputId } from '@/object-record/utils/getRecordFieldInput
import { EllipsisDisplay } from '@/ui/field/display/components/EllipsisDisplay'; import { EllipsisDisplay } from '@/ui/field/display/components/EllipsisDisplay';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { useRecordInlineCellContext } from './RecordInlineCellContext';
const StyledIconContainer = styled.div` const StyledIconContainer = styled.div`
align-items: center; align-items: center;
color: ${({ theme }) => theme.font.color.tertiary}; color: ${({ theme }) => theme.font.color.tertiary};
@ -78,23 +80,10 @@ export type RecordInlineCellContainerProps = {
isCentered?: boolean; isCentered?: boolean;
}; };
// TODO: refactor props drilling with a RecordInlineCellContext export const RecordInlineCellContainer = () => {
export const RecordInlineCellContainer = ({ const { readonly, IconLabel, label, labelWidth, showLabel } =
readonly, useRecordInlineCellContext();
IconLabel,
label,
labelWidth,
showLabel,
buttonIcon,
editModeContent,
displayModeContent,
customEditHotkeyScope,
editModeContentOnly,
isDisplayModeFixHeight,
disableHoverEffect,
loading = false,
isCentered,
}: RecordInlineCellContainerProps) => {
const { recordId, fieldDefinition } = useContext(FieldContext); const { recordId, fieldDefinition } = useContext(FieldContext);
const { setIsFocused } = useFieldFocus(); const { setIsFocused } = useFieldFocus();
@ -149,22 +138,7 @@ export const RecordInlineCellContainer = ({
</StyledLabelAndIconContainer> </StyledLabelAndIconContainer>
)} )}
<StyledValueContainer> <StyledValueContainer>
<RecordInlineCellValue <RecordInlineCellValue />
{...{
displayModeContent,
customEditHotkeyScope,
disableHoverEffect,
editModeContent,
editModeContentOnly,
isDisplayModeFixHeight,
buttonIcon,
label,
loading,
readonly,
showLabel,
isCentered,
}}
/>
</StyledValueContainer> </StyledValueContainer>
</StyledInlineCellBaseContainer> </StyledInlineCellBaseContainer>
); );

View File

@ -0,0 +1,47 @@
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { createContext, ReactElement, useContext } from 'react';
import { IconComponent } from 'twenty-ui';
export type RecordInlineCellContextProps = {
readonly?: boolean;
IconLabel?: IconComponent;
label?: string;
labelWidth?: number;
showLabel?: boolean;
buttonIcon?: IconComponent;
editModeContent?: ReactElement;
editModeContentOnly?: boolean;
displayModeContent?: ReactElement;
customEditHotkeyScope?: HotkeyScope;
isDisplayModeFixHeight?: boolean;
disableHoverEffect?: boolean;
loading?: boolean;
isCentered?: boolean;
};
const defaultRecordInlineCellContextProp: RecordInlineCellContextProps = {
readonly: false,
IconLabel: undefined,
label: '',
labelWidth: 0,
showLabel: false,
buttonIcon: undefined,
editModeContent: undefined,
editModeContentOnly: false,
displayModeContent: undefined,
customEditHotkeyScope: undefined,
isDisplayModeFixHeight: false,
disableHoverEffect: false,
loading: false,
isCentered: false,
};
export const RecordInlineCellContext =
createContext<RecordInlineCellContextProps>(
defaultRecordInlineCellContextProp,
);
export const useRecordInlineCellContext = (): RecordInlineCellContextProps => {
const context = useContext(RecordInlineCellContext);
return context;
};

View File

@ -4,14 +4,17 @@ import styled from '@emotion/styled';
import { useFieldFocus } from '@/object-record/record-field/hooks/useFieldFocus'; import { useFieldFocus } from '@/object-record/record-field/hooks/useFieldFocus';
import { useIsFieldEmpty } from '@/object-record/record-field/hooks/useIsFieldEmpty'; import { useIsFieldEmpty } from '@/object-record/record-field/hooks/useIsFieldEmpty';
import { useIsFieldInputOnly } from '@/object-record/record-field/hooks/useIsFieldInputOnly'; import { useIsFieldInputOnly } from '@/object-record/record-field/hooks/useIsFieldInputOnly';
import { RecordInlineCellContainerProps } from '@/object-record/record-inline-cell/components/RecordInlineCellContainer'; import {
RecordInlineCellContextProps,
useRecordInlineCellContext,
} from '@/object-record/record-inline-cell/components/RecordInlineCellContext';
import { RecordInlineCellButton } from '@/object-record/record-inline-cell/components/RecordInlineCellEditButton'; import { RecordInlineCellButton } from '@/object-record/record-inline-cell/components/RecordInlineCellEditButton';
const StyledRecordInlineCellNormalModeOuterContainer = styled.div< const StyledRecordInlineCellNormalModeOuterContainer = styled.div<
Pick< Pick<
RecordInlineCellDisplayModeProps, RecordInlineCellContextProps,
'disableHoverEffect' | 'isDisplayModeFixHeight' | 'isHovered' 'isDisplayModeFixHeight' | 'disableHoverEffect'
> > & { isHovered?: boolean }
>` >`
align-items: center; align-items: center;
border-radius: ${({ theme }) => theme.border.radius.sm}; border-radius: ${({ theme }) => theme.border.radius.sm};
@ -53,23 +56,19 @@ const StyledEmptyField = styled.div`
color: ${({ theme }) => theme.font.color.light}; color: ${({ theme }) => theme.font.color.light};
`; `;
type RecordInlineCellDisplayModeProps = {
disableHoverEffect?: boolean;
isDisplayModeFixHeight?: boolean;
isHovered?: boolean;
emptyPlaceholder?: string;
} & Pick<RecordInlineCellContainerProps, 'buttonIcon' | 'editModeContentOnly'>;
export const RecordInlineCellDisplayMode = ({ export const RecordInlineCellDisplayMode = ({
children, children,
disableHoverEffect, }: React.PropsWithChildren<unknown>) => {
isDisplayModeFixHeight,
emptyPlaceholder = 'Empty',
isHovered,
buttonIcon,
editModeContentOnly,
}: React.PropsWithChildren<RecordInlineCellDisplayModeProps>) => {
const { isFocused } = useFieldFocus(); const { isFocused } = useFieldFocus();
const {
editModeContentOnly,
showLabel,
label,
buttonIcon,
} = useRecordInlineCellContext();
const isDisplayModeContentEmpty = useIsFieldEmpty(); const isDisplayModeContentEmpty = useIsFieldEmpty();
const showEditButton = const showEditButton =
buttonIcon && buttonIcon &&
@ -81,17 +80,15 @@ export const RecordInlineCellDisplayMode = ({
const shouldDisplayEditModeOnFocus = isFocused && isFieldInputOnly; const shouldDisplayEditModeOnFocus = isFocused && isFieldInputOnly;
const emptyPlaceHolder = showLabel ? 'Empty' : label;
return ( return (
<> <>
<StyledRecordInlineCellNormalModeOuterContainer <StyledRecordInlineCellNormalModeOuterContainer isHovered={isFocused}>
disableHoverEffect={disableHoverEffect}
isDisplayModeFixHeight={isDisplayModeFixHeight}
isHovered={isHovered}
>
<StyledRecordInlineCellNormalModeInnerContainer> <StyledRecordInlineCellNormalModeInnerContainer>
{(isDisplayModeContentEmpty && !shouldDisplayEditModeOnFocus) || {(isDisplayModeContentEmpty && !shouldDisplayEditModeOnFocus) ||
!children ? ( !children ? (
<StyledEmptyField>{emptyPlaceholder}</StyledEmptyField> <StyledEmptyField>{emptyPlaceHolder}</StyledEmptyField>
) : ( ) : (
children children
)} )}

View File

@ -1,8 +1,7 @@
import { css } from '@emotion/react'; import { css } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useFieldFocus } from '@/object-record/record-field/hooks/useFieldFocus'; import { useRecordInlineCellContext } from '@/object-record/record-inline-cell/components/RecordInlineCellContext';
import { RecordInlineCellContainerProps } from '@/object-record/record-inline-cell/components/RecordInlineCellContainer';
import { RecordInlineCellDisplayMode } from '@/object-record/record-inline-cell/components/RecordInlineCellDisplayMode'; import { RecordInlineCellDisplayMode } from '@/object-record/record-inline-cell/components/RecordInlineCellDisplayMode';
import { RecordInlineCellEditMode } from '@/object-record/record-inline-cell/components/RecordInlineCellEditMode'; import { RecordInlineCellEditMode } from '@/object-record/record-inline-cell/components/RecordInlineCellEditMode';
import { RecordInlineCellSkeletonLoader } from '@/object-record/record-inline-cell/components/RecordInlineCellSkeletonLoader'; import { RecordInlineCellSkeletonLoader } from '@/object-record/record-inline-cell/components/RecordInlineCellSkeletonLoader';
@ -29,37 +28,16 @@ const StyledClickableContainer = styled.div<{
`}; `};
`; `;
type RecordInlineCellValueProps = Pick< export const RecordInlineCellValue = () => {
RecordInlineCellContainerProps, const {
| 'displayModeContent'
| 'customEditHotkeyScope'
| 'editModeContent'
| 'editModeContentOnly'
| 'isDisplayModeFixHeight'
| 'disableHoverEffect'
| 'readonly'
| 'buttonIcon'
| 'loading'
| 'showLabel'
| 'label'
| 'isCentered'
>;
export const RecordInlineCellValue = ({
displayModeContent, displayModeContent,
customEditHotkeyScope, customEditHotkeyScope,
disableHoverEffect,
editModeContent, editModeContent,
editModeContentOnly, editModeContentOnly,
isDisplayModeFixHeight,
readonly, readonly,
buttonIcon,
loading, loading,
showLabel,
label,
isCentered, isCentered,
}: RecordInlineCellValueProps) => { } = useRecordInlineCellContext();
const { isFocused } = useFieldFocus();
const { isInlineCellInEditMode, openInlineCell } = useInlineCell(); const { isInlineCellInEditMode, openInlineCell } = useInlineCell();
@ -80,12 +58,7 @@ export const RecordInlineCellValue = ({
)} )}
{editModeContentOnly ? ( {editModeContentOnly ? (
<StyledClickableContainer readonly={readonly} isCentered={isCentered}> <StyledClickableContainer readonly={readonly} isCentered={isCentered}>
<RecordInlineCellDisplayMode <RecordInlineCellDisplayMode>
disableHoverEffect={disableHoverEffect}
isDisplayModeFixHeight={isDisplayModeFixHeight}
isHovered={isFocused}
emptyPlaceholder={showLabel ? 'Empty' : label}
>
{editModeContent} {editModeContent}
</RecordInlineCellDisplayMode> </RecordInlineCellDisplayMode>
</StyledClickableContainer> </StyledClickableContainer>
@ -95,14 +68,7 @@ export const RecordInlineCellValue = ({
onClick={handleDisplayModeClick} onClick={handleDisplayModeClick}
isCentered={isCentered} isCentered={isCentered}
> >
<RecordInlineCellDisplayMode <RecordInlineCellDisplayMode>
disableHoverEffect={disableHoverEffect}
isDisplayModeFixHeight={isDisplayModeFixHeight}
isHovered={isFocused}
emptyPlaceholder={showLabel ? 'Empty' : label}
buttonIcon={buttonIcon}
editModeContentOnly={editModeContentOnly}
>
{displayModeContent} {displayModeContent}
</RecordInlineCellDisplayMode> </RecordInlineCellDisplayMode>
</StyledClickableContainer> </StyledClickableContainer>