Make record name editable on show page (#9172)

When fields are not displayed in show page, title should be editable



https://github.com/user-attachments/assets/049c2998-6944-46c7-9e9e-f6d29da06c6c
This commit is contained in:
Thomas Trompette
2024-12-26 10:59:17 +01:00
committed by GitHub
parent 29d364fa7b
commit f63b0a235a
6 changed files with 137 additions and 10 deletions

View File

@ -0,0 +1,100 @@
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { NavigationDrawerInput } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerInput';
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
import styled from '@emotion/styled';
import { useEffect, useState } from 'react';
import { capitalize } from '~/utils/string/capitalize';
const StyledEditableTitleContainer = styled.div`
align-items: flex-start;
display: flex;
flex-direction: row;
`;
const StyledEditableTitlePrefix = styled.div`
color: ${({ theme }) => theme.font.color.tertiary};
line-height: 24px;
display: flex;
flex-direction: row;
padding: ${({ theme }) => theme.spacing(0.75)};
gap: ${({ theme }) => theme.spacing(1)};
`;
export const RecordEditableName = ({
objectNameSingular,
objectRecordId,
objectLabelPlural,
}: {
objectNameSingular: string;
objectRecordId: string;
objectLabelPlural: string;
}) => {
const [isRenaming, setIsRenaming] = useState(false);
const { record, loading } = useFindOneRecord({
objectNameSingular,
objectRecordId,
recordGqlFields: {
name: true,
},
});
const [recordName, setRecordName] = useState(record?.name);
const { updateOneRecord } = useUpdateOneRecord({
objectNameSingular,
recordGqlFields: {
name: true,
},
});
const handleSubmit = (value: string) => {
updateOneRecord({
idToUpdate: objectRecordId,
updateOneRecordInput: {
name: value,
},
});
setIsRenaming(false);
};
const handleCancel = () => {
setRecordName(record?.name);
setIsRenaming(false);
};
useEffect(() => {
setRecordName(record?.name);
}, [record?.name]);
if (loading) {
return null;
}
return (
<StyledEditableTitleContainer>
<StyledEditableTitlePrefix>
{capitalize(objectLabelPlural)}
<span>{' / '}</span>
</StyledEditableTitlePrefix>
{isRenaming ? (
<NavigationDrawerInput
value={recordName}
onChange={setRecordName}
onSubmit={handleSubmit}
onCancel={handleCancel}
onClickOutside={handleCancel}
hotkeyScope="favorites-folder-input"
/>
) : (
<NavigationDrawerItem
label={recordName}
onClick={() => setIsRenaming(true)}
rightOptions={undefined}
className="navigation-drawer-item"
active
/>
)}
</StyledEditableTitleContainer>
);
};

View File

@ -204,5 +204,6 @@ export const useRecordShowPagePagination = (
navigateToIndexView, navigateToIndexView,
canNavigateToNextRecord, canNavigateToNextRecord,
canNavigateToPreviousRecord, canNavigateToPreviousRecord,
objectMetadataItem,
}; };
}; };

View File

