148 cant access note without title from kanban board (#9817)
closes https://github.com/twentyhq/core-team-issues/issues/148 - fixes not openable kanban card when identifier empty - update card behavior (onClick open recordPage) - fixes right click actionDropdown position ## Before https://github.com/user-attachments/assets/696194b8-d7fa-4fc1-a6f9-b46241a262e5  ## After https://github.com/user-attachments/assets/41e296e5-ae16-47f8-b174-7dd21d74188d 
This commit is contained in:
@ -2,48 +2,29 @@ import { useActionMenu } from '@/action-menu/hooks/useActionMenu';
|
||||
import { recordIndexActionMenuDropdownPositionComponentState } from '@/action-menu/states/recordIndexActionMenuDropdownPositionComponentState';
|
||||
import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getActionMenuDropdownIdFromActionMenuId';
|
||||
import { getActionMenuIdFromRecordIndexId } from '@/action-menu/utils/getActionMenuIdFromRecordIndexId';
|
||||
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
||||
import { useRecordBoardSelection } from '@/object-record/record-board/hooks/useRecordBoardSelection';
|
||||
import { RecordBoardCardContext } from '@/object-record/record-board/record-board-card/contexts/RecordBoardCardContext';
|
||||
import { RecordBoardScopeInternalContext } from '@/object-record/record-board/scopes/scope-internal-context/RecordBoardScopeInternalContext';
|
||||
import { isRecordBoardCardSelectedComponentFamilyState } from '@/object-record/record-board/states/isRecordBoardCardSelectedComponentFamilyState';
|
||||
import { isRecordBoardCompactModeActiveComponentState } from '@/object-record/record-board/states/isRecordBoardCompactModeActiveComponentState';
|
||||
import { recordBoardVisibleFieldDefinitionsComponentSelector } from '@/object-record/record-board/states/selectors/recordBoardVisibleFieldDefinitionsComponentSelector';
|
||||
import {
|
||||
FieldContext,
|
||||
RecordUpdateHook,
|
||||
RecordUpdateHookParams,
|
||||
} from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { getFieldButtonIcon } from '@/object-record/record-field/utils/getFieldButtonIcon';
|
||||
import { RecordIdentifierChip } from '@/object-record/record-index/components/RecordIndexRecordChip';
|
||||
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
|
||||
import { RecordInlineCellEditMode } from '@/object-record/record-inline-cell/components/RecordInlineCellEditMode';
|
||||
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
|
||||
import { RecordValueSetterEffect } from '@/object-record/record-store/components/RecordValueSetterEffect';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
||||
import { RecordBoardScrollWrapperContext } from '@/ui/utilities/scroll/contexts/ScrollWrapperContexts';
|
||||
import { useRecoilComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyStateV2';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
|
||||
import styled from '@emotion/styled';
|
||||
import { ReactNode, useContext, useState } from 'react';
|
||||
import { useContext, useState } from 'react';
|
||||
import { InView, useInView } from 'react-intersection-observer';
|
||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
import {
|
||||
AnimatedEaseInOut,
|
||||
AvatarChipVariant,
|
||||
Checkbox,
|
||||
CheckboxVariant,
|
||||
IconEye,
|
||||
IconEyeOff,
|
||||
LightIconButton,
|
||||
} from 'twenty-ui';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { AnimatedEaseInOut } from 'twenty-ui';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
import { useAddNewCard } from '../../record-board-column/hooks/useAddNewCard';
|
||||
import { RecordBoardCardBody } from '@/object-record/record-board/record-board-card/components/RecordBoardCardBody';
|
||||
import { RecordBoardCardHeader } from '@/object-record/record-board/record-board-card/components/RecordBoardCardHeader';
|
||||
import { useNavigateApp } from '~/hooks/useNavigateApp';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
|
||||
const StyledBoardCard = styled.div<{ selected: boolean }>`
|
||||
background-color: ${({ theme, selected }) =>
|
||||
@ -81,76 +62,11 @@ const StyledBoardCard = styled.div<{ selected: boolean }>`
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledTextInput = styled(TextInput)`
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
width: ${({ theme }) => theme.spacing(53)};
|
||||
`;
|
||||
|
||||
const StyledBoardCardWrapper = styled.div`
|
||||
padding-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const StyledBoardCardHeader = styled.div<{
|
||||
showCompactView: boolean;
|
||||
}>`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
height: 24px;
|
||||
padding-bottom: ${({ theme, showCompactView }) =>
|
||||
theme.spacing(showCompactView ? 2 : 1)};
|
||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||
padding-right: ${({ theme }) => theme.spacing(2)};
|
||||
padding-top: ${({ theme }) => theme.spacing(2)};
|
||||
transition: padding ease-in-out 160ms;
|
||||
|
||||
img {
|
||||
height: ${({ theme }) => theme.icon.size.md}px;
|
||||
object-fit: cover;
|
||||
width: ${({ theme }) => theme.icon.size.md}px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledBoardCardBody = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${({ theme }) => theme.spacing(0.5)};
|
||||
padding-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
padding-left: ${({ theme }) => theme.spacing(2.5)};
|
||||
padding-right: ${({ theme }) => theme.spacing(2)};
|
||||
span {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
svg {
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
margin-right: ${({ theme }) => theme.spacing(2)};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledCheckboxContainer = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: end;
|
||||
`;
|
||||
|
||||
const StyledFieldContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: fit-content;
|
||||
max-width: 100%;
|
||||
`;
|
||||
|
||||
const StyledCompactIconContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-left: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
export const RecordBoardCard = ({
|
||||
isCreating = false,
|
||||
onCreateSuccess,
|
||||
@ -160,15 +76,10 @@ export const RecordBoardCard = ({
|
||||
onCreateSuccess?: () => void;
|
||||
position?: 'first' | 'last';
|
||||
}) => {
|
||||
const navigate = useNavigateApp();
|
||||
|
||||
const { recordId } = useContext(RecordBoardCardContext);
|
||||
|
||||
const [newLabelValue, setNewLabelValue] = useState('');
|
||||
|
||||
const { handleBlur, handleInputEnter } = useAddNewCard();
|
||||
|
||||
const { updateOneRecord, objectMetadataItem } =
|
||||
useContext(RecordBoardContext);
|
||||
|
||||
const visibleFieldDefinitions = useRecoilComponentValueV2(
|
||||
recordBoardVisibleFieldDefinitionsComponentSelector,
|
||||
);
|
||||
@ -185,16 +96,12 @@ export const RecordBoardCard = ({
|
||||
recordId,
|
||||
);
|
||||
|
||||
const record = useRecoilValue(recordStoreFamilyState(recordId));
|
||||
const { indexIdentifierUrl } = useRecordIndexContextOrThrow();
|
||||
const { objectNameSingular } = useRecordIndexContextOrThrow();
|
||||
|
||||
const recordBoardId = useAvailableScopeIdOrThrow(
|
||||
RecordBoardScopeInternalContext,
|
||||
);
|
||||
|
||||
const { checkIfLastUnselectAndCloseDropdown } =
|
||||
useRecordBoardSelection(recordBoardId);
|
||||
|
||||
const actionMenuId = getActionMenuIdFromRecordIndexId(recordBoardId);
|
||||
|
||||
const actionMenuDropdownId =
|
||||
@ -221,42 +128,19 @@ export const RecordBoardCard = ({
|
||||
|
||||
const handleCardClick = () => {
|
||||
if (!isCreating) {
|
||||
setIsCurrentCardSelected(!isCurrentCardSelected);
|
||||
checkIfLastUnselectAndCloseDropdown();
|
||||
navigate(AppPath.RecordShowPage, {
|
||||
objectNameSingular,
|
||||
objectRecordId: recordId,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const PreventSelectOnClickContainer = ({
|
||||
children,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
}) => (
|
||||
<StyledFieldContainer
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</StyledFieldContainer>
|
||||
);
|
||||
|
||||
const onMouseLeaveBoard = useDebouncedCallback(() => {
|
||||
if (isCompactModeActive && isCardExpanded) {
|
||||
setIsCardExpanded(false);
|
||||
}
|
||||
}, 800);
|
||||
|
||||
const useUpdateOneRecordHook: RecordUpdateHook = () => {
|
||||
const updateEntity = ({ variables }: RecordUpdateHookParams) => {
|
||||
updateOneRecord?.({
|
||||
idToUpdate: variables.where.id as string,
|
||||
updateOneRecordInput: variables.updateOneRecordInput,
|
||||
});
|
||||
};
|
||||
|
||||
return [updateEntity, { loading: false }];
|
||||
};
|
||||
|
||||
const scrollWrapperRef = useContext(RecordBoardScrollWrapperContext);
|
||||
|
||||
const { ref: cardRef } = useInView({
|
||||
@ -285,110 +169,23 @@ export const RecordBoardCard = ({
|
||||
onMouseLeave={onMouseLeaveBoard}
|
||||
onClick={handleCardClick}
|
||||
>
|
||||
<StyledBoardCardHeader showCompactView={isCompactModeActive}>
|
||||
{isCreating && position !== undefined ? (
|
||||
<RecordInlineCellEditMode>
|
||||
<StyledTextInput
|
||||
autoFocus
|
||||
value={newLabelValue}
|
||||
onInputEnter={() =>
|
||||
handleInputEnter(
|
||||
labelIdentifierField?.label ?? '',
|
||||
newLabelValue,
|
||||
position,
|
||||
onCreateSuccess,
|
||||
)
|
||||
}
|
||||
onBlur={() =>
|
||||
handleBlur(
|
||||
labelIdentifierField?.label ?? '',
|
||||
newLabelValue,
|
||||
position,
|
||||
onCreateSuccess,
|
||||
)
|
||||
}
|
||||
onChange={(text: string) => setNewLabelValue(text)}
|
||||
placeholder={labelIdentifierField?.label}
|
||||
/>
|
||||
</RecordInlineCellEditMode>
|
||||
) : (
|
||||
<RecordIdentifierChip
|
||||
objectNameSingular={objectMetadataItem.nameSingular}
|
||||
record={record as ObjectRecord}
|
||||
variant={AvatarChipVariant.Transparent}
|
||||
maxWidth={150}
|
||||
to={indexIdentifierUrl(recordId)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!isCreating && (
|
||||
<>
|
||||
{isCompactModeActive && (
|
||||
<StyledCompactIconContainer className="compact-icon-container">
|
||||
<LightIconButton
|
||||
Icon={isCardExpanded ? IconEyeOff : IconEye}
|
||||
accent="tertiary"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setIsCardExpanded((prev) => !prev);
|
||||
}}
|
||||
/>
|
||||
</StyledCompactIconContainer>
|
||||
)}
|
||||
|
||||
<StyledCheckboxContainer className="checkbox-container">
|
||||
<Checkbox
|
||||
hoverable
|
||||
checked={isCurrentCardSelected}
|
||||
onChange={() =>
|
||||
setIsCurrentCardSelected(!isCurrentCardSelected)
|
||||
}
|
||||
variant={CheckboxVariant.Secondary}
|
||||
/>
|
||||
</StyledCheckboxContainer>
|
||||
</>
|
||||
)}
|
||||
</StyledBoardCardHeader>
|
||||
|
||||
{labelIdentifierField && (
|
||||
<RecordBoardCardHeader
|
||||
identifierFieldDefinition={labelIdentifierField}
|
||||
isCreating={isCreating}
|
||||
onCreateSuccess={onCreateSuccess}
|
||||
position={position}
|
||||
isCardExpanded={isCardExpanded}
|
||||
setIsCardExpanded={setIsCardExpanded}
|
||||
/>
|
||||
)}
|
||||
<AnimatedEaseInOut
|
||||
isOpen={isCardExpanded || !isCompactModeActive}
|
||||
initial={false}
|
||||
>
|
||||
<StyledBoardCardBody>
|
||||
{visibleFieldDefinitionsFiltered.map((fieldDefinition) => (
|
||||
<PreventSelectOnClickContainer
|
||||
key={fieldDefinition.fieldMetadataId}
|
||||
>
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
recordId: isCreating ? '' : recordId,
|
||||
maxWidth: 156,
|
||||
recoilScopeId:
|
||||
(isCreating ? 'new' : recordId) +
|
||||
fieldDefinition.fieldMetadataId,
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition: {
|
||||
disableTooltip: false,
|
||||
fieldMetadataId: fieldDefinition.fieldMetadataId,
|
||||
label: fieldDefinition.label,
|
||||
iconName: fieldDefinition.iconName,
|
||||
type: fieldDefinition.type,
|
||||
metadata: fieldDefinition.metadata,
|
||||
defaultValue: fieldDefinition.defaultValue,
|
||||
editButtonIcon: getFieldButtonIcon({
|
||||
metadata: fieldDefinition.metadata,
|
||||
type: fieldDefinition.type,
|
||||
}),
|
||||
},
|
||||
useUpdateRecord: useUpdateOneRecordHook,
|
||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||
}}
|
||||
>
|
||||
<RecordInlineCell />
|
||||
</FieldContext.Provider>
|
||||
</PreventSelectOnClickContainer>
|
||||
))}
|
||||
</StyledBoardCardBody>
|
||||
<RecordBoardCardBody
|
||||
fieldDefinitions={visibleFieldDefinitionsFiltered}
|
||||
/>
|
||||
</AnimatedEaseInOut>
|
||||
</StyledBoardCard>
|
||||
</InView>
|
||||
|
||||
@ -0,0 +1,71 @@
|
||||
import { RecordBoardFieldDefinition } from '@/object-record/record-board/types/RecordBoardFieldDefinition';
|
||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import {
|
||||
FieldContext,
|
||||
RecordUpdateHook,
|
||||
RecordUpdateHookParams,
|
||||
} from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { getFieldButtonIcon } from '@/object-record/record-field/utils/getFieldButtonIcon';
|
||||
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
|
||||
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
|
||||
import { StopPropagationContainer } from '@/object-record/record-board/record-board-card/components/StopPropagationContainer';
|
||||
import { useContext } from 'react';
|
||||
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
||||
import { RecordBoardCardBodyContainer } from '@/object-record/record-board/record-board-card/components/RecordBoardCardBodyContainer';
|
||||
import { RecordBoardCardContext } from '@/object-record/record-board/record-board-card/contexts/RecordBoardCardContext';
|
||||
|
||||
export const RecordBoardCardBody = ({
|
||||
fieldDefinitions,
|
||||
}: {
|
||||
fieldDefinitions: RecordBoardFieldDefinition<FieldMetadata>[];
|
||||
}) => {
|
||||
const { recordId } = useContext(RecordBoardCardContext);
|
||||
|
||||
const { updateOneRecord } = useContext(RecordBoardContext);
|
||||
|
||||
const useUpdateOneRecordHook: RecordUpdateHook = () => {
|
||||
const updateEntity = ({ variables }: RecordUpdateHookParams) => {
|
||||
updateOneRecord?.({
|
||||
idToUpdate: variables.where.id as string,
|
||||
updateOneRecordInput: variables.updateOneRecordInput,
|
||||
});
|
||||
};
|
||||
|
||||
return [updateEntity, { loading: false }];
|
||||
};
|
||||
|
||||
return (
|
||||
<RecordBoardCardBodyContainer>
|
||||
{fieldDefinitions.map((fieldDefinition) => (
|
||||
<StopPropagationContainer key={fieldDefinition.fieldMetadataId}>
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
recordId,
|
||||
maxWidth: 156,
|
||||
recoilScopeId:
|
||||
(recordId || 'new') + fieldDefinition.fieldMetadataId,
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition: {
|
||||
disableTooltip: false,
|
||||
fieldMetadataId: fieldDefinition.fieldMetadataId,
|
||||
label: fieldDefinition.label,
|
||||
iconName: fieldDefinition.iconName,
|
||||
type: fieldDefinition.type,
|
||||
metadata: fieldDefinition.metadata,
|
||||
defaultValue: fieldDefinition.defaultValue,
|
||||
editButtonIcon: getFieldButtonIcon({
|
||||
metadata: fieldDefinition.metadata,
|
||||
type: fieldDefinition.type,
|
||||
}),
|
||||
},
|
||||
useUpdateRecord: useUpdateOneRecordHook,
|
||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||
}}
|
||||
>
|
||||
<RecordInlineCell />
|
||||
</FieldContext.Provider>
|
||||
</StopPropagationContainer>
|
||||
))}
|
||||
</RecordBoardCardBodyContainer>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,21 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const StyledBoardCardBody = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${({ theme }) => theme.spacing(0.5)};
|
||||
padding-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
padding-left: ${({ theme }) => theme.spacing(2.5)};
|
||||
padding-right: ${({ theme }) => theme.spacing(2)};
|
||||
span {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
svg {
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
margin-right: ${({ theme }) => theme.spacing(2)};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export { StyledBoardCardBody as RecordBoardCardBodyContainer };
|
||||
@ -0,0 +1,218 @@
|
||||
import {
|
||||
AvatarChipVariant,
|
||||
Checkbox,
|
||||
CheckboxVariant,
|
||||
LightIconButton,
|
||||
IconEye,
|
||||
IconEyeOff,
|
||||
} from 'twenty-ui';
|
||||
import { RecordBoardCardHeaderContainer } from '@/object-record/record-board/record-board-card/components/RecordBoardCardHeaderContainer';
|
||||
import { RecordInlineCellEditMode } from '@/object-record/record-inline-cell/components/RecordInlineCellEditMode';
|
||||
import styled from '@emotion/styled';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { Dispatch, SetStateAction, useContext, useState } from 'react';
|
||||
import { useAddNewCard } from '@/object-record/record-board/record-board-column/hooks/useAddNewCard';
|
||||
import { RecordBoardFieldDefinition } from '@/object-record/record-board/types/RecordBoardFieldDefinition';
|
||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import {
|
||||
FieldContext,
|
||||
RecordUpdateHook,
|
||||
RecordUpdateHookParams,
|
||||
} from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { getFieldButtonIcon } from '@/object-record/record-field/utils/getFieldButtonIcon';
|
||||
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
|
||||
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
||||
import { RecordBoardCardContext } from '@/object-record/record-board/record-board-card/contexts/RecordBoardCardContext';
|
||||
import { RecordIdentifierChip } from '@/object-record/record-index/components/RecordIndexRecordChip';
|
||||
import { useRecoilComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyStateV2';
|
||||
import { isRecordBoardCardSelectedComponentFamilyState } from '@/object-record/record-board/states/isRecordBoardCardSelectedComponentFamilyState';
|
||||
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
||||
import { RecordBoardScopeInternalContext } from '@/object-record/record-board/scopes/scope-internal-context/RecordBoardScopeInternalContext';
|
||||
import { useRecordBoardSelection } from '@/object-record/record-board/hooks/useRecordBoardSelection';
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { isRecordBoardCompactModeActiveComponentState } from '@/object-record/record-board/states/isRecordBoardCompactModeActiveComponentState';
|
||||
import { StopPropagationContainer } from '@/object-record/record-board/record-board-card/components/StopPropagationContainer';
|
||||
|
||||
const StyledTextInput = styled(TextInput)`
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
width: ${({ theme }) => theme.spacing(53)};
|
||||
`;
|
||||
|
||||
const StyledCompactIconContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-left: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const StyledCheckboxContainer = styled.div`
|
||||
margin-left: auto;
|
||||
`;
|
||||
|
||||
type RecordBoardCardHeaderProps = {
|
||||
isCreating?: boolean;
|
||||
onCreateSuccess?: () => void;
|
||||
position?: 'first' | 'last';
|
||||
identifierFieldDefinition: RecordBoardFieldDefinition<FieldMetadata>;
|
||||
isCardExpanded?: boolean;
|
||||
setIsCardExpanded?: Dispatch<SetStateAction<boolean>>;
|
||||
};
|
||||
|
||||
export const RecordBoardCardHeader = ({
|
||||
isCreating = false,
|
||||
onCreateSuccess,
|
||||
position,
|
||||
identifierFieldDefinition,
|
||||
isCardExpanded,
|
||||
setIsCardExpanded,
|
||||
}: RecordBoardCardHeaderProps) => {
|
||||
const [newLabelValue, setNewLabelValue] = useState('');
|
||||
|
||||
const { handleBlur, handleInputEnter } = useAddNewCard();
|
||||
|
||||
const { recordId } = useContext(RecordBoardCardContext);
|
||||
|
||||
const { indexIdentifierUrl } = useRecordIndexContextOrThrow();
|
||||
|
||||
const record = useRecoilValue(recordStoreFamilyState(recordId));
|
||||
|
||||
const { updateOneRecord, objectMetadataItem } =
|
||||
useContext(RecordBoardContext);
|
||||
|
||||
const recordBoardId = useAvailableScopeIdOrThrow(
|
||||
RecordBoardScopeInternalContext,
|
||||
);
|
||||
|
||||
const showCompactView = useRecoilComponentValueV2(
|
||||
isRecordBoardCompactModeActiveComponentState,
|
||||
);
|
||||
|
||||
const isIdentifierEmpty =
|
||||
(record?.[identifierFieldDefinition.metadata.fieldName] || '').trim() ===
|
||||
'';
|
||||
|
||||
const { checkIfLastUnselectAndCloseDropdown } =
|
||||
useRecordBoardSelection(recordBoardId);
|
||||
|
||||
const [isCurrentCardSelected, setIsCurrentCardSelected] =
|
||||
useRecoilComponentFamilyStateV2(
|
||||
isRecordBoardCardSelectedComponentFamilyState,
|
||||
recordId,
|
||||
);
|
||||
|
||||
const useUpdateOneRecordHook: RecordUpdateHook = () => {
|
||||
const updateEntity = ({ variables }: RecordUpdateHookParams) => {
|
||||
updateOneRecord?.({
|
||||
idToUpdate: variables.where.id as string,
|
||||
updateOneRecordInput: variables.updateOneRecordInput,
|
||||
});
|
||||
};
|
||||
|
||||
return [updateEntity, { loading: false }];
|
||||
};
|
||||
|
||||
return (
|
||||
<RecordBoardCardHeaderContainer showCompactView={showCompactView}>
|
||||
<StopPropagationContainer>
|
||||
{isCreating && position !== undefined ? (
|
||||
<RecordInlineCellEditMode>
|
||||
<StyledTextInput
|
||||
autoFocus
|
||||
value={newLabelValue}
|
||||
onInputEnter={() =>
|
||||
handleInputEnter(
|
||||
identifierFieldDefinition.label ?? '',
|
||||
newLabelValue,
|
||||
position,
|
||||
onCreateSuccess,
|
||||
)
|
||||
}
|
||||
onBlur={() =>
|
||||
handleBlur(
|
||||
identifierFieldDefinition.label ?? '',
|
||||
newLabelValue,
|
||||
position,
|
||||
onCreateSuccess,
|
||||
)
|
||||
}
|
||||
onChange={(text: string) => setNewLabelValue(text)}
|
||||
placeholder={identifierFieldDefinition.label}
|
||||
/>
|
||||
</RecordInlineCellEditMode>
|
||||
) : isIdentifierEmpty ? (
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
recordId: (record as ObjectRecord).id,
|
||||
maxWidth: 156,
|
||||
recoilScopeId:
|
||||
(isCreating ? 'new' : recordId) +
|
||||
identifierFieldDefinition.fieldMetadataId,
|
||||
isLabelIdentifier: true,
|
||||
fieldDefinition: {
|
||||
disableTooltip: false,
|
||||
fieldMetadataId: identifierFieldDefinition.fieldMetadataId,
|
||||
label: `Set ${identifierFieldDefinition.label}`,
|
||||
iconName: identifierFieldDefinition.iconName,
|
||||
type: identifierFieldDefinition.type,
|
||||
metadata: identifierFieldDefinition.metadata,
|
||||
defaultValue: identifierFieldDefinition.defaultValue,
|
||||
editButtonIcon: getFieldButtonIcon({
|
||||
metadata: identifierFieldDefinition.metadata,
|
||||
type: identifierFieldDefinition.type,
|
||||
}),
|
||||
},
|
||||
useUpdateRecord: useUpdateOneRecordHook,
|
||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||
}}
|
||||
>
|
||||
<RecordInlineCell />
|
||||
</FieldContext.Provider>
|
||||
) : (
|
||||
<RecordIdentifierChip
|
||||
objectNameSingular={objectMetadataItem.nameSingular}
|
||||
record={record as ObjectRecord}
|
||||
variant={AvatarChipVariant.Transparent}
|
||||
maxWidth={150}
|
||||
to={indexIdentifierUrl(recordId)}
|
||||
/>
|
||||
)}
|
||||
</StopPropagationContainer>
|
||||
|
||||
{!isCreating && (
|
||||
<>
|
||||
{showCompactView && (
|
||||
<StyledCompactIconContainer className="compact-icon-container">
|
||||
<StopPropagationContainer>
|
||||
<LightIconButton
|
||||
Icon={isCardExpanded ? IconEyeOff : IconEye}
|
||||
accent="tertiary"
|
||||
onClick={() => {
|
||||
setIsCardExpanded?.((prev) => !prev);
|
||||
}}
|
||||
/>
|
||||
</StopPropagationContainer>
|
||||
</StyledCompactIconContainer>
|
||||
)}
|
||||
<StyledCheckboxContainer className="checkbox-container">
|
||||
<StopPropagationContainer>
|
||||
<Checkbox
|
||||
hoverable
|
||||
checked={isCurrentCardSelected}
|
||||
onChange={() => {
|
||||
setIsCurrentCardSelected(!isCurrentCardSelected);
|
||||
checkIfLastUnselectAndCloseDropdown();
|
||||
}}
|
||||
variant={CheckboxVariant.Secondary}
|
||||
/>
|
||||
</StopPropagationContainer>
|
||||
</StyledCheckboxContainer>
|
||||
</>
|
||||
)}
|
||||
</RecordBoardCardHeaderContainer>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,26 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const StyledBoardCardHeader = styled.div<{
|
||||
showCompactView: boolean;
|
||||
}>`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
height: 24px;
|
||||
padding-bottom: ${({ theme, showCompactView }) =>
|
||||
theme.spacing(showCompactView ? 2 : 1)};
|
||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||
padding-right: ${({ theme }) => theme.spacing(2)};
|
||||
padding-top: ${({ theme }) => theme.spacing(2)};
|
||||
transition: padding ease-in-out 160ms;
|
||||
|
||||
img {
|
||||
height: ${({ theme }) => theme.icon.size.md}px;
|
||||
object-fit: cover;
|
||||
width: ${({ theme }) => theme.icon.size.md}px;
|
||||
}
|
||||
`;
|
||||
|
||||
export { StyledBoardCardHeader as RecordBoardCardHeaderContainer };
|
||||
@ -0,0 +1,23 @@
|
||||
import { ReactNode } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const StyledFieldContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: fit-content;
|
||||
max-width: 100%;
|
||||
`;
|
||||
|
||||
export const StopPropagationContainer = ({
|
||||
children,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
}) => (
|
||||
<StyledFieldContainer
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</StyledFieldContainer>
|
||||
);
|
||||
@ -3,11 +3,8 @@ import styled from '@emotion/styled';
|
||||
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
|
||||
|
||||
import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader';
|
||||
import {
|
||||
StyledBoardCardBody,
|
||||
StyledBoardCardHeader,
|
||||
} from '@/object-record/record-board/record-board-card/components/RecordBoardCard';
|
||||
|
||||
import { RecordBoardCardBodyContainer } from '@/object-record/record-board/record-board-card/components/RecordBoardCardBodyContainer';
|
||||
import { RecordBoardCardHeaderContainer } from '@/object-record/record-board/record-board-card/components/RecordBoardCardHeaderContainer';
|
||||
const StyledSkeletonIconAndText = styled.div`
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
@ -42,18 +39,18 @@ export const RecordBoardColumnCardContainerSkeletonLoader = ({
|
||||
highlightColor={theme.background.transparent.lighter}
|
||||
borderRadius={4}
|
||||
>
|
||||
<StyledBoardCardHeader showCompactView={isCompactModeActive}>
|
||||
<RecordBoardCardHeaderContainer showCompactView={isCompactModeActive}>
|
||||
<StyledSkeletonTitle>
|
||||
<Skeleton
|
||||
width={titleSkeletonWidth}
|
||||
height={SKELETON_LOADER_HEIGHT_SIZES.standard.s}
|
||||
/>
|
||||
</StyledSkeletonTitle>
|
||||
</StyledBoardCardHeader>
|
||||
</RecordBoardCardHeaderContainer>
|
||||
<StyledSeparator />
|
||||
{!isCompactModeActive &&
|
||||
skeletonItems.map(({ id }) => (
|
||||
<StyledBoardCardBody key={id}>
|
||||
<RecordBoardCardBodyContainer key={id}>
|
||||
<StyledSkeletonIconAndText>
|
||||
<Skeleton
|
||||
width={16}
|
||||
@ -64,7 +61,7 @@ export const RecordBoardColumnCardContainerSkeletonLoader = ({
|
||||
height={SKELETON_LOADER_HEIGHT_SIZES.standard.s}
|
||||
/>
|
||||
</StyledSkeletonIconAndText>
|
||||
</StyledBoardCardBody>
|
||||
</RecordBoardCardBodyContainer>
|
||||
))}
|
||||
</SkeletonTheme>
|
||||
);
|
||||
|
||||
@ -26,7 +26,7 @@ import { useDropdown } from '../hooks/useDropdown';
|
||||
|
||||
const StyledDropdownFallbackAnchor = styled.div`
|
||||
left: 0;
|
||||
position: absolute;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
`;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user