diff --git a/packages/twenty-front/src/modules/settings/components/SettingsNavigationDrawerItem.tsx b/packages/twenty-front/src/modules/settings/components/SettingsNavigationDrawerItem.tsx index f5bf9d2173..9df0d0dbb1 100644 --- a/packages/twenty-front/src/modules/settings/components/SettingsNavigationDrawerItem.tsx +++ b/packages/twenty-front/src/modules/settings/components/SettingsNavigationDrawerItem.tsx @@ -40,8 +40,7 @@ export const SettingsNavigationDrawerItem = ({ to={href} Icon={item.Icon} active={isActive} - soon={item.soon} - isNew={item.isNew} + modifier={item.modifier} onClick={item.onClick} /> @@ -56,8 +55,7 @@ export const SettingsNavigationDrawerItem = ({ to={href || undefined} Icon={item.Icon} active={isActive} - soon={item.soon} - isNew={item.isNew} + modifier={item.modifier} onClick={item.onClick} /> ); diff --git a/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx b/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx index c3aec543db..37305db70e 100644 --- a/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx +++ b/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx @@ -7,7 +7,10 @@ import { billingState } from '@/client-config/states/billingState'; import { supportChatState } from '@/client-config/states/supportChatState'; import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap'; import { getDocumentationUrl } from '@/support/utils/getDocumentationUrl'; -import { type NavigationDrawerItemIndentationLevel } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem'; +import { + type NavigationDrawerItemIndentationLevel, + type NavigationDrawerItemModifier, +} from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { t } from '@lingui/core/macro'; @@ -57,8 +60,7 @@ export type SettingsNavigationItem = { isHidden?: boolean; subItems?: SettingsNavigationItem[]; isAdvanced?: boolean; - soon?: boolean; - isNew?: boolean; + modifier?: NavigationDrawerItemModifier; }; const useSettingsNavigationItems = (): SettingsNavigationSection[] => { @@ -179,7 +181,7 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => { isHidden: !isApplicationEnabled || !permissionMap[PermissionFlagType.WORKSPACE], - isNew: true, + modifier: 'new', }, { label: t`AI`, @@ -187,7 +189,7 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => { Icon: IconSparkles, isHidden: !isAIEnabled || !permissionMap[PermissionFlagType.WORKSPACE], - isNew: true, + modifier: 'new', }, { label: t`Security`, diff --git a/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInAppNavigationDrawerMock.tsx b/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInAppNavigationDrawerMock.tsx index 626bca578d..b1ca20b102 100644 --- a/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInAppNavigationDrawerMock.tsx +++ b/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInAppNavigationDrawerMock.tsx @@ -45,7 +45,7 @@ export const SignInAppNavigationDrawerMock = ({ label={t`Search`} Icon={IconSearch} onClick={() => {}} - keyboard={[getOsControlSymbol(), 'K']} + modifier={{ keyboard: [getOsControlSymbol(), 'K'] }} /> JSX.Element); iconColor?: string | null; active?: boolean; - danger?: boolean; - soon?: boolean; - isNew?: boolean; - count?: number; - keyboard?: string[]; + modifier?: NavigationDrawerItemModifier; rightOptions?: ReactNode; alwaysShowRightOptions?: boolean; isDragging?: boolean; isRightOptionsDropdownOpen?: boolean; triggerEvent?: TriggerEventType; - mouseUpNavigation?: boolean; preventCollapseOnMobile?: boolean; isSelectedInEditMode?: boolean; variant?: 'default' | 'tertiary'; @@ -67,14 +67,13 @@ export type NavigationDrawerItemProps = { type StyledItemProps = Pick< NavigationDrawerItemProps, | 'active' - | 'danger' | 'indentationLevel' - | 'soon' | 'to' | 'isDragging' | 'isSelectedInEditMode' | 'variant' > & { + isSoon: boolean; isNavigationDrawerExpanded: boolean; hasRightOptions: boolean; href?: string; @@ -92,14 +91,11 @@ const StyledItem = styled.button` : '1px solid transparent'}; border-radius: ${themeCssVariables.border.radius.sm}; box-sizing: border-box; - color: ${({ active, danger, soon, variant }) => { + color: ${({ active, isSoon, variant }) => { if (active === true) { return themeCssVariables.font.color.primary; } - if (danger === true) { - return themeCssVariables.color.red; - } - if (soon === true) { + if (isSoon) { return themeCssVariables.font.color.light; } if (variant === 'tertiary') { @@ -107,8 +103,8 @@ const StyledItem = styled.button` } return themeCssVariables.font.color.secondary; }}; - cursor: ${({ soon, isDragging }) => - isDragging ? 'grabbing' : soon ? 'default' : 'pointer'}; + cursor: ${({ isSoon, isDragging }) => + isDragging ? 'grabbing' : isSoon ? 'default' : 'pointer'}; display: flex; font-family: ${themeCssVariables.font.family}; font-size: ${themeCssVariables.font.size.md}; @@ -122,7 +118,7 @@ const StyledItem = styled.button` ? themeCssVariables.spacing['0.5'] : themeCssVariables.spacing[1]}; padding-top: ${themeCssVariables.spacing[1]}; - pointer-events: ${({ soon }) => (soon ? 'none' : 'auto')}; + pointer-events: ${({ isSoon }) => (isSoon ? 'none' : 'auto')}; text-decoration: none; user-select: none; width: ${({ isNavigationDrawerExpanded, hasRightOptions }) => @@ -132,10 +128,7 @@ const StyledItem = styled.button` &:hover { background: ${themeCssVariables.background.transparent.light}; - color: ${({ danger }) => - danger - ? themeCssVariables.color.red - : themeCssVariables.font.color.primary}; + color: ${themeCssVariables.font.color.primary}; } &:hover .keyboard-shortcuts { @@ -172,20 +165,6 @@ const StyledItemSecondaryLabel = styled.span` font-weight: ${themeCssVariables.font.weight.regular}; `; -const StyledItemCount = styled.span` - align-items: center; - background-color: ${themeCssVariables.color.blue}; - border-radius: ${themeCssVariables.border.radius.rounded}; - color: ${themeCssVariables.grayScale.gray1}; - display: flex; - font-size: ${themeCssVariables.font.size.xs}; - font-weight: ${themeCssVariables.font.weight.semiBold}; - height: 16px; - justify-content: center; - margin-left: auto; - width: 16px; -`; - const StyledKeyBoardShortcut = styled.span` align-items: center; background: ${themeCssVariables.background.transparent.lighter}; @@ -277,18 +256,13 @@ export const NavigationDrawerItem = ({ to, onClick, active, - danger, - soon, - isNew, - count, - keyboard, + modifier, subItemState, rightOptions, alwaysShowRightOptions = false, isDragging, isRightOptionsDropdownOpen, triggerEvent, - mouseUpNavigation = false, preventCollapseOnMobile = false, isSelectedInEditMode = false, variant = 'default', @@ -301,10 +275,15 @@ export const NavigationDrawerItem = ({ const { navigationItemId } = useNavigationDrawerTooltip(label, to); + const isSoon = modifier === 'soon'; + const isNew = modifier === 'new'; + const keyboardKeys = + isDefined(modifier) && typeof modifier === 'object' + ? modifier.keyboard + : undefined; + const showBreadcrumb = indentationLevel === 2; - const showStyledSpacer = Boolean( - soon || isNew || count || keyboard || rightOptions, - ); + const showStyledSpacer = isDefined(modifier) || isDefined(rightOptions); const handleMobileNavigation = () => { if (isMobile && !preventCollapseOnMobile) { @@ -314,6 +293,7 @@ export const NavigationDrawerItem = ({ const isExternalLink = isDefined(to) && (to.startsWith('http://') || to.startsWith('https://')); + const isInternalLink = isDefined(to) && !isExternalLink; const handleExternalLinkClick = () => { handleMobileNavigation(); @@ -332,40 +312,37 @@ export const NavigationDrawerItem = ({ triggerEvent, }); + const elementType = isExternalLink + ? 'a' + : isInternalLink + ? Link + : isDefined(rightOptions) + ? 'div' + : undefined; + return ( {showBreadcrumb && ( @@ -419,7 +396,7 @@ export const NavigationDrawerItem = ({ {showStyledSpacer && } - {soon && ( + {isSoon && ( @@ -431,23 +408,25 @@ export const NavigationDrawerItem = ({ )} - {!!count && ( - - {count} - - )} - - {keyboard && ( + {isDefined(keyboardKeys) && ( - + )} {isDefined(rightOptions) && ( - + {/* When StyledItem renders as a Link, we need both handlers to + prevent navigation when interacting with rightOptions: + - onMouseDown: stops useMouseDownNavigation from calling navigate() + - onClickCapture: prevents the native follow since the child's + stopPropagation blocks Link's own preventDefault */} + e.stopPropagation()} + onClickCapture={(e) => e.preventDefault()} + > - + diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/__stories__/NavigationDrawerItem.stories.tsx b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/__stories__/NavigationDrawerItem.stories.tsx index e4eb47280a..9a2041f958 100644 --- a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/__stories__/NavigationDrawerItem.stories.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/__stories__/NavigationDrawerItem.stories.tsx @@ -105,23 +105,14 @@ export const NewPill: Story = { args={{ label: 'New Feature', Icon: IconSearch, - isNew: true, + modifier: 'new', }} /> - @@ -195,12 +186,6 @@ export const Catalog: CatalogStory = { pseudo: { hover: ['.hover'] }, catalog: { dimensions: [ - { - name: 'danger', - values: [true, false], - props: (danger: boolean) => ({ danger }), - labels: (danger: boolean) => (danger ? 'Danger' : 'No Danger'), - }, { name: 'active', values: [true, false], @@ -216,23 +201,19 @@ export const Catalog: CatalogStory = { }, { name: 'adornments', - values: [ - 'Without Adornments', - 'Soon Pill', - 'New Pill', - 'Count', - 'Keyboard Keys', - ], + values: ['Without Modifier', 'Soon', 'New', 'Keyboard Keys'], props: (adornmentName: string) => - adornmentName === 'Soon Pill' - ? { soon: true } - : adornmentName === 'New Pill' - ? { isNew: true } - : adornmentName === 'Count' - ? { count: 3 } - : adornmentName === 'Keyboard Keys' - ? { keyboard: [getOsControlSymbol(), 'K'] } - : {}, + adornmentName === 'Soon' + ? { modifier: 'soon' } + : adornmentName === 'New' + ? { modifier: 'new' } + : adornmentName === 'Keyboard Keys' + ? { + modifier: { + keyboard: [getOsControlSymbol(), 'K'], + }, + } + : {}, }, ], },