@ -17,7 +17,7 @@ import { useHotkeyScopeOnMount } from '~/hooks/useHotkeyScopeOnMount';
type NavigationDrawerInputProps = { type NavigationDrawerInputProps = {
className?: string; className?: string;
Icon: IconComponent | ((props: TablerIconsProps) => JSX.Element); Icon?: IconComponent | ((props: TablerIconsProps) => JSX.Element);
value: string; value: string;
onChange: (value: string) => void; onChange: (value: string) => void;
onSubmit: (value: string) => void; onSubmit: (value: string) => void;

View File

@ -32,7 +32,7 @@ export type NavigationDrawerItemProps = {
subItemState?: NavigationDrawerSubItemState; subItemState?: NavigationDrawerSubItemState;
to?: string; to?: string;
onClick?: () => void; onClick?: () => void;
Icon: IconComponent | ((props: TablerIconsProps) => JSX.Element); Icon?: IconComponent | ((props: TablerIconsProps) => JSX.Element);
active?: boolean; active?: boolean;
danger?: boolean; danger?: boolean;
soon?: boolean; soon?: boolean;
@ -257,6 +257,7 @@ export const NavigationDrawerItem = ({
const [isNavigationDrawerExpanded, setIsNavigationDrawerExpanded] = const [isNavigationDrawerExpanded, setIsNavigationDrawerExpanded] =
useRecoilState(isNavigationDrawerExpandedState); useRecoilState(isNavigationDrawerExpandedState);
const showBreadcrumb = indentationLevel === 2; const showBreadcrumb = indentationLevel === 2;
const showStyledSpacer = !!soon || !!count || !!keyboard || !!rightOptions;
const handleItemClick = () => { const handleItemClick = () => {
if (isMobile) { if (isMobile) {
@ -320,7 +321,7 @@ export const NavigationDrawerItem = ({
</StyledEllipsisContainer> </StyledEllipsisContainer>
</StyledLabelParent> </StyledLabelParent>
<StyledSpacer /> {showStyledSpacer && <StyledSpacer />}
{soon && ( {soon && (
<NavigationDrawerAnimatedCollapseWrapper> <NavigationDrawerAnimatedCollapseWrapper>
@ -341,8 +342,9 @@ export const NavigationDrawerItem = ({
</StyledKeyBoardShortcut> </StyledKeyBoardShortcut>
</NavigationDrawerAnimatedCollapseWrapper> </NavigationDrawerAnimatedCollapseWrapper>
)} )}
<NavigationDrawerAnimatedCollapseWrapper>
{rightOptions && ( {rightOptions && (
<NavigationDrawerAnimatedCollapseWrapper>
<StyledRightOptionsContainer <StyledRightOptionsContainer
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
@ -358,8 +360,8 @@ export const NavigationDrawerItem = ({
{rightOptions} {rightOptions}
</StyledRightOptionsVisbility> </StyledRightOptionsVisbility>
</StyledRightOptionsContainer> </StyledRightOptionsContainer>
)} </NavigationDrawerAnimatedCollapseWrapper>
</NavigationDrawerAnimatedCollapseWrapper> )}
</StyledItemElementsContainer> </StyledItemElementsContainer>
</StyledItem> </StyledItem>
</StyledNavigationDrawerItemContainer> </StyledNavigationDrawerItemContainer>

View File

@ -1,7 +1,5 @@
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { Select, SelectOption } from '@/ui/input/components/Select'; import { Select, SelectOption } from '@/ui/input/components/Select';
import { WorkflowSingleRecordPicker } from '@/workflow/components/WorkflowSingleRecordPicker';
import { WorkflowStepHeader } from '@/workflow/components/WorkflowStepHeader';
import { WorkflowUpdateRecordAction } from '@/workflow/types/Workflow'; import { WorkflowUpdateRecordAction } from '@/workflow/types/Workflow';
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
@ -15,7 +13,9 @@ import {
import { formatFieldMetadataItemAsFieldDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsFieldDefinition'; import { formatFieldMetadataItemAsFieldDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsFieldDefinition';
import { FormFieldInput } from '@/object-record/record-field/components/FormFieldInput'; import { FormFieldInput } from '@/object-record/record-field/components/FormFieldInput';
import { FormMultiSelectFieldInput } from '@/object-record/record-field/form-types/components/FormMultiSelectFieldInput'; import { FormMultiSelectFieldInput } from '@/object-record/record-field/form-types/components/FormMultiSelectFieldInput';
import { WorkflowSingleRecordPicker } from '@/workflow/components/WorkflowSingleRecordPicker';
import { WorkflowStepBody } from '@/workflow/components/WorkflowStepBody'; import { WorkflowStepBody } from '@/workflow/components/WorkflowStepBody';
import { WorkflowStepHeader } from '@/workflow/components/WorkflowStepHeader';
import { WorkflowVariablePicker } from '@/workflow/components/WorkflowVariablePicker'; import { WorkflowVariablePicker } from '@/workflow/components/WorkflowVariablePicker';
import { JsonValue } from 'type-fest'; import { JsonValue } from 'type-fest';
import { useDebouncedCallback } from 'use-debounce'; import { useDebouncedCallback } from 'use-debounce';
@ -176,6 +176,7 @@ export const WorkflowEditActionFormUpdateRecord = ({
initialTitle={headerTitle} initialTitle={headerTitle}
headerType="Action" headerType="Action"
/> />
<WorkflowStepBody> <WorkflowStepBody>
<Select <Select
dropdownId="workflow-edit-action-record-update-object-name" dropdownId="workflow-edit-action-record-update-object-name"

View File

@ -1,3 +1,6 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { RecordEditableName } from '@/object-record/components/RecordEditableName';
import { useRecordShowContainerTabs } from '@/object-record/record-show/hooks/useRecordShowContainerTabs';
import { useRecordShowPage } from '@/object-record/record-show/hooks/useRecordShowPage'; import { useRecordShowPage } from '@/object-record/record-show/hooks/useRecordShowPage';
import { useRecordShowPagePagination } from '@/object-record/record-show/hooks/useRecordShowPagePagination'; import { useRecordShowPagePagination } from '@/object-record/record-show/hooks/useRecordShowPagePagination';
import { PageHeader } from '@/ui/layout/page/components/PageHeader'; import { PageHeader } from '@/ui/layout/page/components/PageHeader';
@ -17,13 +20,33 @@ export const RecordShowPageHeader = ({
navigateToPreviousRecord, navigateToPreviousRecord,
navigateToNextRecord, navigateToNextRecord,
navigateToIndexView, navigateToIndexView,
objectMetadataItem,
} = useRecordShowPagePagination(objectNameSingular, objectRecordId); } = useRecordShowPagePagination(objectNameSingular, objectRecordId);
const { headerIcon } = useRecordShowPage(objectNameSingular, objectRecordId); const { headerIcon } = useRecordShowPage(objectNameSingular, objectRecordId);
const { layout } = useRecordShowContainerTabs(
false,
objectNameSingular as CoreObjectNameSingular,
false,
objectMetadataItem,
);
const hasEditableName = layout.hideSummaryAndFields === true;
return ( return (
<PageHeader <PageHeader
title={viewName} title={
hasEditableName ? (
<RecordEditableName
objectNameSingular={objectNameSingular}
objectRecordId={objectRecordId}
objectLabelPlural={objectMetadataItem.labelPlural}
/>
) : (
viewName
)
}
hasPaginationButtons hasPaginationButtons
hasClosePageButton hasClosePageButton
onClosePage={navigateToIndexView} onClosePage={navigateToIndexView}