diff --git a/packages/twenty-front/src/modules/page-layout/constants/DefaultNoteRecordPageLayout.ts b/packages/twenty-front/src/modules/page-layout/constants/DefaultNoteRecordPageLayout.ts index ff80d7e341..7aebb11f5c 100644 --- a/packages/twenty-front/src/modules/page-layout/constants/DefaultNoteRecordPageLayout.ts +++ b/packages/twenty-front/src/modules/page-layout/constants/DefaultNoteRecordPageLayout.ts @@ -95,7 +95,7 @@ export const DEFAULT_NOTE_RECORD_PAGE_LAYOUT: PageLayout = { title: 'Note', icon: 'IconNotes', position: 150, - layoutMode: PageLayoutTabLayoutMode.CANVAS, + layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST, pageLayoutId: DEFAULT_NOTE_RECORD_PAGE_LAYOUT_ID, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), diff --git a/packages/twenty-front/src/modules/page-layout/constants/DefaultTaskRecordPageLayout.ts b/packages/twenty-front/src/modules/page-layout/constants/DefaultTaskRecordPageLayout.ts index 89157e87a5..518f918fa6 100644 --- a/packages/twenty-front/src/modules/page-layout/constants/DefaultTaskRecordPageLayout.ts +++ b/packages/twenty-front/src/modules/page-layout/constants/DefaultTaskRecordPageLayout.ts @@ -92,7 +92,7 @@ export const DEFAULT_TASK_RECORD_PAGE_LAYOUT: PageLayout = { title: 'Note', icon: 'IconNotes', position: 150, - layoutMode: PageLayoutTabLayoutMode.CANVAS, + layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST, pageLayoutId: DEFAULT_TASK_RECORD_PAGE_LAYOUT_ID, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), diff --git a/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetRenderer.tsx b/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetRenderer.tsx index 55a42d3680..bf70e5d7e7 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetRenderer.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetRenderer.tsx @@ -161,6 +161,8 @@ export const WidgetRenderer = ({ widget }: WidgetRendererProps) => { variant === 'side-column' && !isRecordPageGlobalEditionEnabled; + const isCanvasVariant = variant === 'canvas'; + const widgetCard = ( { isDragging={isDragging} isResizing={isResizing} isLastWidget={isLastWidget} - onMouseEnter={handleMouseEnter} - onMouseLeave={handleMouseLeave} + onMouseEnter={isCanvasVariant ? undefined : handleMouseEnter} + onMouseLeave={isCanvasVariant ? undefined : handleMouseLeave} data-widget-id={widget.id} data-testid={widget.id} className="widget" diff --git a/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/WidgetCard.tsx b/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/WidgetCard.tsx index 99fe9bf62c..bf14303b7c 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/WidgetCard.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/WidgetCard.tsx @@ -106,6 +106,9 @@ const StyledWidgetCard = styled.div` &:hover { // border-color shorthand must precede border-bottom-color longhand for correct CSS cascade border-color: ${(props) => { + if (props.variant === 'canvas') { + return computeBorderColor(props); + } if ( props.isEditable && !props.isDragging && @@ -118,6 +121,10 @@ const StyledWidgetCard = styled.div` }}; border-bottom-color: ${(props) => { + if (props.variant === 'canvas') { + return computeBorderColor(props); + } + const { variant, isEditable } = props; if (variant === 'side-column' && !isEditable) { diff --git a/packages/twenty-front/src/modules/side-panel/components/hooks/usePageLayoutHeaderInfo.ts b/packages/twenty-front/src/modules/side-panel/components/hooks/usePageLayoutHeaderInfo.ts index 7596f5c519..4faf7725f0 100644 --- a/packages/twenty-front/src/modules/side-panel/components/hooks/usePageLayoutHeaderInfo.ts +++ b/packages/twenty-front/src/modules/side-panel/components/hooks/usePageLayoutHeaderInfo.ts @@ -16,6 +16,7 @@ import { type IconComponent, } from 'twenty-ui/display'; import { ThemeContext } from 'twenty-ui/theme-constants'; +import { PageLayoutTabLayoutMode } from '~/generated-metadata/graphql'; type PageLayoutHeaderInfo = { headerIcon: IconComponent | undefined; @@ -65,10 +66,12 @@ export const usePageLayoutHeaderInfo = ({ ? tab.title : ''; + const isCanvasTab = tab.layoutMode === PageLayoutTabLayoutMode.CANVAS; + return { headerIcon: IconAppWindow, headerIconColor: iconColor, - headerType: t`Tab`, + headerType: isCanvasTab ? t`Full tab widget` : t`Tab`, title, isReadonly: false, tab, diff --git a/packages/twenty-front/src/modules/side-panel/hooks/useOpenWidgetSettingsInSidePanel.ts b/packages/twenty-front/src/modules/side-panel/hooks/useOpenWidgetSettingsInSidePanel.ts index 08983600aa..e1507bdfe1 100644 --- a/packages/twenty-front/src/modules/side-panel/hooks/useOpenWidgetSettingsInSidePanel.ts +++ b/packages/twenty-front/src/modules/side-panel/hooks/useOpenWidgetSettingsInSidePanel.ts @@ -1,17 +1,23 @@ import { useCallback } from 'react'; import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext'; +import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState'; import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pageLayoutEditingWidgetIdComponentState'; +import { pageLayoutTabSettingsOpenTabIdComponentState } from '@/page-layout/states/pageLayoutTabSettingsOpenTabIdComponentState'; import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu'; import { useIsDashboardPageLayout } from '@/side-panel/pages/page-layout/hooks/useIsDashboardPageLayout'; import { useNavigatePageLayoutSidePanel } from '@/side-panel/pages/page-layout/hooks/useNavigatePageLayoutSidePanel'; import { sidePanelPageState } from '@/side-panel/states/sidePanelPageState'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; +import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState'; import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState'; import { t } from '@lingui/core/macro'; import { SidePanelPages } from 'twenty-shared/types'; -import { WidgetType } from '~/generated-metadata/graphql'; +import { + PageLayoutTabLayoutMode, + WidgetType, +} from '~/generated-metadata/graphql'; export const useOpenWidgetSettingsInSidePanel = ( pageLayoutIdFromProps?: string, @@ -32,6 +38,16 @@ export const useOpenWidgetSettingsInSidePanel = ( const { closeSidePanelMenu } = useSidePanelMenu(); const setSidePanelPage = useSetAtomState(sidePanelPageState); + const pageLayoutDraft = useAtomComponentStateValue( + pageLayoutDraftComponentState, + pageLayoutId, + ); + + const setPageLayoutTabSettingsOpenTabId = useSetAtomComponentState( + pageLayoutTabSettingsOpenTabIdComponentState, + pageLayoutId, + ); + const openWidgetSettingsInSidePanel = useCallback( ({ widgetId, @@ -112,12 +128,27 @@ export const useOpenWidgetSettingsInSidePanel = ( return; } + const containingTab = pageLayoutDraft.tabs.find((tab) => + tab.widgets.some((w) => w.id === widgetId), + ); + + if (containingTab?.layoutMode === PageLayoutTabLayoutMode.CANVAS) { + setPageLayoutTabSettingsOpenTabId(containingTab.id); + navigatePageLayoutSidePanel({ + sidePanelPage: SidePanelPages.PageLayoutTabSettings, + resetNavigationStack: true, + }); + return; + } + setSidePanelPage(SidePanelPages.CommandMenuDisplay); closeSidePanelMenu(); }, [ isDashboardPageLayout, + pageLayoutDraft, setPageLayoutEditingWidgetId, + setPageLayoutTabSettingsOpenTabId, navigatePageLayoutSidePanel, closeSidePanelMenu, setSidePanelPage, diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/CanvasTabSettingsContent.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/CanvasTabSettingsContent.tsx new file mode 100644 index 0000000000..5cf3132a38 --- /dev/null +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/CanvasTabSettingsContent.tsx @@ -0,0 +1,190 @@ +import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem'; +import { CommandMenuItemDropdown } from '@/command-menu/components/CommandMenuItemDropdown'; +import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget'; +import { SidePanelGroup } from '@/side-panel/components/SidePanelGroup'; +import { SidePanelList } from '@/side-panel/components/SidePanelList'; +import { CanvasTabWidgetVisibilityDropdownContent } from '@/side-panel/pages/page-layout/components/dropdown-content/CanvasTabWidgetVisibilityDropdownContent'; +import { TAB_SETTINGS_SELECTABLE_ITEM_IDS } from '@/side-panel/pages/page-layout/constants/settings/TabSettingsSelectableItemIds'; +import { useTranslatedVisibilityLabel } from '@/side-panel/pages/page-layout/hooks/useTranslatedVisibilityLabel'; +import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; +import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; +import { useModal } from '@/ui/layout/modal/hooks/useModal'; +import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; +import { useLingui } from '@lingui/react/macro'; +import { isDefined } from 'twenty-shared/utils'; +import { + IconChevronLeft, + IconChevronRight, + IconEyeX, + IconPinned, + IconRefreshDot, + IconTrash, +} from 'twenty-ui/display'; + +const RESET_TAB_TO_DEFAULT_MODAL_ID = 'reset-canvas-tab-to-default-modal'; + +type CanvasTabSettingsContentProps = { + pageLayoutId: string; + canvasWidget: PageLayoutWidget | undefined; + canSetAsPinned: boolean; + canMoveLeft: boolean; + canMoveRight: boolean; + canShowResetToDefault: boolean; + canDelete: boolean; + onMoveLeft: () => void; + onMoveRight: () => void; + onSetAsPinned: () => void; + onResetToDefault: () => void; + onDelete: () => void; +}; + +export const CanvasTabSettingsContent = ({ + pageLayoutId, + canvasWidget, + canSetAsPinned, + canMoveLeft, + canMoveRight, + canShowResetToDefault, + canDelete, + onMoveLeft, + onMoveRight, + onSetAsPinned, + onResetToDefault, + onDelete, +}: CanvasTabSettingsContentProps) => { + const { t } = useLingui(); + const { openModal } = useModal(); + + const visibilityLabel = useTranslatedVisibilityLabel( + canvasWidget?.conditionalAvailabilityExpression, + ); + + const handleResetToDefault = () => { + openModal(RESET_TAB_TO_DEFAULT_MODAL_ID); + }; + + const selectableItemIds = [ + ...(canSetAsPinned ? [TAB_SETTINGS_SELECTABLE_ITEM_IDS.SET_AS_PINNED] : []), + ...(canMoveLeft ? [TAB_SETTINGS_SELECTABLE_ITEM_IDS.MOVE_LEFT] : []), + ...(canMoveRight ? [TAB_SETTINGS_SELECTABLE_ITEM_IDS.MOVE_RIGHT] : []), + ...(isDefined(canvasWidget) + ? [TAB_SETTINGS_SELECTABLE_ITEM_IDS.VISIBILITY_RESTRICTION] + : []), + ...(canShowResetToDefault + ? [TAB_SETTINGS_SELECTABLE_ITEM_IDS.RESET_TO_DEFAULT] + : []), + ...(canDelete ? [TAB_SETTINGS_SELECTABLE_ITEM_IDS.DELETE] : []), + ]; + + return ( + <> + + + {canSetAsPinned && ( + + + + )} + {canMoveLeft && ( + + + + )} + {canMoveRight && ( + + + + )} + + + {isDefined(canvasWidget) && ( + + + + + } + dropdownPlacement="bottom-end" + description={visibilityLabel} + contextualTextPosition="right" + /> + + )} + {canShowResetToDefault && ( + + + + )} + {canDelete && ( + + + + )} + + + + + ); +}; diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/RegularTabSettingsContent.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/RegularTabSettingsContent.tsx new file mode 100644 index 0000000000..3a8f35d0ff --- /dev/null +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/RegularTabSettingsContent.tsx @@ -0,0 +1,157 @@ +import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem'; +import { SidePanelGroup } from '@/side-panel/components/SidePanelGroup'; +import { SidePanelList } from '@/side-panel/components/SidePanelList'; +import { TAB_SETTINGS_SELECTABLE_ITEM_IDS } from '@/side-panel/pages/page-layout/constants/settings/TabSettingsSelectableItemIds'; +import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; +import { useModal } from '@/ui/layout/modal/hooks/useModal'; +import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; +import { useLingui } from '@lingui/react/macro'; +import { + IconChevronLeft, + IconChevronRight, + IconCopyPlus, + IconPinned, + IconRefreshDot, + IconTrash, +} from 'twenty-ui/display'; + +const RESET_TAB_TO_DEFAULT_MODAL_ID = 'reset-regular-tab-to-default-modal'; + +type RegularTabSettingsContentProps = { + canSetAsPinned: boolean; + canMoveLeft: boolean; + canMoveRight: boolean; + canShowResetToDefault: boolean; + canDelete: boolean; + onMoveLeft: () => void; + onMoveRight: () => void; + onSetAsPinned: () => void; + onDuplicate: () => void; + onResetToDefault: () => void; + onDelete: () => void; +}; + +export const RegularTabSettingsContent = ({ + canSetAsPinned, + canMoveLeft, + canMoveRight, + canShowResetToDefault, + canDelete, + onMoveLeft, + onMoveRight, + onSetAsPinned, + onDuplicate, + onResetToDefault, + onDelete, +}: RegularTabSettingsContentProps) => { + const { t } = useLingui(); + const { openModal } = useModal(); + + const handleResetToDefault = () => { + openModal(RESET_TAB_TO_DEFAULT_MODAL_ID); + }; + + const selectableItemIds = [ + ...(canMoveLeft ? [TAB_SETTINGS_SELECTABLE_ITEM_IDS.MOVE_LEFT] : []), + ...(canMoveRight ? [TAB_SETTINGS_SELECTABLE_ITEM_IDS.MOVE_RIGHT] : []), + ...(canSetAsPinned ? [TAB_SETTINGS_SELECTABLE_ITEM_IDS.SET_AS_PINNED] : []), + TAB_SETTINGS_SELECTABLE_ITEM_IDS.DUPLICATE, + ...(canShowResetToDefault + ? [TAB_SETTINGS_SELECTABLE_ITEM_IDS.RESET_TO_DEFAULT] + : []), + ...(canDelete ? [TAB_SETTINGS_SELECTABLE_ITEM_IDS.DELETE] : []), + ]; + + return ( + <> + + + {canMoveLeft && ( + + + + )} + {canMoveRight && ( + + + + )} + {canSetAsPinned && ( + + + + )} + + + + {canShowResetToDefault && ( + + + + )} + {canDelete && ( + + + + )} + + + + + ); +}; diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/SidePanelPageLayoutTabSettingsContent.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/SidePanelPageLayoutTabSettingsContent.tsx index aedfb3fdc5..fe44806134 100644 --- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/SidePanelPageLayoutTabSettingsContent.tsx +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/SidePanelPageLayoutTabSettingsContent.tsx @@ -1,4 +1,3 @@ -import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { useDeletePageLayoutTab } from '@/page-layout/hooks/useDeletePageLayoutTab'; import { useDuplicatePageLayoutTab } from '@/page-layout/hooks/useDuplicatePageLayoutTab'; @@ -9,40 +8,27 @@ import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDr import { pageLayoutTabSettingsOpenTabIdComponentState } from '@/page-layout/states/pageLayoutTabSettingsOpenTabIdComponentState'; import { getTabListInstanceIdFromPageLayoutAndRecord } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutAndRecord'; import { sortTabsByPosition } from '@/page-layout/utils/sortTabsByPosition'; -import { SidePanelGroup } from '@/side-panel/components/SidePanelGroup'; -import { SidePanelList } from '@/side-panel/components/SidePanelList'; import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu'; -import { TAB_SETTINGS_SELECTABLE_ITEM_IDS } from '@/side-panel/pages/page-layout/constants/settings/TabSettingsSelectableItemIds'; -import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; -import { useModal } from '@/ui/layout/modal/hooks/useModal'; -import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; +import { CanvasTabSettingsContent } from '@/side-panel/pages/page-layout/components/CanvasTabSettingsContent'; +import { RegularTabSettingsContent } from '@/side-panel/pages/page-layout/components/RegularTabSettingsContent'; import { useAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentState'; import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; -import { useLingui } from '@lingui/react/macro'; import { isDefined } from 'twenty-shared/utils'; import { - IconChevronLeft, - IconChevronRight, - IconCopyPlus, - IconPinned, - IconRefreshDot, - IconTrash, -} from 'twenty-ui/display'; -import { PageLayoutType } from '~/generated-metadata/graphql'; + PageLayoutTabLayoutMode, + PageLayoutType, +} from '~/generated-metadata/graphql'; type SidePanelPageLayoutTabSettingsContentProps = { pageLayoutId: string; recordId: string; }; -const RESET_TAB_TO_DEFAULT_MODAL_ID = 'reset-tab-to-default-modal'; - export const SidePanelPageLayoutTabSettingsContent = ({ pageLayoutId, recordId, }: SidePanelPageLayoutTabSettingsContentProps) => { - const { t } = useLingui(); const { closeSidePanelMenu } = useSidePanelMenu(); const currentWorkspace = useAtomStateValue(currentWorkspaceState); @@ -76,7 +62,6 @@ export const SidePanelPageLayoutTabSettingsContent = ({ const { setAsPinnedTab } = useSetAsPinnedTab(pageLayoutId); const { resetPageLayoutTabToDefault } = useResetPageLayoutTabToDefault(pageLayoutId); - const { openModal } = useModal(); if (!isDefined(pageLayoutTabSettingsOpenTabId)) { return null; @@ -110,114 +95,43 @@ export const SidePanelPageLayoutTabSettingsContent = ({ }; const handleResetToDefault = () => { - openModal(RESET_TAB_TO_DEFAULT_MODAL_ID); - }; - - const handleConfirmReset = () => { resetPageLayoutTabToDefault(tab.id); }; - const selectableItemIds = [ - ...(canMoveLeft ? [TAB_SETTINGS_SELECTABLE_ITEM_IDS.MOVE_LEFT] : []), - ...(canMoveRight ? [TAB_SETTINGS_SELECTABLE_ITEM_IDS.MOVE_RIGHT] : []), - ...(canSetAsPinned ? [TAB_SETTINGS_SELECTABLE_ITEM_IDS.SET_AS_PINNED] : []), - TAB_SETTINGS_SELECTABLE_ITEM_IDS.DUPLICATE, - ...(canShowResetToDefault - ? [TAB_SETTINGS_SELECTABLE_ITEM_IDS.RESET_TO_DEFAULT] - : []), - ...(canDelete ? [TAB_SETTINGS_SELECTABLE_ITEM_IDS.DELETE] : []), - ]; + const isCanvasTab = tab.layoutMode === PageLayoutTabLayoutMode.CANVAS; + + if (isCanvasTab) { + return ( + moveLeft(tab.id)} + onMoveRight={() => moveRight(tab.id)} + onSetAsPinned={() => setAsPinnedTab(tab.id)} + onResetToDefault={handleResetToDefault} + onDelete={handleDelete} + /> + ); + } return ( - <> - - - {canMoveLeft && ( - moveLeft(tab.id)} - > - moveLeft(tab.id)} - /> - - )} - {canMoveRight && ( - moveRight(tab.id)} - > - moveRight(tab.id)} - /> - - )} - {canSetAsPinned && ( - setAsPinnedTab(tab.id)} - > - setAsPinnedTab(tab.id)} - /> - - )} - duplicateTab(tab.id)} - > - duplicateTab(tab.id)} - /> - - {canShowResetToDefault && ( - - - - )} - {canDelete && ( - - - - )} - - - - + moveLeft(tab.id)} + onMoveRight={() => moveRight(tab.id)} + onSetAsPinned={() => setAsPinnedTab(tab.id)} + onDuplicate={() => duplicateTab(tab.id)} + onResetToDefault={handleResetToDefault} + onDelete={handleDelete} + /> ); }; diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/WidgetSettingsManageSection.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/WidgetSettingsManageSection.tsx index 3bd5913ae3..ce13c297f2 100644 --- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/WidgetSettingsManageSection.tsx +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/WidgetSettingsManageSection.tsx @@ -7,8 +7,8 @@ import { SidePanelGroup } from '@/side-panel/components/SidePanelGroup'; import { WidgetVisibilityDropdownContent } from '@/side-panel/pages/page-layout/components/dropdown-content/WidgetVisibilityDropdownContent'; import { WIDGET_SETTINGS_SELECTABLE_ITEM_IDS } from '@/side-panel/pages/page-layout/constants/settings/WidgetSettingsSelectableItemIds'; import { useNavigatePageLayoutSidePanel } from '@/side-panel/pages/page-layout/hooks/useNavigatePageLayoutSidePanel'; +import { useTranslatedVisibilityLabel } from '@/side-panel/pages/page-layout/hooks/useTranslatedVisibilityLabel'; import { useWidgetInEditMode } from '@/side-panel/pages/page-layout/hooks/useWidgetInEditMode'; -import { getVisibilityLabel } from '@/side-panel/pages/page-layout/utils/getVisibilityLabel'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; import { useModal } from '@/ui/layout/modal/hooks/useModal'; @@ -51,19 +51,14 @@ export const WidgetSettingsManageSection = ({ const { openModal } = useModal(); + const visibilityLabel = useTranslatedVisibilityLabel( + widgetInEditMode?.conditionalAvailabilityExpression, + ); + if (!isDefined(pageLayoutEditingWidgetId)) { return null; } - const visibilityLabel = getVisibilityLabel( - widgetInEditMode?.conditionalAvailabilityExpression, - { - anyDevice: t`Any device`, - mobile: t`Mobile`, - desktop: t`Desktop`, - }, - ); - const handleResetToDefault = () => { openModal(RESET_WIDGET_TO_DEFAULT_MODAL_ID); }; diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/CanvasTabWidgetVisibilityDropdownContent.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/CanvasTabWidgetVisibilityDropdownContent.tsx new file mode 100644 index 0000000000..c0c6666368 --- /dev/null +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/CanvasTabWidgetVisibilityDropdownContent.tsx @@ -0,0 +1,75 @@ +import { useUpdatePageLayoutWidget } from '@/page-layout/hooks/useUpdatePageLayoutWidget'; +import { VISIBILITY_OPTIONS } from '@/side-panel/pages/page-layout/constants/VisibilityOptions'; +import { useVisibilityLabels } from '@/side-panel/pages/page-layout/hooks/useVisibilityLabels'; +import { expressionToOptionId } from '@/side-panel/pages/page-layout/utils/expressionToOptionId'; +import { optionIdToExpression } from '@/side-panel/pages/page-layout/utils/optionIdToExpression'; +import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; +import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponentInstanceContext'; +import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown'; +import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; +import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; +import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState'; +import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; +import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; +import { MenuItemSelect } from 'twenty-ui/navigation'; + +type CanvasTabWidgetVisibilityDropdownContentProps = { + widgetId: string; + currentExpression: string | null | undefined; + pageLayoutId: string; +}; + +export const CanvasTabWidgetVisibilityDropdownContent = ({ + widgetId, + currentExpression, + pageLayoutId, +}: CanvasTabWidgetVisibilityDropdownContentProps) => { + const currentOptionId = expressionToOptionId(currentExpression); + + const dropdownId = useAvailableComponentInstanceIdOrThrow( + DropdownComponentInstanceContext, + ); + + const selectedItemId = useAtomComponentStateValue( + selectedItemIdComponentState, + dropdownId, + ); + + const { updatePageLayoutWidget } = useUpdatePageLayoutWidget(pageLayoutId); + + const { closeDropdown } = useCloseDropdown(); + + const handleSelectVisibility = (optionId: string) => { + updatePageLayoutWidget(widgetId, { + conditionalAvailabilityExpression: optionIdToExpression(optionId), + }); + closeDropdown(); + }; + + const visibilityLabels = useVisibilityLabels(); + + return ( + + option.id)} + > + {VISIBILITY_OPTIONS.map((option) => ( + handleSelectVisibility(option.id)} + > + handleSelectVisibility(option.id)} + /> + + ))} + + + ); +}; diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/WidgetVisibilityDropdownContent.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/WidgetVisibilityDropdownContent.tsx index 4dce50d35c..32ee0fca80 100644 --- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/WidgetVisibilityDropdownContent.tsx +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/WidgetVisibilityDropdownContent.tsx @@ -1,8 +1,10 @@ -import { EXPRESSION_DEVICE_DESKTOP } from '@/side-panel/pages/page-layout/constants/ExpressionDeviceDesktop'; -import { EXPRESSION_DEVICE_MOBILE } from '@/side-panel/pages/page-layout/constants/ExpressionDeviceMobile'; +import { VISIBILITY_OPTIONS } from '@/side-panel/pages/page-layout/constants/VisibilityOptions'; import { usePageLayoutIdFromContextStore } from '@/side-panel/pages/page-layout/hooks/usePageLayoutIdFromContextStore'; import { useUpdateCurrentWidgetConfig } from '@/side-panel/pages/page-layout/hooks/useUpdateCurrentWidgetConfig'; +import { useVisibilityLabels } from '@/side-panel/pages/page-layout/hooks/useVisibilityLabels'; import { useWidgetInEditMode } from '@/side-panel/pages/page-layout/hooks/useWidgetInEditMode'; +import { expressionToOptionId } from '@/side-panel/pages/page-layout/utils/expressionToOptionId'; +import { optionIdToExpression } from '@/side-panel/pages/page-layout/utils/optionIdToExpression'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponentInstanceContext'; import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown'; @@ -11,51 +13,9 @@ import { SelectableListItem } from '@/ui/layout/selectable-list/components/Selec import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; -import { useLingui } from '@lingui/react/macro'; import { MenuItemSelect } from 'twenty-ui/navigation'; -const VISIBILITY_ANY_DEVICE = 'any-device'; -const VISIBILITY_MOBILE = 'mobile'; -const VISIBILITY_DESKTOP = 'desktop'; - -const VISIBILITY_OPTIONS = [ - { id: VISIBILITY_ANY_DEVICE }, - { id: VISIBILITY_MOBILE }, - { id: VISIBILITY_DESKTOP }, -] as const; - -const expressionToOptionId = ( - expression: string | null | undefined, -): string => { - if (!expression) { - return VISIBILITY_ANY_DEVICE; - } - - if (expression === EXPRESSION_DEVICE_MOBILE) { - return VISIBILITY_MOBILE; - } - - if (expression === EXPRESSION_DEVICE_DESKTOP) { - return VISIBILITY_DESKTOP; - } - - return VISIBILITY_ANY_DEVICE; -}; - -const optionIdToExpression = (optionId: string): string | null => { - switch (optionId) { - case VISIBILITY_MOBILE: - return EXPRESSION_DEVICE_MOBILE; - case VISIBILITY_DESKTOP: - return EXPRESSION_DEVICE_DESKTOP; - default: - return null; - } -}; - export const WidgetVisibilityDropdownContent = () => { - const { t } = useLingui(); - const { pageLayoutId } = usePageLayoutIdFromContextStore(); const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId); @@ -85,11 +45,7 @@ export const WidgetVisibilityDropdownContent = () => { closeDropdown(); }; - const visibilityLabels: Record = { - [VISIBILITY_ANY_DEVICE]: t`Any device`, - [VISIBILITY_MOBILE]: t`Mobile`, - [VISIBILITY_DESKTOP]: t`Desktop`, - }; + const visibilityLabels = useVisibilityLabels(); return ( diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/constants/VisibilityAnyDevice.ts b/packages/twenty-front/src/modules/side-panel/pages/page-layout/constants/VisibilityAnyDevice.ts new file mode 100644 index 0000000000..817b7dba56 --- /dev/null +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/constants/VisibilityAnyDevice.ts @@ -0,0 +1 @@ +export const VISIBILITY_ANY_DEVICE = 'any-device'; diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/constants/VisibilityDesktop.ts b/packages/twenty-front/src/modules/side-panel/pages/page-layout/constants/VisibilityDesktop.ts new file mode 100644 index 0000000000..fe2e7c1c9f --- /dev/null +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/constants/VisibilityDesktop.ts @@ -0,0 +1 @@ +export const VISIBILITY_DESKTOP = 'desktop'; diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/constants/VisibilityMobile.ts b/packages/twenty-front/src/modules/side-panel/pages/page-layout/constants/VisibilityMobile.ts new file mode 100644 index 0000000000..1cf903ecca --- /dev/null +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/constants/VisibilityMobile.ts @@ -0,0 +1 @@ +export const VISIBILITY_MOBILE = 'mobile'; diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/constants/VisibilityOptions.ts b/packages/twenty-front/src/modules/side-panel/pages/page-layout/constants/VisibilityOptions.ts new file mode 100644 index 0000000000..a5b2b7c4f8 --- /dev/null +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/constants/VisibilityOptions.ts @@ -0,0 +1,9 @@ +import { VISIBILITY_ANY_DEVICE } from '@/side-panel/pages/page-layout/constants/VisibilityAnyDevice'; +import { VISIBILITY_DESKTOP } from '@/side-panel/pages/page-layout/constants/VisibilityDesktop'; +import { VISIBILITY_MOBILE } from '@/side-panel/pages/page-layout/constants/VisibilityMobile'; + +export const VISIBILITY_OPTIONS = [ + { id: VISIBILITY_ANY_DEVICE }, + { id: VISIBILITY_MOBILE }, + { id: VISIBILITY_DESKTOP }, +] as const; diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/constants/settings/TabSettingsSelectableItemIds.ts b/packages/twenty-front/src/modules/side-panel/pages/page-layout/constants/settings/TabSettingsSelectableItemIds.ts index 49d71d6ec4..bbd00c663f 100644 --- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/constants/settings/TabSettingsSelectableItemIds.ts +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/constants/settings/TabSettingsSelectableItemIds.ts @@ -3,6 +3,7 @@ export const TAB_SETTINGS_SELECTABLE_ITEM_IDS = { MOVE_RIGHT: 'tab-move-right', SET_AS_PINNED: 'tab-set-as-pinned', DUPLICATE: 'tab-duplicate', + VISIBILITY_RESTRICTION: 'tab-visibility-restriction', RESET_TO_DEFAULT: 'tab-reset-to-default', DELETE: 'tab-delete', } as const; diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/hooks/useTranslatedVisibilityLabel.ts b/packages/twenty-front/src/modules/side-panel/pages/page-layout/hooks/useTranslatedVisibilityLabel.ts new file mode 100644 index 0000000000..3195f31003 --- /dev/null +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/hooks/useTranslatedVisibilityLabel.ts @@ -0,0 +1,14 @@ +import { getVisibilityLabel } from '@/side-panel/pages/page-layout/utils/getVisibilityLabel'; +import { useLingui } from '@lingui/react/macro'; + +export const useTranslatedVisibilityLabel = ( + expression: string | null | undefined, +): string => { + const { t } = useLingui(); + + return getVisibilityLabel(expression, { + anyDevice: t`Any device`, + mobile: t`Mobile`, + desktop: t`Desktop`, + }); +}; diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/hooks/useVisibilityLabels.ts b/packages/twenty-front/src/modules/side-panel/pages/page-layout/hooks/useVisibilityLabels.ts new file mode 100644 index 0000000000..b2e1035433 --- /dev/null +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/hooks/useVisibilityLabels.ts @@ -0,0 +1,14 @@ +import { VISIBILITY_ANY_DEVICE } from '@/side-panel/pages/page-layout/constants/VisibilityAnyDevice'; +import { VISIBILITY_DESKTOP } from '@/side-panel/pages/page-layout/constants/VisibilityDesktop'; +import { VISIBILITY_MOBILE } from '@/side-panel/pages/page-layout/constants/VisibilityMobile'; +import { useLingui } from '@lingui/react/macro'; + +export const useVisibilityLabels = (): Record => { + const { t } = useLingui(); + + return { + [VISIBILITY_ANY_DEVICE]: t`Any device`, + [VISIBILITY_MOBILE]: t`Mobile`, + [VISIBILITY_DESKTOP]: t`Desktop`, + }; +}; diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/utils/expressionToOptionId.ts b/packages/twenty-front/src/modules/side-panel/pages/page-layout/utils/expressionToOptionId.ts new file mode 100644 index 0000000000..11a8296dc5 --- /dev/null +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/utils/expressionToOptionId.ts @@ -0,0 +1,23 @@ +import { EXPRESSION_DEVICE_DESKTOP } from '@/side-panel/pages/page-layout/constants/ExpressionDeviceDesktop'; +import { EXPRESSION_DEVICE_MOBILE } from '@/side-panel/pages/page-layout/constants/ExpressionDeviceMobile'; +import { VISIBILITY_ANY_DEVICE } from '@/side-panel/pages/page-layout/constants/VisibilityAnyDevice'; +import { VISIBILITY_DESKTOP } from '@/side-panel/pages/page-layout/constants/VisibilityDesktop'; +import { VISIBILITY_MOBILE } from '@/side-panel/pages/page-layout/constants/VisibilityMobile'; + +export const expressionToOptionId = ( + expression: string | null | undefined, +): string => { + if (!expression) { + return VISIBILITY_ANY_DEVICE; + } + + if (expression === EXPRESSION_DEVICE_MOBILE) { + return VISIBILITY_MOBILE; + } + + if (expression === EXPRESSION_DEVICE_DESKTOP) { + return VISIBILITY_DESKTOP; + } + + return VISIBILITY_ANY_DEVICE; +}; diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/utils/optionIdToExpression.ts b/packages/twenty-front/src/modules/side-panel/pages/page-layout/utils/optionIdToExpression.ts new file mode 100644 index 0000000000..be5c399cc8 --- /dev/null +++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/utils/optionIdToExpression.ts @@ -0,0 +1,15 @@ +import { EXPRESSION_DEVICE_DESKTOP } from '@/side-panel/pages/page-layout/constants/ExpressionDeviceDesktop'; +import { EXPRESSION_DEVICE_MOBILE } from '@/side-panel/pages/page-layout/constants/ExpressionDeviceMobile'; +import { VISIBILITY_DESKTOP } from '@/side-panel/pages/page-layout/constants/VisibilityDesktop'; +import { VISIBILITY_MOBILE } from '@/side-panel/pages/page-layout/constants/VisibilityMobile'; + +export const optionIdToExpression = (optionId: string): string | null => { + switch (optionId) { + case VISIBILITY_MOBILE: + return EXPRESSION_DEVICE_MOBILE; + case VISIBILITY_DESKTOP: + return EXPRESSION_DEVICE_DESKTOP; + default: + return null; + } +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout-tabs.template.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout-tabs.template.ts index fbf8967348..f49c7eed2e 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout-tabs.template.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout-tabs.template.ts @@ -140,7 +140,7 @@ export const TAB_PROPS = { title: 'Note', position: 15, icon: 'IconNotes', - layoutMode: PageLayoutTabLayoutMode.CANVAS, + layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST, }, flow: { title: 'Flow', @@ -203,13 +203,13 @@ export const WIDGET_PROPS = { title: 'Note', type: WidgetType.FIELD_RICH_TEXT, gridPosition: GRID_POSITIONS.RICH_TEXT, - position: CANVAS_LAYOUT_POSITIONS.DEFAULT, + position: VERTICAL_LIST_LAYOUT_POSITIONS.FIRST, }, taskRichText: { title: 'Task', type: WidgetType.FIELD_RICH_TEXT, gridPosition: GRID_POSITIONS.RICH_TEXT, - position: CANVAS_LAYOUT_POSITIONS.DEFAULT, + position: VERTICAL_LIST_LAYOUT_POSITIONS.FIRST, }, workflow: { title: 'Flow',