diff --git a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownLayoutContent.tsx b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownLayoutContent.tsx index 7f239536c..447046977 100644 --- a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownLayoutContent.tsx +++ b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownLayoutContent.tsx @@ -1,7 +1,6 @@ import { IconBaselineDensitySmall, IconChevronLeft, - IconLayoutKanban, IconLayoutList, IconLayoutNavbar, IconLayoutSidebarRight, @@ -9,6 +8,7 @@ import { MenuItem, MenuItemSelect, MenuItemToggle, + OverflowingTextWithTooltip, } from 'twenty-ui'; import { useObjectOptionsForBoard } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard'; @@ -24,7 +24,7 @@ import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly'; import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType'; -import { ViewType } from '@/views/types/ViewType'; +import { ViewType, viewTypeIconMapping } from '@/views/types/ViewType'; import { useGetAvailableFieldsForKanban } from '@/views/view-picker/hooks/useGetAvailableFieldsForKanban'; import { useLingui } from '@lingui/react/macro'; import { useRecoilValue } from 'recoil'; @@ -75,6 +75,7 @@ export const ObjectOptionsDropdownLayoutContent = () => { }; const isDefaultView = currentView?.key === 'INDEX'; + const nbsp = '\u00A0'; return ( <> @@ -101,15 +102,20 @@ export const ObjectOptionsDropdownLayoutContent = () => { }} /> + {nbsp}ยท{nbsp} + + + ) : availableFieldsForKanban.length === 0 ? ( + t`Create Select...` + ) : undefined } selected={currentView?.type === ViewType.Kanban} onClick={handleSelectKanbanViewType} diff --git a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownMenuContent.tsx b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownMenuContent.tsx index cf68237fd..a2e1a38c9 100644 --- a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownMenuContent.tsx +++ b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownMenuContent.tsx @@ -2,10 +2,8 @@ import { Key } from 'ts-key-enum'; import { AppTooltip, IconCopy, - IconLayoutKanban, IconLayoutList, IconListDetails, - IconTable, IconTrash, MenuItem, } from 'twenty-ui'; @@ -23,7 +21,7 @@ import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly'; -import { ViewType } from '@/views/types/ViewType'; +import { ViewType, viewTypeIconMapping } from '@/views/types/ViewType'; import { useDeleteViewFromCurrentState } from '@/views/view-picker/hooks/useDeleteViewFromCurrentState'; import { viewPickerReferenceViewIdComponentState } from '@/views/view-picker/states/viewPickerReferenceViewIdComponentState'; import { useTheme } from '@emotion/react'; @@ -85,9 +83,7 @@ export const ObjectOptionsDropdownMenuContent = () => { onContentChange('layout')} - LeftIcon={ - currentView?.type === ViewType.Table ? IconTable : IconLayoutKanban - } + LeftIcon={viewTypeIconMapping(currentView?.type ?? ViewType.Table)} text={t`Layout`} contextualText={`${capitalize(currentView?.type ?? '')}`} hasSubMenu diff --git a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownMenuViewName.tsx b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownMenuViewName.tsx index c8047eca7..15e977681 100644 --- a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownMenuViewName.tsx +++ b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownMenuViewName.tsx @@ -61,6 +61,8 @@ export const ObjectOptionsDropdownMenuViewName = ({ const [viewPickerSelectedIcon, setViewPickerSelectedIcon] = useRecoilComponentStateV2(viewPickerSelectedIconComponentState); + setViewPickerSelectedIcon(currentView.icon); + const viewPickerIsPersisting = useRecoilComponentValueV2( viewPickerIsPersistingComponentState, ); diff --git a/packages/twenty-front/src/modules/object-record/object-options-dropdown/hooks/useSetViewTypeFromLayoutOptionsMenu.ts b/packages/twenty-front/src/modules/object-record/object-options-dropdown/hooks/useSetViewTypeFromLayoutOptionsMenu.ts index 154b5734d..053d9b36d 100644 --- a/packages/twenty-front/src/modules/object-record/object-options-dropdown/hooks/useSetViewTypeFromLayoutOptionsMenu.ts +++ b/packages/twenty-front/src/modules/object-record/object-options-dropdown/hooks/useSetViewTypeFromLayoutOptionsMenu.ts @@ -9,7 +9,7 @@ import { usePersistViewGroupRecords } from '@/views/hooks/internal/usePersistVie import { useUpdateCurrentView } from '@/views/hooks/useUpdateCurrentView'; import { GraphQLView } from '@/views/types/GraphQLView'; import { ViewGroup } from '@/views/types/ViewGroup'; -import { ViewType } from '@/views/types/ViewType'; +import { ViewType, viewTypeIconMapping } from '@/views/types/ViewType'; import { useGetAvailableFieldsForKanban } from '@/views/view-picker/hooks/useGetAvailableFieldsForKanban'; import { useCallback } from 'react'; import { useRecoilCallback, useSetRecoilState } from 'recoil'; @@ -122,13 +122,20 @@ export const useSetViewTypeFromLayoutOptionsMenu = () => { ); } setRecordIndexViewType(viewType); - await updateCurrentView(updateCurrentViewParams); - break; + + if (shouldChangeIcon(currentView.icon, currentView.type)) { + updateCurrentViewParams.icon = + viewTypeIconMapping(viewType).displayName; + } + return await updateCurrentView(updateCurrentViewParams); } case ViewType.Table: setRecordIndexViewType(viewType); - await updateCurrentView(updateCurrentViewParams); - break; + if (shouldChangeIcon(currentView.icon, currentView.type)) { + updateCurrentViewParams.icon = + viewTypeIconMapping(viewType).displayName; + } + return await updateCurrentView(updateCurrentViewParams); default: { return assertUnreachable(viewType); } @@ -144,6 +151,25 @@ export const useSetViewTypeFromLayoutOptionsMenu = () => { ], ); + const shouldChangeIcon = ( + oldIcon: string, + oldViewType: ViewType, + ): boolean => { + if ( + oldViewType === ViewType.Kanban && + oldIcon === viewTypeIconMapping(ViewType.Kanban).displayName + ) { + return true; + } + if ( + oldViewType === ViewType.Table && + oldIcon === viewTypeIconMapping(ViewType.Table).displayName + ) { + return true; + } + return false; + }; + return { setAndPersistViewType, }; diff --git a/packages/twenty-front/src/modules/views/types/ViewType.ts b/packages/twenty-front/src/modules/views/types/ViewType.ts index 84ad319c6..b71e0468b 100644 --- a/packages/twenty-front/src/modules/views/types/ViewType.ts +++ b/packages/twenty-front/src/modules/views/types/ViewType.ts @@ -1,4 +1,21 @@ +import { IconComponent, IconLayoutKanban, IconTable } from 'twenty-ui'; + export enum ViewType { Table = 'table', Kanban = 'kanban', } + +const VIEW_TYPE_ICON_MAPPING = [ + { icon: IconLayoutKanban, value: ViewType.Kanban }, + { icon: IconTable, value: ViewType.Table }, +] as const satisfies { + icon: IconComponent; + value: ViewType; +}[]; + +export const viewTypeIconMapping = (viewType?: ViewType) => { + return ( + VIEW_TYPE_ICON_MAPPING.find((type) => type.value === viewType)?.icon ?? + IconTable + ); +}; diff --git a/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerContentCreateMode.tsx b/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerContentCreateMode.tsx index d5eafd43b..d8c323eb7 100644 --- a/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerContentCreateMode.tsx +++ b/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerContentCreateMode.tsx @@ -1,12 +1,13 @@ import styled from '@emotion/styled'; import { Key } from 'ts-key-enum'; -import { IconLayoutKanban, IconTable, IconX } from 'twenty-ui'; +import { IconX } from 'twenty-ui'; import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById'; import { IconPicker } from '@/ui/input/components/IconPicker'; import { Select } from '@/ui/input/components/Select'; import { TextInputV2 } from '@/ui/input/components/TextInputV2'; import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader'; +import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; @@ -16,7 +17,7 @@ import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/ import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { viewObjectMetadataIdComponentState } from '@/views/states/viewObjectMetadataIdComponentState'; import { ViewsHotkeyScope } from '@/views/types/ViewsHotkeyScope'; -import { ViewType } from '@/views/types/ViewType'; +import { ViewType, viewTypeIconMapping } from '@/views/types/ViewType'; import { ViewPickerCreateButton } from '@/views/view-picker/components/ViewPickerCreateButton'; import { ViewPickerIconAndNameContainer } from '@/views/view-picker/components/ViewPickerIconAndNameContainer'; import { ViewPickerSaveButtonContainer } from '@/views/view-picker/components/ViewPickerSaveButtonContainer'; @@ -32,9 +33,8 @@ import { viewPickerIsPersistingComponentState } from '@/views/view-picker/states import { viewPickerKanbanFieldMetadataIdComponentState } from '@/views/view-picker/states/viewPickerKanbanFieldMetadataIdComponentState'; import { viewPickerSelectedIconComponentState } from '@/views/view-picker/states/viewPickerSelectedIconComponentState'; import { viewPickerTypeComponentState } from '@/views/view-picker/states/viewPickerTypeComponentState'; -import { useMemo, useState } from 'react'; import { useLingui } from '@lingui/react/macro'; -import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent'; +import { useMemo, useState } from 'react'; const StyledNoKanbanFieldAvailableContainer = styled.div` color: ${({ theme }) => theme.font.color.light}; @@ -101,8 +101,7 @@ export const ViewPickerContentCreateMode = () => { ViewsHotkeyScope.ListDropdown, ); - const defaultIcon = - viewPickerType === ViewType.Kanban ? 'IconLayoutKanban' : 'IconTable'; + const defaultIcon = viewTypeIconMapping(viewPickerType).displayName; const selectedIcon = useMemo(() => { if (hasManuallySelectedIcon) { @@ -165,11 +164,15 @@ export const ViewPickerContentCreateMode = () => { setViewPickerType(value); }} options={[ - { value: ViewType.Table, label: t`Table`, Icon: IconTable }, + { + value: ViewType.Table, + label: t`Table`, + Icon: viewTypeIconMapping(ViewType.Table), + }, { value: ViewType.Kanban, label: t`Kanban`, - Icon: IconLayoutKanban, + Icon: viewTypeIconMapping(ViewType.Kanban), }, ]} dropdownId={VIEW_PICKER_VIEW_TYPE_DROPDOWN_ID} diff --git a/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerContentEffect.tsx b/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerContentEffect.tsx index c3c2a194c..a6bb2755f 100644 --- a/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerContentEffect.tsx +++ b/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerContentEffect.tsx @@ -5,7 +5,7 @@ import { prefetchViewsFromObjectMetadataItemFamilySelector } from '@/prefetch/st import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; -import { ViewType } from '@/views/types/ViewType'; +import { viewTypeIconMapping } from '@/views/types/ViewType'; import { useGetAvailableFieldsForKanban } from '@/views/view-picker/hooks/useGetAvailableFieldsForKanban'; import { useViewPickerMode } from '@/views/view-picker/hooks/useViewPickerMode'; import { viewPickerInputNameComponentState } from '@/views/view-picker/states/viewPickerInputNameComponentState'; @@ -66,7 +66,8 @@ export const ViewPickerContentEffect = () => { !viewPickerIsDirty ) { const defaultIcon = - viewPickerType === ViewType.Kanban ? 'IconLayoutKanban' : 'IconTable'; + viewTypeIconMapping(viewPickerType).displayName ?? 'IconTable'; + if (viewPickerMode === 'create-empty') { setViewPickerSelectedIcon(defaultIcon); } else { diff --git a/packages/twenty-ui/src/navigation/menu-item/components/MenuItemSelect.tsx b/packages/twenty-ui/src/navigation/menu-item/components/MenuItemSelect.tsx index 1b2a64181..d666d38c5 100644 --- a/packages/twenty-ui/src/navigation/menu-item/components/MenuItemSelect.tsx +++ b/packages/twenty-ui/src/navigation/menu-item/components/MenuItemSelect.tsx @@ -2,6 +2,7 @@ import { css, useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { IconCheck, IconChevronRight, IconComponent } from '@ui/display'; +import { ReactNode } from 'react'; import { MenuItemLeftContent } from '../internals/components/MenuItemLeftContent'; import { StyledMenuItemBase } from '../internals/components/StyledMenuItemBase'; @@ -47,7 +48,7 @@ type MenuItemSelectProps = { disabled?: boolean; hovered?: boolean; hasSubMenu?: boolean; - contextualText?: string; + contextualText?: ReactNode; }; export const MenuItemSelect = ({