496 add open in full page button on command menu record page (#10659)
Closes https://github.com/twentyhq/core-team-issues/issues/496 I upgraded react tabler icons to the latest version to be able to use the newest icons. The option menu was no longer accessible on right drawer record pages, this pr fixes this and creates a new button which opens the record show page. This button is accessible via the shortcut `Command` + `Enter` https://github.com/user-attachments/assets/570071b2-4406-40bd-be48-a0e5e430ed70
This commit is contained in:
@ -54,7 +54,7 @@
|
||||
"@sniptt/guards": "^0.2.0",
|
||||
"@stoplight/elements": "^8.0.5",
|
||||
"@swc/jest": "^0.2.29",
|
||||
"@tabler/icons-react": "^2.44.0",
|
||||
"@tabler/icons-react": "^3.31.0",
|
||||
"@types/dompurify": "^3.0.5",
|
||||
"@types/facepaint": "^1.2.5",
|
||||
"@types/lodash.camelcase": "^4.3.7",
|
||||
|
||||
@ -0,0 +1,66 @@
|
||||
import { RightDrawerActionMenuDropdownHotkeyScope } from '@/action-menu/types/RightDrawerActionMenuDropdownHotkeyScope';
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { getLinkToShowPage } from '@/object-metadata/utils/getLinkToShowPage';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
||||
import styled from '@emotion/styled';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Button, IconBrowserMaximize, getOsControlSymbol } from 'twenty-ui';
|
||||
import { useNavigateApp } from '~/hooks/useNavigateApp';
|
||||
|
||||
const StyledLink = styled(Link)`
|
||||
text-decoration: none;
|
||||
`;
|
||||
|
||||
type RecordShowRightDrawerOpenRecordButtonProps = {
|
||||
objectNameSingular: string;
|
||||
record: ObjectRecord;
|
||||
};
|
||||
|
||||
export const RecordShowRightDrawerOpenRecordButton = ({
|
||||
objectNameSingular,
|
||||
record,
|
||||
}: RecordShowRightDrawerOpenRecordButtonProps) => {
|
||||
const { closeCommandMenu } = useCommandMenu();
|
||||
|
||||
const to = getLinkToShowPage(objectNameSingular, record);
|
||||
|
||||
const navigate = useNavigateApp();
|
||||
|
||||
const handleOpenRecord = () => {
|
||||
navigate(AppPath.RecordShowPage, {
|
||||
objectNameSingular,
|
||||
objectRecordId: record.id,
|
||||
});
|
||||
closeCommandMenu();
|
||||
};
|
||||
|
||||
useScopedHotkeys(
|
||||
['ctrl+Enter,meta+Enter'],
|
||||
handleOpenRecord,
|
||||
AppHotkeyScope.CommandMenuOpen,
|
||||
[closeCommandMenu, navigate, objectNameSingular, record.id],
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
['ctrl+Enter,meta+Enter'],
|
||||
handleOpenRecord,
|
||||
RightDrawerActionMenuDropdownHotkeyScope.RightDrawerActionMenuDropdown,
|
||||
[closeCommandMenu, navigate, objectNameSingular, record.id],
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledLink to={to} onClick={closeCommandMenu}>
|
||||
<Button
|
||||
title="Open"
|
||||
variant="primary"
|
||||
accent="blue"
|
||||
size="medium"
|
||||
Icon={IconBrowserMaximize}
|
||||
hotkeys={[getOsControlSymbol(), '⏎']}
|
||||
/>
|
||||
</StyledLink>
|
||||
);
|
||||
};
|
||||
@ -70,7 +70,7 @@ export const RightDrawerActionMenuDropdown = () => {
|
||||
}}
|
||||
data-select-disable
|
||||
clickableComponent={
|
||||
<Button title="Actions" hotkeys={[getOsControlSymbol(), 'O']} />
|
||||
<Button title="Options" hotkeys={[getOsControlSymbol(), 'O']} />
|
||||
}
|
||||
dropdownPlacement="top-end"
|
||||
dropdownOffset={{ y: parseInt(theme.spacing(2), 10) }}
|
||||
|
||||
@ -16,11 +16,11 @@ import { msg } from '@lingui/core/macro';
|
||||
import { userEvent, waitFor, within } from '@storybook/test';
|
||||
import {
|
||||
ComponentDecorator,
|
||||
getCanvasElementForDropdownTesting,
|
||||
IconFileExport,
|
||||
IconHeart,
|
||||
IconTrash,
|
||||
MenuItemAccent,
|
||||
getCanvasElementForDropdownTesting,
|
||||
} from 'twenty-ui';
|
||||
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||
|
||||
@ -124,19 +124,19 @@ export const WithButtonClicks: Story = {
|
||||
play: async () => {
|
||||
const canvas = within(getCanvasElementForDropdownTesting());
|
||||
|
||||
let actionButton = await canvas.findByText('Actions');
|
||||
let actionButton = await canvas.findByText('Options');
|
||||
await userEvent.click(actionButton);
|
||||
|
||||
const deleteButton = await canvas.findByText('Delete');
|
||||
await userEvent.click(deleteButton);
|
||||
|
||||
actionButton = await canvas.findByText('Actions');
|
||||
actionButton = await canvas.findByText('Options');
|
||||
await userEvent.click(actionButton);
|
||||
|
||||
const addToFavoritesButton = await canvas.findByText('Add to favorites');
|
||||
await userEvent.click(addToFavoritesButton);
|
||||
|
||||
actionButton = await canvas.findByText('Actions');
|
||||
actionButton = await canvas.findByText('Options');
|
||||
await userEvent.click(actionButton);
|
||||
|
||||
const exportButton = await canvas.findByText('Export');
|
||||
|
||||
@ -22,11 +22,14 @@ import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelect
|
||||
import { isCommandMenuClosingState } from '@/command-menu/states/isCommandMenuClosingState';
|
||||
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
|
||||
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
|
||||
import { contextStoreCurrentObjectMetadataItemComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemComponentState';
|
||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
|
||||
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
||||
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
|
||||
import { RIGHT_DRAWER_RECORD_INSTANCE_ID } from '@/object-record/record-right-drawer/constants/RightDrawerRecordInstanceId';
|
||||
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
|
||||
import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState';
|
||||
import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
|
||||
@ -278,6 +281,56 @@ export const useCommandMenu = () => {
|
||||
)
|
||||
.getValue();
|
||||
|
||||
if (!objectMetadataItem) {
|
||||
throw new Error(
|
||||
`No object metadata item found for object name ${objectNameSingular}`,
|
||||
);
|
||||
}
|
||||
|
||||
set(
|
||||
contextStoreCurrentObjectMetadataItemComponentState.atomFamily({
|
||||
instanceId: RIGHT_DRAWER_RECORD_INSTANCE_ID,
|
||||
}),
|
||||
objectMetadataItem,
|
||||
);
|
||||
|
||||
set(
|
||||
contextStoreTargetedRecordsRuleComponentState.atomFamily({
|
||||
instanceId: RIGHT_DRAWER_RECORD_INSTANCE_ID,
|
||||
}),
|
||||
{
|
||||
mode: 'selection',
|
||||
selectedRecordIds: [recordId],
|
||||
},
|
||||
);
|
||||
|
||||
set(
|
||||
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
|
||||
instanceId: RIGHT_DRAWER_RECORD_INSTANCE_ID,
|
||||
}),
|
||||
1,
|
||||
);
|
||||
|
||||
set(
|
||||
contextStoreCurrentViewTypeComponentState.atomFamily({
|
||||
instanceId: RIGHT_DRAWER_RECORD_INSTANCE_ID,
|
||||
}),
|
||||
ContextStoreViewType.ShowPage,
|
||||
);
|
||||
|
||||
set(
|
||||
contextStoreCurrentViewIdComponentState.atomFamily({
|
||||
instanceId: RIGHT_DRAWER_RECORD_INSTANCE_ID,
|
||||
}),
|
||||
snapshot
|
||||
.getLoadable(
|
||||
contextStoreCurrentViewIdComponentState.atomFamily({
|
||||
instanceId: MAIN_CONTEXT_STORE_INSTANCE_ID,
|
||||
}),
|
||||
)
|
||||
.getValue(),
|
||||
);
|
||||
|
||||
const Icon = objectMetadataItem?.icon
|
||||
? getIcon(objectMetadataItem.icon)
|
||||
: getIcon('IconList');
|
||||
|
||||
@ -4,6 +4,7 @@ import { ActionMenuComponentInstanceContext } from '@/action-menu/states/context
|
||||
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
||||
import { RecordFilterGroupsComponentInstanceContext } from '@/object-record/record-filter-group/states/context/RecordFilterGroupsComponentInstanceContext';
|
||||
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
|
||||
import { RIGHT_DRAWER_RECORD_INSTANCE_ID } from '@/object-record/record-right-drawer/constants/RightDrawerRecordInstanceId';
|
||||
import { isNewViewableRecordLoadingState } from '@/object-record/record-right-drawer/states/isNewViewableRecordLoading';
|
||||
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
|
||||
import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState';
|
||||
@ -52,11 +53,11 @@ export const RightDrawerRecord = () => {
|
||||
>
|
||||
<ContextStoreComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId: `record-show-${objectRecordId}`,
|
||||
instanceId: RIGHT_DRAWER_RECORD_INSTANCE_ID,
|
||||
}}
|
||||
>
|
||||
<ActionMenuComponentInstanceContext.Provider
|
||||
value={{ instanceId: `record-show-${objectRecordId}` }}
|
||||
value={{ instanceId: RIGHT_DRAWER_RECORD_INSTANCE_ID }}
|
||||
>
|
||||
<StyledRightDrawerRecord isMobile={isMobile}>
|
||||
<RecordFieldValueSelectorContextProvider>
|
||||
|
||||
@ -0,0 +1 @@
|
||||
export const RIGHT_DRAWER_RECORD_INSTANCE_ID = 'right-drawer-record';
|
||||
@ -1,4 +1,5 @@
|
||||
import { RecordShowRightDrawerActionMenu } from '@/action-menu/components/RecordShowRightDrawerActionMenu';
|
||||
import { RecordShowRightDrawerOpenRecordButton } from '@/action-menu/components/RecordShowRightDrawerOpenRecordButton';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
import { isNewViewableRecordLoadingState } from '@/object-record/record-right-drawer/states/isNewViewableRecordLoading';
|
||||
import { CardComponents } from '@/object-record/record-show/components/CardComponents';
|
||||
@ -133,8 +134,16 @@ export const ShowPageSubContainer = ({
|
||||
<StyledContentContainer isInRightDrawer={isInRightDrawer}>
|
||||
{renderActiveTabContent()}
|
||||
</StyledContentContainer>
|
||||
{isInRightDrawer && recordFromStore && !recordFromStore.deletedAt && (
|
||||
<RightDrawerFooter actions={[<RecordShowRightDrawerActionMenu />]} />
|
||||
{isInRightDrawer && recordFromStore && (
|
||||
<RightDrawerFooter
|
||||
actions={[
|
||||
<RecordShowRightDrawerActionMenu />,
|
||||
<RecordShowRightDrawerOpenRecordButton
|
||||
objectNameSingular={targetableObject.targetObjectNameSingular}
|
||||
record={recordFromStore}
|
||||
/>,
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</StyledShowPageRightContainer>
|
||||
</>
|
||||
|
||||
@ -3,7 +3,7 @@ import { useTheme } from '@emotion/react';
|
||||
import IconMicrosoftRaw from '../assets/microsoft.svg?react';
|
||||
|
||||
interface IconMicrosoftProps {
|
||||
size?: number;
|
||||
size?: number | string;
|
||||
}
|
||||
|
||||
export const IconMicrosoft = (props: IconMicrosoftProps) => {
|
||||
|
||||
@ -3,7 +3,7 @@ import { useTheme } from '@emotion/react';
|
||||
import IconMicrosoftCalendarRaw from '../assets/microsoft-calendar.svg?react';
|
||||
|
||||
interface IconMicrosoftCalendarProps {
|
||||
size?: number;
|
||||
size?: number | string;
|
||||
}
|
||||
|
||||
export const IconMicrosoftCalendar = (props: IconMicrosoftCalendarProps) => {
|
||||
|
||||
@ -3,7 +3,7 @@ import { useTheme } from '@emotion/react';
|
||||
import IconMicrosoftOutlookRaw from '../assets/microsoft-outlook.svg?react';
|
||||
|
||||
interface IconMicrosoftOutlookProps {
|
||||
size?: number;
|
||||
size?: number | string;
|
||||
}
|
||||
|
||||
export const IconMicrosoftOutlook = (props: IconMicrosoftOutlookProps) => {
|
||||
|
||||
@ -12,9 +12,10 @@ export {
|
||||
IconArrowDown,
|
||||
IconArrowLeft,
|
||||
IconArrowRight,
|
||||
IconArrowsVertical,
|
||||
IconArrowUp,
|
||||
IconArrowUpRight,
|
||||
IconArrowsDiagonal,
|
||||
IconArrowsVertical,
|
||||
IconAt,
|
||||
IconBaselineDensitySmall,
|
||||
IconBell,
|
||||
@ -30,6 +31,7 @@ export {
|
||||
IconBrandLinkedin,
|
||||
IconBrandX,
|
||||
IconBriefcase,
|
||||
IconBrowserMaximize,
|
||||
IconBuildingSkyscraper,
|
||||
IconCalendar,
|
||||
IconCalendarDue,
|
||||
@ -42,8 +44,8 @@ export {
|
||||
IconChevronDown,
|
||||
IconChevronLeft,
|
||||
IconChevronRight,
|
||||
IconChevronsRight,
|
||||
IconChevronUp,
|
||||
IconChevronsRight,
|
||||
IconCircleDot,
|
||||
IconCircleOff,
|
||||
IconCirclePlus,
|
||||
@ -53,19 +55,15 @@ export {
|
||||
IconClockPlay,
|
||||
IconClockShare,
|
||||
IconCode,
|
||||
IconStepInto,
|
||||
IconLogin2,
|
||||
IconLogout,
|
||||
IconCodeCircle,
|
||||
IconCoins,
|
||||
IconColorSwatch,
|
||||
IconMessageCircle as IconComment,
|
||||
IconCube,
|
||||
IconTypography,
|
||||
IconCopy,
|
||||
IconCreativeCommonsSa,
|
||||
IconCreditCard,
|
||||
IconCsv,
|
||||
IconCube,
|
||||
IconCurrencyAfghani,
|
||||
IconCurrencyBahraini,
|
||||
IconCurrencyBaht,
|
||||
@ -186,6 +184,8 @@ export {
|
||||
IconLoader,
|
||||
IconLock,
|
||||
IconLockOpen,
|
||||
IconLogin2,
|
||||
IconLogout,
|
||||
IconMail,
|
||||
IconMailCog,
|
||||
IconMap,
|
||||
@ -250,6 +250,7 @@ export {
|
||||
IconSquareKey,
|
||||
IconSquareRoundedCheck,
|
||||
IconSquareRoundedX,
|
||||
IconStepInto,
|
||||
IconTable,
|
||||
IconTag,
|
||||
IconTags,
|
||||
@ -263,6 +264,7 @@ export {
|
||||
IconTimelineEvent,
|
||||
IconTrash,
|
||||
IconTrashX,
|
||||
IconTypography,
|
||||
IconUnlink,
|
||||
IconUpload,
|
||||
IconUser,
|
||||
@ -278,4 +280,4 @@ export {
|
||||
IconX,
|
||||
} from '@tabler/icons-react';
|
||||
|
||||
export type { TablerIconsProps } from '@tabler/icons-react';
|
||||
export type { IconProps as TablerIconsProps } from '@tabler/icons-react';
|
||||
|
||||
@ -893,6 +893,7 @@ import {
|
||||
IconBroadcastOff,
|
||||
IconBrowser,
|
||||
IconBrowserCheck,
|
||||
IconBrowserMaximize,
|
||||
IconBrowserOff,
|
||||
IconBrowserPlus,
|
||||
IconBrowserX,
|
||||
@ -8393,4 +8394,5 @@ export const ALL_ICONS = {
|
||||
IconZoomReset,
|
||||
IconZzz,
|
||||
IconZzzOff,
|
||||
IconBrowserMaximize,
|
||||
};
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { FunctionComponent } from 'react';
|
||||
|
||||
export type IconComponentProps = {
|
||||
className?: string;
|
||||
size?: number;
|
||||
stroke?: number;
|
||||
size?: number | string;
|
||||
stroke?: number | string;
|
||||
color?: string;
|
||||
};
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { motion, useAnimation } from 'framer-motion';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
|
||||
interface CircularProgressBarProps {
|
||||
size?: number;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { IconComponent } from '@ui/display';
|
||||
import React from 'react';
|
||||
import React, { FunctionComponent } from 'react';
|
||||
|
||||
export type MainButtonVariant = 'primary' | 'secondary';
|
||||
|
||||
@ -103,7 +103,7 @@ const StyledButton = styled.button<
|
||||
`;
|
||||
|
||||
type MainButtonProps = Props & {
|
||||
Icon?: IconComponent;
|
||||
Icon?: IconComponent | FunctionComponent<{ size: number }>;
|
||||
};
|
||||
|
||||
export const MainButton = ({
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
export type { TablerIconsProps } from '@tabler/icons-react';
|
||||
export {
|
||||
IconBook,
|
||||
IconChevronDown,
|
||||
IconChevronLeft,
|
||||
IconChevronRight,
|
||||
} from '@tabler/icons-react';
|
||||
export type { IconProps as TablerIconsProps } from '@tabler/icons-react';
|
||||
|
||||
23
yarn.lock
23
yarn.lock
@ -16553,22 +16553,21 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tabler/icons-react@npm:^2.44.0":
|
||||
version: 2.47.0
|
||||
resolution: "@tabler/icons-react@npm:2.47.0"
|
||||
"@tabler/icons-react@npm:^3.31.0":
|
||||
version: 3.31.0
|
||||
resolution: "@tabler/icons-react@npm:3.31.0"
|
||||
dependencies:
|
||||
"@tabler/icons": "npm:2.47.0"
|
||||
prop-types: "npm:^15.7.2"
|
||||
"@tabler/icons": "npm:3.31.0"
|
||||
peerDependencies:
|
||||
react: ^16.5.1 || ^17.0.0 || ^18.0.0
|
||||
checksum: 10c0/09a78a1b88aab69a11cd783c7d1bb75aeabe2ed03e3f957d00113dfde974ec73ee850551c430664634469ebcf32db749497a81bd30c01f2fb3425884cba7429c
|
||||
react: ">= 16"
|
||||
checksum: 10c0/aceefe1b6c12a2e64921900f49c09c0c24bcd837dfb3d4274914065e33a5175944f1e25ce9a80d110272e5ae0fefc24062a38f1ec75a4ec79c63d40acb9d1d12
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tabler/icons@npm:2.47.0":
|
||||
version: 2.47.0
|
||||
resolution: "@tabler/icons@npm:2.47.0"
|
||||
checksum: 10c0/ffdffa1289ca958f7d7c06c19e35c51a664d916df510fb44fb673abc2031747f2b3c47e21ba4c1ca07a6872b793f2063179bbbfb6afd1c68b522ca333460a7f3
|
||||
"@tabler/icons@npm:3.31.0":
|
||||
version: 3.31.0
|
||||
resolution: "@tabler/icons@npm:3.31.0"
|
||||
checksum: 10c0/03fcfff0b705e1474030acee2ff0523bfa1fda7a29a33d031a9fd8e3b52ecc2e0380f9cf5e81bd054b30dc0acdb819b1e08ab746805be8d660934a08b2c3545e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -47190,7 +47189,7 @@ __metadata:
|
||||
"@swc/core": "npm:1.7.42"
|
||||
"@swc/helpers": "npm:~0.5.2"
|
||||
"@swc/jest": "npm:^0.2.29"
|
||||
"@tabler/icons-react": "npm:^2.44.0"
|
||||
"@tabler/icons-react": "npm:^3.31.0"
|
||||
"@testing-library/jest-dom": "npm:^6.1.5"
|
||||
"@testing-library/react": "npm:14.0.0"
|
||||
"@types/addressparser": "npm:^1.0.3"
|
||||
|
||||
Reference in New Issue
Block a user