diff --git a/packages/twenty-front/src/modules/activities/calendar/components/CalendarEventDetails.tsx b/packages/twenty-front/src/modules/activities/calendar/components/CalendarEventDetails.tsx index 641a99d68..762cc0b97 100644 --- a/packages/twenty-front/src/modules/activities/calendar/components/CalendarEventDetails.tsx +++ b/packages/twenty-front/src/modules/activities/calendar/components/CalendarEventDetails.tsx @@ -125,7 +125,7 @@ export const CalendarEventDetails = ({ size={ChipSize.Large} variant={ChipVariant.Highlighted} clickable={false} - leftComponent={} + leftComponent={() => } label="Event" /> diff --git a/packages/twenty-front/src/modules/activities/emails/components/MessageThreadSubscribersChip.tsx b/packages/twenty-front/src/modules/activities/emails/components/MessageThreadSubscribersChip.tsx index 6ef97d07b..a10b37781 100644 --- a/packages/twenty-front/src/modules/activities/emails/components/MessageThreadSubscribersChip.tsx +++ b/packages/twenty-front/src/modules/activities/emails/components/MessageThreadSubscribersChip.tsx @@ -51,16 +51,20 @@ export const MessageThreadSubscribersChip = ({ - ) : ( + leftComponent={() => { + if (isOnlyOneSubscriber) { + return ( + + ); + } + + return ( ( ))} /> - ) - } - rightComponent={} + ); + }} + rightComponent={() => } clickable /> ); diff --git a/packages/twenty-front/src/modules/activities/files/components/AttachmentRow.tsx b/packages/twenty-front/src/modules/activities/files/components/AttachmentRow.tsx index a54d74681..404cb9403 100644 --- a/packages/twenty-front/src/modules/activities/files/components/AttachmentRow.tsx +++ b/packages/twenty-front/src/modules/activities/files/components/AttachmentRow.tsx @@ -16,7 +16,11 @@ import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { useMemo, useState } from 'react'; import { isDefined } from 'twenty-shared'; -import { IconCalendar, OverflowingTextWithTooltip } from 'twenty-ui'; +import { + IconCalendar, + OverflowingTextWithTooltip, + isModifiedEvent, +} from 'twenty-ui'; import { formatToHumanReadableDate } from '~/utils/date-utils'; import { getFileNameAndExtension } from '~/utils/file/getFileNameAndExtension'; @@ -145,7 +149,7 @@ export const AttachmentRow = ({ const handleOpenDocument = (e: React.MouseEvent) => { // Cmd/Ctrl+click opens new tab, right click opens context menu - if (e.metaKey || e.ctrlKey || e.button === 2) { + if (isModifiedEvent(e) || e.button === 2) { return; } diff --git a/packages/twenty-front/src/modules/object-record/components/RecordChip.tsx b/packages/twenty-front/src/modules/object-record/components/RecordChip.tsx index cb7acb410..bbc54d04f 100644 --- a/packages/twenty-front/src/modules/object-record/components/RecordChip.tsx +++ b/packages/twenty-front/src/modules/object-record/components/RecordChip.tsx @@ -1,4 +1,10 @@ -import { AvatarChip, AvatarChipVariant } from 'twenty-ui'; +import { + AvatarChip, + AvatarChipVariant, + ChipSize, + LinkAvatarChip, + isModifiedEvent, +} from 'twenty-ui'; import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; import { getLinkToShowPage } from '@/object-metadata/utils/getLinkToShowPage'; @@ -6,14 +12,16 @@ import { useRecordChipData } from '@/object-record/hooks/useRecordChipData'; import { recordIndexOpenRecordInSelector } from '@/object-record/record-index/states/selectors/recordIndexOpenRecordInSelector'; import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType'; -import { MouseEvent } from 'react'; import { useRecoilValue } from 'recoil'; - export type RecordChipProps = { objectNameSingular: string; record: ObjectRecord; className?: string; variant?: AvatarChipVariant; + forceDisableClick?: boolean; + maxWidth?: number; + to?: string | undefined; + size?: ChipSize; }; export const RecordChip = ({ @@ -21,6 +29,10 @@ export const RecordChip = ({ record, className, variant, + maxWidth, + to, + size, + forceDisableClick = false, }: RecordChipProps) => { const { recordChipData } = useRecordChipData({ objectNameSingular, @@ -33,30 +45,52 @@ export const RecordChip = ({ recordIndexOpenRecordInSelector, ); - const handleClick = (e: MouseEvent) => { - e.stopPropagation(); - if (recordIndexOpenRecordIn === ViewOpenRecordInType.SIDE_PANEL) { - openRecordInCommandMenu({ - recordId: record.id, - objectNameSingular, - }); - } - }; + // TODO temporary until we create a record show page for Workspaces members + if (forceDisableClick) { + return ( + + ); + } + + const isSidePanelViewOpenRecordInType = + recordIndexOpenRecordIn === ViewOpenRecordInType.SIDE_PANEL; + const onClick = isSidePanelViewOpenRecordInType + ? () => + openRecordInCommandMenu({ + recordId: record.id, + objectNameSingular, + }) + : undefined; return ( - { + // TODO refactor wrapper event listener to avoid colliding events + clickEvent.stopPropagation(); + + const isModifiedEventResult = isModifiedEvent(clickEvent); + if (isSidePanelViewOpenRecordInType && !isModifiedEventResult) { + clickEvent.preventDefault(); + onClick?.(); + } + }} /> ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx index 71b6764f3..3ba3df1bd 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx @@ -8,8 +8,11 @@ import { isRecordBoardCardSelectedComponentFamilyState } from '@/object-record/r import { isRecordBoardCompactModeActiveComponentState } from '@/object-record/record-board/states/isRecordBoardCompactModeActiveComponentState'; import { recordBoardVisibleFieldDefinitionsComponentSelector } from '@/object-record/record-board/states/selectors/recordBoardVisibleFieldDefinitionsComponentSelector'; +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 { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext'; import { RecordValueSetterEffect } from '@/object-record/record-store/components/RecordValueSetterEffect'; +import { AppPath } from '@/types/AppPath'; 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'; @@ -19,12 +22,10 @@ import styled from '@emotion/styled'; import { useContext, useState } from 'react'; import { InView, useInView } from 'react-intersection-observer'; import { useSetRecoilState } from 'recoil'; +import { isDefined } from 'twenty-shared'; import { AnimatedEaseInOut } from 'twenty-ui'; import { useDebouncedCallback } from 'use-debounce'; -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 }) => @@ -169,7 +170,7 @@ export const RecordBoardCard = ({ onMouseLeave={onMouseLeaveBoard} onClick={handleCardClick} > - {labelIdentifierField && ( + {isDefined(labelIdentifierField) && ( @@ -156,7 +153,7 @@ export const RecordBoardCardHeader = ({ ) : isIdentifierEmpty ? ( ) : ( - { - openRecordInCommandMenu({ - recordId, - objectNameSingular: objectMetadataItem.nameSingular, - }); - } - : undefined - } - to={ - recordIndexOpenRecordIn === ViewOpenRecordInType.RECORD_PAGE - ? indexIdentifierUrl(recordId) - : undefined - } - /> + isDefined(record) && ( + + ) )} diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/ChipFieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/ChipFieldDisplay.tsx index ac125fcd0..285c0217d 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/ChipFieldDisplay.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/ChipFieldDisplay.tsx @@ -1,52 +1,22 @@ -import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; import { RecordChip } from '@/object-record/components/RecordChip'; import { useChipFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useChipFieldDisplay'; -import { RecordIdentifierChip } from '@/object-record/record-index/components/RecordIndexRecordChip'; -import { recordIndexOpenRecordInSelector } from '@/object-record/record-index/states/selectors/recordIndexOpenRecordInSelector'; -import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType'; -import { useRecoilValue } from 'recoil'; +import { isDefined } from 'twenty-shared'; import { ChipSize } from 'twenty-ui'; export const ChipFieldDisplay = () => { - const { - recordValue, - objectNameSingular, - isLabelIdentifier, - labelIdentifierLink, - } = useChipFieldDisplay(); + const { recordValue, objectNameSingular, labelIdentifierLink } = + useChipFieldDisplay(); - const recordIndexOpenRecordIn = useRecoilValue( - recordIndexOpenRecordInSelector, - ); - - const { openRecordInCommandMenu } = useCommandMenu(); - - if (!recordValue) { + if (!isDefined(recordValue)) { return null; } - return isLabelIdentifier ? ( - { - openRecordInCommandMenu({ - recordId: recordValue.id, - objectNameSingular, - }); - } - : undefined - } + to={labelIdentifierLink} /> - ) : ( - ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/RelationToOneFieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/RelationToOneFieldDisplay.tsx index dede0f879..cec3be5b1 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/RelationToOneFieldDisplay.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/RelationToOneFieldDisplay.tsx @@ -1,17 +1,22 @@ +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { RecordChip } from '@/object-record/components/RecordChip'; import { useRelationToOneFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useRelationToOneFieldDisplay'; +import { isDefined } from 'twenty-shared'; export const RelationToOneFieldDisplay = () => { const { fieldValue, fieldDefinition, generateRecordChipData } = useRelationToOneFieldDisplay(); if ( - !fieldValue || - !fieldDefinition?.metadata.relationObjectMetadataNameSingular + !isDefined(fieldValue) || + !isDefined(fieldDefinition?.metadata.relationObjectMetadataNameSingular) ) { return null; } + const isWorkspaceMemberFieldMetadataRelation = + fieldDefinition.metadata.relationObjectMetadataNameSingular === + CoreObjectNameSingular.WorkspaceMember; const recordChipData = generateRecordChipData(fieldValue); return ( @@ -19,6 +24,7 @@ export const RelationToOneFieldDisplay = () => { key={recordChipData.recordId} objectNameSingular={recordChipData.objectNameSingular} record={fieldValue} + forceDisableClick={isWorkspaceMemberFieldMetadataRelation} /> ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexRecordChip.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexRecordChip.tsx deleted file mode 100644 index a0b749e7f..000000000 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexRecordChip.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { useGetStandardObjectIcon } from '@/object-metadata/hooks/useGetStandardObjectIcon'; -import { useRecordChipData } from '@/object-record/hooks/useRecordChipData'; -import { ObjectRecord } from '@/object-record/types/ObjectRecord'; -import { isNonEmptyString } from '@sniptt/guards'; -import { AvatarChip, AvatarChipVariant, ChipSize } from 'twenty-ui'; - -export type RecordIdentifierChipProps = { - objectNameSingular: string; - record: ObjectRecord; - variant?: AvatarChipVariant; - size?: ChipSize; - to?: string; - maxWidth?: number; - onClick?: () => void; -}; - -export const RecordIdentifierChip = ({ - objectNameSingular, - record, - variant, - size, - onClick, - to, - maxWidth, -}: RecordIdentifierChipProps) => { - const { recordChipData } = useRecordChipData({ - objectNameSingular, - record, - }); - - const { Icon: LeftIcon, IconColor: LeftIconColor } = - useGetStandardObjectIcon(objectNameSingular); - - if (!isNonEmptyString(recordChipData.name.trim())) { - return null; - } - - return ( - - ); -}; diff --git a/packages/twenty-front/src/modules/ui/field/display/components/ActorDisplay.tsx b/packages/twenty-front/src/modules/ui/field/display/components/ActorDisplay.tsx index e758e7174..88cbe2fc1 100644 --- a/packages/twenty-front/src/modules/ui/field/display/components/ActorDisplay.tsx +++ b/packages/twenty-front/src/modules/ui/field/display/components/ActorDisplay.tsx @@ -4,7 +4,6 @@ import { ConnectedAccountProvider } from 'twenty-shared'; import { useMemo } from 'react'; import { AvatarChip, - AvatarChipVariant, IconApi, IconCalendar, IconCsv, @@ -71,7 +70,6 @@ export const ActorDisplay = ({ LeftIcon={LeftIcon} avatarUrl={avatarUrl ?? undefined} isIconInverted={isIconInverted} - variant={AvatarChipVariant.Transparent} /> ); }; diff --git a/packages/twenty-front/src/modules/ui/layout/expandable-list/components/ExpandableList.tsx b/packages/twenty-front/src/modules/ui/layout/expandable-list/components/ExpandableList.tsx index 83e3714f3..c7a60019f 100644 --- a/packages/twenty-front/src/modules/ui/layout/expandable-list/components/ExpandableList.tsx +++ b/packages/twenty-front/src/modules/ui/layout/expandable-list/components/ExpandableList.tsx @@ -1,6 +1,10 @@ import styled from '@emotion/styled'; import { ReactElement, useCallback, useEffect, useRef, useState } from 'react'; -import { AnimatedContainer, Chip, ChipVariant } from 'twenty-ui'; +import { + AnimatedContainer, + ChipSize, + OverflowingTextWithTooltip, +} from 'twenty-ui'; import { ExpandedListDropdown } from '@/ui/layout/expandable-list/components/ExpandedListDropdown'; import { isFirstOverflowingChildElement } from '@/ui/layout/expandable-list/utils/isFirstOverflowingChildElement'; @@ -34,7 +38,7 @@ const StyledChildContainer = styled.div` } `; -const StyledChipCount = styled(Chip)` +const StyledUnShrinkableContainer = styled.div` flex-shrink: 0; `; @@ -150,11 +154,12 @@ export const ExpandableList = ({ {canDisplayChipCount && ( - + + + )} {isListExpanded && ( diff --git a/packages/twenty-front/src/modules/ui/layout/right-drawer/components/RightDrawerTopBar.tsx b/packages/twenty-front/src/modules/ui/layout/right-drawer/components/RightDrawerTopBar.tsx index 1283073a3..6f0d07113 100644 --- a/packages/twenty-front/src/modules/ui/layout/right-drawer/components/RightDrawerTopBar.tsx +++ b/packages/twenty-front/src/modules/ui/layout/right-drawer/components/RightDrawerTopBar.tsx @@ -102,7 +102,7 @@ export const RightDrawerTopBar = () => { } + leftComponent={() => } size={ChipSize.Large} accent={ChipAccent.TextSecondary} clickable={false} diff --git a/packages/twenty-ui/src/display/avatar-chip/components/AvatarChip.tsx b/packages/twenty-ui/src/display/avatar-chip/components/AvatarChip.tsx new file mode 100644 index 000000000..5f9086bfd --- /dev/null +++ b/packages/twenty-ui/src/display/avatar-chip/components/AvatarChip.tsx @@ -0,0 +1,37 @@ +import { AvatarChipsLeftComponent } from '@ui/display/avatar-chip/components/AvatarChipLeftComponent'; +import { AvatarChipsCommonProps } from '@ui/display/avatar-chip/types/AvatarChipsCommonProps.type'; +import { Chip, ChipVariant } from '@ui/display/chip/components/Chip'; + +export type AvatarChipProps = AvatarChipsCommonProps; +export const AvatarChip = ({ + name, + LeftIcon, + LeftIconColor, + avatarType, + avatarUrl, + className, + isIconInverted, + maxWidth, + placeholderColorSeed, + size, +}: AvatarChipProps) => ( + ( + + )} + clickable={false} + className={className} + maxWidth={maxWidth} + /> +); diff --git a/packages/twenty-ui/src/display/avatar-chip/components/AvatarChipLeftComponent.tsx b/packages/twenty-ui/src/display/avatar-chip/components/AvatarChipLeftComponent.tsx new file mode 100644 index 000000000..8c12fc523 --- /dev/null +++ b/packages/twenty-ui/src/display/avatar-chip/components/AvatarChipLeftComponent.tsx @@ -0,0 +1,74 @@ +import { useTheme } from '@emotion/react'; +import styled from '@emotion/styled'; +import { Avatar } from '@ui/display/avatar/components/Avatar'; +import { AvatarType } from '@ui/display/avatar/types/AvatarType'; +import { IconComponent } from '@ui/display/icon/types/IconComponent'; +import { isDefined } from 'twenty-shared'; +import { Nullable } from 'vitest'; + +const StyledInvertedIconContainer = styled.div<{ backgroundColor: string }>` + display: flex; + align-items: center; + justify-content: center; + width: 14px; + height: 14px; + border-radius: 4px; + background-color: ${({ backgroundColor }) => backgroundColor}; +`; + +export type AvatarChipsLeftComponentProps = { + name: string; + avatarUrl?: string; + avatarType?: Nullable; + LeftIcon?: IconComponent; + LeftIconColor?: string; + isIconInverted?: boolean; + placeholderColorSeed?: string; +}; + +export const AvatarChipsLeftComponent: React.FC< + AvatarChipsLeftComponentProps +> = ({ + LeftIcon, + placeholderColorSeed, + avatarType, + avatarUrl, + name, + isIconInverted = false, + LeftIconColor, +}) => { + const theme = useTheme(); + if (!isDefined(LeftIcon)) { + return ( + + ); + } + + if (isIconInverted) { + return ( + + + + ); + } + + return ( + + ); +}; diff --git a/packages/twenty-ui/src/display/avatar-chip/components/LinkAvatarChip.tsx b/packages/twenty-ui/src/display/avatar-chip/components/LinkAvatarChip.tsx new file mode 100644 index 000000000..2a7edc484 --- /dev/null +++ b/packages/twenty-ui/src/display/avatar-chip/components/LinkAvatarChip.tsx @@ -0,0 +1,53 @@ +import { AvatarChipsLeftComponent } from '@ui/display/avatar-chip/components/AvatarChipLeftComponent'; +import { AvatarChipsCommonProps } from '@ui/display/avatar-chip/types/AvatarChipsCommonProps.type'; +import { AvatarChipVariant } from '@ui/display/avatar-chip/types/AvatarChipsVariant.type'; +import { ChipVariant } from '@ui/display/chip/components/Chip'; +import { LinkChip, LinkChipProps } from '@ui/display/chip/components/LinkChip'; + +export type LinkAvatarChipProps = Omit & { + to: string; + onClick?: LinkChipProps['onClick']; + variant?: AvatarChipVariant; +}; + +export const LinkAvatarChip = ({ + to, + onClick, + name, + LeftIcon, + LeftIconColor, + avatarType, + avatarUrl, + className, + isIconInverted, + maxWidth, + placeholderColorSeed, + size, + variant, +}: LinkAvatarChipProps) => ( + missleading + variant === AvatarChipVariant.Regular + ? ChipVariant.Highlighted + : ChipVariant.Regular + } + size={size} + leftComponent={() => ( + + )} + className={className} + maxWidth={maxWidth} + /> +); diff --git a/packages/twenty-ui/src/display/avatar-chip/types/AvatarChipsCommonProps.type.ts b/packages/twenty-ui/src/display/avatar-chip/types/AvatarChipsCommonProps.type.ts new file mode 100644 index 000000000..dc7528896 --- /dev/null +++ b/packages/twenty-ui/src/display/avatar-chip/types/AvatarChipsCommonProps.type.ts @@ -0,0 +1,8 @@ +import { AvatarChipsLeftComponentProps } from '@ui/display/avatar-chip/components/AvatarChipLeftComponent'; +import { ChipSize } from '@ui/display/chip/components/Chip'; + +export type AvatarChipsCommonProps = { + size?: ChipSize; + className?: string; + maxWidth?: number; +} & AvatarChipsLeftComponentProps; diff --git a/packages/twenty-ui/src/display/avatar-chip/types/AvatarChipsVariant.type.ts b/packages/twenty-ui/src/display/avatar-chip/types/AvatarChipsVariant.type.ts new file mode 100644 index 000000000..d7ff78f72 --- /dev/null +++ b/packages/twenty-ui/src/display/avatar-chip/types/AvatarChipsVariant.type.ts @@ -0,0 +1,4 @@ +export enum AvatarChipVariant { + Regular = 'regular', + Transparent = 'transparent', +} diff --git a/packages/twenty-ui/src/display/chip/components/AvatarChip.tsx b/packages/twenty-ui/src/display/chip/components/AvatarChip.tsx deleted file mode 100644 index eeb251187..000000000 --- a/packages/twenty-ui/src/display/chip/components/AvatarChip.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import { styled } from '@linaria/react'; -import { Avatar } from '@ui/display/avatar/components/Avatar'; -import { AvatarType } from '@ui/display/avatar/types/AvatarType'; -import { Chip, ChipSize, ChipVariant } from '@ui/display/chip/components/Chip'; -import { IconComponent } from '@ui/display/icon/types/IconComponent'; -import { ThemeContext } from '@ui/theme'; -import { Nullable } from '@ui/utilities/types/Nullable'; -import { MouseEvent, useContext } from 'react'; -import { isDefined } from 'twenty-shared'; - -// Import Link from react-router-dom instead of UndecoratedLink -import { Link } from 'react-router-dom'; - -export type AvatarChipProps = { - name: string; - avatarUrl?: string; - avatarType?: Nullable; - variant?: AvatarChipVariant; - size?: ChipSize; - LeftIcon?: IconComponent; - LeftIconColor?: string; - isIconInverted?: boolean; - className?: string; - placeholderColorSeed?: string; - onClick?: (event: MouseEvent) => void; - to?: string; - maxWidth?: number; -}; - -export enum AvatarChipVariant { - Regular = 'regular', - Transparent = 'transparent', -} - -const StyledInvertedIconContainer = styled.div<{ backgroundColor: string }>` - display: flex; - align-items: center; - justify-content: center; - width: 14px; - height: 14px; - border-radius: 4px; - background-color: ${({ backgroundColor }) => backgroundColor}; -`; - -// Ideally we would use the UndecoratedLink component from @ui/navigation -// but it led to a bug probably linked to circular dependencies, which was hard to solve -const StyledLink = styled(Link)` - text-decoration: none; -`; - -export const AvatarChip = ({ - name, - avatarUrl, - avatarType = 'rounded', - variant = AvatarChipVariant.Regular, - LeftIcon, - LeftIconColor, - isIconInverted, - className, - placeholderColorSeed, - onClick, - to, - size = ChipSize.Small, - maxWidth, -}: AvatarChipProps) => { - const { theme } = useContext(ThemeContext); - - const chip = ( - - - - ) : ( - - ) - ) : ( - - ) - } - clickable={isDefined(onClick) || isDefined(to)} - onClick={to ? undefined : onClick} - className={className} - maxWidth={maxWidth} - /> - ); - - if (!isDefined(to)) return chip; - return ( - - {chip} - - ); -}; diff --git a/packages/twenty-ui/src/display/chip/components/Chip.tsx b/packages/twenty-ui/src/display/chip/components/Chip.tsx index 8807d25dc..29d0516df 100644 --- a/packages/twenty-ui/src/display/chip/components/Chip.tsx +++ b/packages/twenty-ui/src/display/chip/components/Chip.tsx @@ -1,6 +1,6 @@ import { Theme, withTheme } from '@emotion/react'; import { styled } from '@linaria/react'; -import { MouseEvent, ReactNode } from 'react'; +import { ReactNode } from 'react'; import { OverflowingTextWithTooltip } from '@ui/display/tooltip/OverflowingTextWithTooltip'; @@ -21,7 +21,7 @@ export enum ChipVariant { Rounded = 'rounded', } -type ChipProps = { +export type ChipProps = { size?: ChipSize; disabled?: boolean; clickable?: boolean; @@ -29,10 +29,9 @@ type ChipProps = { maxWidth?: number; variant?: ChipVariant; accent?: ChipAccent; - leftComponent?: ReactNode; - rightComponent?: ReactNode; + leftComponent?: (() => ReactNode) | null; + rightComponent?: (() => ReactNode) | null; className?: string; - onClick?: (event: MouseEvent) => void; }; const StyledContainer = withTheme(styled.div< @@ -128,10 +127,9 @@ export const Chip = ({ disabled = false, clickable = true, variant = ChipVariant.Regular, - leftComponent, - rightComponent, + leftComponent = null, + rightComponent = null, accent = ChipAccent.TextPrimary, - onClick, className, maxWidth, }: ChipProps) => { @@ -143,13 +141,12 @@ export const Chip = ({ disabled={disabled} size={size} variant={variant} - onClick={onClick} className={className} maxWidth={maxWidth} > - {leftComponent} + {leftComponent?.()} - {rightComponent} + {rightComponent?.()} ); }; diff --git a/packages/twenty-ui/src/display/chip/components/LinkChip.tsx b/packages/twenty-ui/src/display/chip/components/LinkChip.tsx new file mode 100644 index 000000000..897c1adb1 --- /dev/null +++ b/packages/twenty-ui/src/display/chip/components/LinkChip.tsx @@ -0,0 +1,53 @@ +import styled from '@emotion/styled'; +import { + Chip, + ChipAccent, + ChipProps, + ChipSize, + ChipVariant, +} from '@ui/display/chip/components/Chip'; +import { MouseEvent } from 'react'; +import { Link } from 'react-router-dom'; + +export type LinkChipProps = Omit< + ChipProps, + 'onClick' | 'disabled' | 'clickable' +> & { + to: string; + onClick?: (event: MouseEvent) => void; +}; + +// Ideally we would use the UndecoratedLink component from @ui/navigation +// but it led to a bug probably linked to circular dependencies, which was hard to solve +const StyledLink = styled(Link)` + text-decoration: none; +`; + +export const LinkChip = ({ + to, + size = ChipSize.Small, + label, + variant = ChipVariant.Regular, + leftComponent = null, + rightComponent = null, + accent = ChipAccent.TextPrimary, + className, + maxWidth, + onClick, +}: LinkChipProps) => { + return ( + + + + ); +}; diff --git a/packages/twenty-ui/src/display/chip/components/__stories__/EntityChip.stories.tsx b/packages/twenty-ui/src/display/chip/components/__stories__/EntityChip.stories.tsx index 2682eec04..b70d6df46 100644 --- a/packages/twenty-ui/src/display/chip/components/__stories__/EntityChip.stories.tsx +++ b/packages/twenty-ui/src/display/chip/components/__stories__/EntityChip.stories.tsx @@ -1,5 +1,5 @@ import { Meta, StoryObj } from '@storybook/react'; -import { AvatarChip } from '@ui/display/chip/components/AvatarChip'; +import { AvatarChip } from '@ui/display/avatar-chip/components/AvatarChip'; import { ComponentDecorator, RouterDecorator } from '@ui/testing'; diff --git a/packages/twenty-ui/src/display/index.ts b/packages/twenty-ui/src/display/index.ts index b8dca47f3..f3c95251a 100644 --- a/packages/twenty-ui/src/display/index.ts +++ b/packages/twenty-ui/src/display/index.ts @@ -1,3 +1,8 @@ +export * from './avatar-chip/components/AvatarChip'; +export * from './avatar-chip/components/AvatarChipLeftComponent'; +export * from './avatar-chip/components/LinkAvatarChip'; +export * from './avatar-chip/types/AvatarChipsCommonProps.type'; +export * from './avatar-chip/types/AvatarChipsVariant.type'; export * from './avatar/components/Avatar'; export * from './avatar/components/AvatarGroup'; export * from './avatar/components/states/isInvalidAvatarUrlState'; @@ -7,8 +12,8 @@ export * from './avatar/types/AvatarType'; export * from './banner/components/Banner'; export * from './checkmark/components/AnimatedCheckmark'; export * from './checkmark/components/Checkmark'; -export * from './chip/components/AvatarChip'; export * from './chip/components/Chip'; +export * from './chip/components/LinkChip'; export * from './color/components/ColorSample'; export * from './icon/components/IconAddressBook'; export * from './icon/components/IconGmail'; diff --git a/packages/twenty-ui/src/utilities/events/isModifiedEvent.ts b/packages/twenty-ui/src/utilities/events/isModifiedEvent.ts new file mode 100644 index 000000000..18600b84e --- /dev/null +++ b/packages/twenty-ui/src/utilities/events/isModifiedEvent.ts @@ -0,0 +1,16 @@ +type LimitedMouseEvent = Pick< + MouseEvent, + 'button' | 'metaKey' | 'altKey' | 'ctrlKey' | 'shiftKey' +>; + +export const isModifiedEvent = ({ + altKey, + ctrlKey, + shiftKey, + metaKey, + button, +}: LimitedMouseEvent) => { + const pressedKey = [altKey, ctrlKey, shiftKey, metaKey].some((key) => key); + const isLeftClick = button === 0; + return pressedKey || !isLeftClick; +}; diff --git a/packages/twenty-ui/src/utilities/index.ts b/packages/twenty-ui/src/utilities/index.ts index d9ed28a8d..79fca2a78 100644 --- a/packages/twenty-ui/src/utilities/index.ts +++ b/packages/twenty-ui/src/utilities/index.ts @@ -10,6 +10,7 @@ export * from './device/getOsControlSymbol'; export * from './device/getOsShortcutSeparator'; export * from './device/getUserDevice'; export * from './dimensions/components/AutogrowWrapper'; +export * from './events/isModifiedEvent'; export * from './responsive/hooks/useIsMobile'; export * from './screen-size/hooks/useScreenSize'; export * from './state/utils/createState';