Refactor kanban new card creation (#8339)

On the kanban page, the record creation was changed a few weeks ago to
enable creation on top / bottom of the columns.

However, this introduced a glitch (missing background opacity). While
fixing it, I have refactored the component structure to:
- separate "New" button from the Empty record card
This commit is contained in:
Charles Bochet 2024-11-05 15:16:38 +01:00 committed by GitHub
parent be8141ce5e
commit 84b0b78b6f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 141 additions and 134 deletions

View File

@ -9,8 +9,9 @@ import { useRecordBoardStates } from '@/object-record/record-board/hooks/interna
import { RecordBoardColumnCardContainerSkeletonLoader } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnCardContainerSkeletonLoader';
import { RecordBoardColumnCardsMemo } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnCardsMemo';
import { RecordBoardColumnFetchMoreLoader } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnFetchMoreLoader';
import { RecordBoardColumnNewButton } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnNewButton';
import { RecordBoardColumnNewOpportunityButton } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnNewOpportunityButton';
import { RecordBoardColumnNewOpportunity } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnNewOpportunity';
import { RecordBoardColumnNewRecord } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnNewRecord';
import { RecordBoardColumnNewRecordButton } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnNewRecordButton';
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
import { useIsOpportunitiesCompanyFieldDisabled } from '@/object-record/record-board/record-board-column/hooks/useIsOpportunitiesCompanyFieldDisabled';
import { getNumberOfCardsPerColumnForSkeletonLoading } from '@/object-record/record-board/record-board-column/utils/getNumberOfCardsPerColumnForSkeletonLoading';
@ -74,6 +75,33 @@ export const RecordBoardColumnCardsContainer = ({
// eslint-disable-next-line react/jsx-props-no-spreading
{...droppableProvided?.droppableProps}
>
<Draggable
draggableId={`new-${columnDefinition.id}-top`}
index={-1}
isDragDisabled={true}
>
{(draggableProvided) => (
<div
ref={draggableProvided?.innerRef}
// eslint-disable-next-line react/jsx-props-no-spreading
{...draggableProvided?.draggableProps}
>
{objectMetadataItem.nameSingular ===
CoreObjectNameSingular.Opportunity &&
!isOpportunitiesCompanyFieldDisabled ? (
<RecordBoardColumnNewOpportunity
columnId={columnDefinition.id}
position="first"
/>
) : (
<RecordBoardColumnNewRecord
columnId={columnDefinition.id}
position="first"
/>
)}
</div>
)}
</Draggable>
{isRecordIndexBoardColumnLoading ? (
Array.from(
{
@ -98,7 +126,7 @@ export const RecordBoardColumnCardsContainer = ({
)}
<RecordBoardColumnFetchMoreLoader />
<Draggable
draggableId={`new-${columnDefinition.id}`}
draggableId={`new-${columnDefinition.id}-bottom`}
index={recordIds.length}
isDragDisabled={true}
>
@ -108,16 +136,23 @@ export const RecordBoardColumnCardsContainer = ({
// eslint-disable-next-line react/jsx-props-no-spreading
{...draggableProvided?.draggableProps}
>
{objectMetadataItem.nameSingular ===
CoreObjectNameSingular.Opportunity &&
!isOpportunitiesCompanyFieldDisabled ? (
<RecordBoardColumnNewOpportunity
columnId={columnDefinition.id}
position="last"
/>
) : (
<RecordBoardColumnNewRecord
columnId={columnDefinition.id}
position="last"
/>
)}
<StyledNewButtonContainer>
{objectMetadataItem.nameSingular ===
CoreObjectNameSingular.Opportunity &&
!isOpportunitiesCompanyFieldDisabled ? (
<RecordBoardColumnNewOpportunityButton
columnId={columnDefinition.id}
/>
) : (
<RecordBoardColumnNewButton columnId={columnDefinition.id} />
)}
<RecordBoardColumnNewRecordButton
columnId={columnDefinition.id}
/>
</StyledNewButtonContainer>
</div>
)}

View File

@ -1,18 +1,16 @@
import styled from '@emotion/styled';
import { useContext, useState } from 'react';
import { IconDotsVertical, IconPlus, LightIconButton, Tag } from 'twenty-ui';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
import { RecordBoardCard } from '@/object-record/record-board/record-board-card/components/RecordBoardCard';
import { RecordBoardColumnDropdownMenu } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnDropdownMenu';
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
import { useColumnNewCardActions } from '@/object-record/record-board/record-board-column/hooks/useColumnNewCardActions';
import { useIsOpportunitiesCompanyFieldDisabled } from '@/object-record/record-board/record-board-column/hooks/useIsOpportunitiesCompanyFieldDisabled';
import { RecordBoardColumnHotkeyScope } from '@/object-record/record-board/types/BoardColumnHotkeyScope';
import { RecordGroupDefinitionType } from '@/object-record/record-group/types/RecordGroupDefinition';
import { SingleEntitySelect } from '@/object-record/relation-picker/components/SingleEntitySelect';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { IconDotsVertical, IconPlus, LightIconButton, Tag } from 'twenty-ui';
const StyledHeader = styled.div`
align-items: center;
@ -102,12 +100,9 @@ export const RecordBoardColumnHeader = () => {
const boardColumnTotal = 0;
const {
newRecord,
handleNewButtonClick,
handleCreateSuccess,
handleEntitySelect,
} = useColumnNewCardActions(columnDefinition?.id ?? '');
const { handleNewButtonClick } = useColumnNewCardActions(
columnDefinition?.id ?? '',
);
const { isOpportunitiesCompanyFieldDisabled } =
useIsOpportunitiesCompanyFieldDisabled();
@ -173,26 +168,6 @@ export const RecordBoardColumnHeader = () => {
stageId={columnDefinition.id}
/>
)}
{newRecord?.isCreating &&
newRecord.position === 'first' &&
(newRecord.isOpportunity ? (
<SingleEntitySelect
disableBackgroundBlur
onCancel={() => handleCreateSuccess('first', columnDefinition.id)}
onEntitySelected={(company) =>
company && handleEntitySelect('first', company)
}
relationObjectNameSingular={CoreObjectNameSingular.Company}
relationPickerScopeId="relation-picker"
selectedRelationRecordIds={[]}
/>
) : (
<RecordBoardCard
isCreating={true}
onCreateSuccess={() => handleCreateSuccess('first')}
position="first"
/>
))}
</StyledColumn>
);
};

View File

@ -0,0 +1,54 @@
import styled from '@emotion/styled';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useAddNewCard } from '@/object-record/record-board/record-board-column/hooks/useAddNewCard';
import { recordBoardNewRecordByColumnIdSelector } from '@/object-record/record-board/states/selectors/recordBoardNewRecordByColumnIdSelector';
import { SingleEntitySelect } from '@/object-record/relation-picker/components/SingleEntitySelect';
import { useRecoilValue } from 'recoil';
const StyledCompanyPickerContainer = styled.div`
align-items: center;
align-self: baseline;
background-color: ${({ theme }) => theme.background.primary};
border: none;
border-radius: ${({ theme }) => theme.border.radius.sm};
color: ${({ theme }) => theme.font.color.tertiary};
cursor: pointer;
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
`;
export const RecordBoardColumnNewOpportunity = ({
columnId,
position,
}: {
columnId: string;
position: 'last' | 'first';
}) => {
const newRecord = useRecoilValue(
recordBoardNewRecordByColumnIdSelector({
familyKey: columnId,
scopeId: columnId,
}),
);
const { handleCreateSuccess, handleEntitySelect } = useAddNewCard();
return (
<>
{newRecord.isCreating && newRecord.position === position && (
<StyledCompanyPickerContainer>
<SingleEntitySelect
disableBackgroundBlur
onCancel={() => handleCreateSuccess(position, columnId, false)}
onEntitySelected={(company) =>
company ? handleEntitySelect(position, company) : null
}
relationObjectNameSingular={CoreObjectNameSingular.Company}
relationPickerScopeId="relation-picker"
selectedRelationRecordIds={[]}
/>
</StyledCompanyPickerContainer>
)}
</>
);
};

View File

@ -1,62 +0,0 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { IconPlus } from 'twenty-ui';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useColumnNewCardActions } from '@/object-record/record-board/record-board-column/hooks/useColumnNewCardActions';
import { SingleEntitySelect } from '@/object-record/relation-picker/components/SingleEntitySelect';
const StyledButton = styled.button`
align-items: center;
align-self: baseline;
background-color: ${({ theme }) => theme.background.primary};
border: none;
border-radius: ${({ theme }) => theme.border.radius.sm};
color: ${({ theme }) => theme.font.color.tertiary};
cursor: pointer;
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
padding: ${({ theme }) => theme.spacing(1)};
&:hover {
background-color: ${({ theme }) => theme.background.tertiary};
}
`;
export const RecordBoardColumnNewOpportunityButton = ({
columnId,
}: {
columnId: string;
}) => {
const theme = useTheme();
const {
newRecord,
handleNewButtonClick,
handleEntitySelect,
handleCreateSuccess,
} = useColumnNewCardActions(columnId);
return (
<>
{newRecord.isCreating &&
newRecord.position === 'last' &&
newRecord.isOpportunity ? (
<SingleEntitySelect
disableBackgroundBlur
onCancel={() => handleCreateSuccess('last', columnId, false)}
onEntitySelected={(company) =>
company ? handleEntitySelect('last', company) : null
}
relationObjectNameSingular={CoreObjectNameSingular.Company}
relationPickerScopeId="relation-picker"
selectedRelationRecordIds={[]}
/>
) : (
<StyledButton onClick={() => handleNewButtonClick('last', true)}>
<IconPlus size={theme.icon.size.md} />
New
</StyledButton>
)}
</>
);
};

View File

@ -0,0 +1,32 @@
import { RecordBoardCard } from '@/object-record/record-board/record-board-card/components/RecordBoardCard';
import { useAddNewCard } from '@/object-record/record-board/record-board-column/hooks/useAddNewCard';
import { recordBoardNewRecordByColumnIdSelector } from '@/object-record/record-board/states/selectors/recordBoardNewRecordByColumnIdSelector';
import { useRecoilValue } from 'recoil';
export const RecordBoardColumnNewRecord = ({
columnId,
position,
}: {
columnId: string;
position: 'first' | 'last';
}) => {
const newRecord = useRecoilValue(
recordBoardNewRecordByColumnIdSelector({
familyKey: columnId,
scopeId: columnId,
}),
);
const { handleCreateSuccess } = useAddNewCard();
return (
<>
{newRecord.isCreating && newRecord.position === position && (
<RecordBoardCard
isCreating={true}
onCreateSuccess={() => handleCreateSuccess(position)}
position={position}
/>
)}
</>
);
};

View File

@ -1,4 +1,3 @@
import { RecordBoardCard } from '@/object-record/record-board/record-board-card/components/RecordBoardCard';
import { useColumnNewCardActions } from '@/object-record/record-board/record-board-column/hooks/useColumnNewCardActions';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
@ -15,34 +14,20 @@ const StyledNewButton = styled.button`
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
padding: ${({ theme }) => theme.spacing(1)};
&:hover {
background-color: ${({ theme }) => theme.background.tertiary};
}
`;
export const RecordBoardColumnNewButton = ({
export const RecordBoardColumnNewRecordButton = ({
columnId,
}: {
columnId: string;
}) => {
const theme = useTheme();
const { newRecord, handleNewButtonClick, handleCreateSuccess } =
useColumnNewCardActions(columnId);
if (
newRecord.isCreating &&
newRecord.position === 'last' &&
!newRecord.isOpportunity
) {
return (
<RecordBoardCard
isCreating={true}
onCreateSuccess={() => handleCreateSuccess('last')}
position="last"
/>
);
}
const { handleNewButtonClick } = useColumnNewCardActions(columnId);
return (
<StyledNewButton onClick={() => handleNewButtonClick('last', false)}>

View File

@ -1,6 +1,5 @@
import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates';
import { useAddNewCard } from '@/object-record/record-board/record-board-column/hooks/useAddNewCard';
import { recordBoardNewRecordByColumnIdSelector } from '@/object-record/record-board/states/selectors/recordBoardNewRecordByColumnIdSelector';
import { useRecoilValue } from 'recoil';
export const useColumnNewCardActions = (columnId: string) => {
@ -12,15 +11,7 @@ export const useColumnNewCardActions = (columnId: string) => {
(field) => field.isLabelIdentifier,
);
const { handleAddNewCardClick, handleCreateSuccess, handleEntitySelect } =
useAddNewCard();
const newRecord = useRecoilValue(
recordBoardNewRecordByColumnIdSelector({
familyKey: columnId,
scopeId: columnId,
}),
);
const { handleAddNewCardClick } = useAddNewCard();
const handleNewButtonClick = (
position: 'first' | 'last',
@ -36,9 +27,6 @@ export const useColumnNewCardActions = (columnId: string) => {
};
return {
newRecord,
handleNewButtonClick,
handleCreateSuccess,
handleEntitySelect,
};
};