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:
Raphaël Bosi
2025-03-05 12:02:31 +01:00
committed by GitHub
parent 03c945ef97
commit f3e667a651
19 changed files with 173 additions and 40 deletions

1
.nvmrc
View File

@ -1 +0,0 @@
18.17.1

View File

@ -54,7 +54,7 @@
"@sniptt/guards": "^0.2.0", "@sniptt/guards": "^0.2.0",
"@stoplight/elements": "^8.0.5", "@stoplight/elements": "^8.0.5",
"@swc/jest": "^0.2.29", "@swc/jest": "^0.2.29",
"@tabler/icons-react": "^2.44.0", "@tabler/icons-react": "^3.31.0",
"@types/dompurify": "^3.0.5", "@types/dompurify": "^3.0.5",
"@types/facepaint": "^1.2.5", "@types/facepaint": "^1.2.5",
"@types/lodash.camelcase": "^4.3.7", "@types/lodash.camelcase": "^4.3.7",

View File

@ -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>
);
};

View File

@ -70,7 +70,7 @@ export const RightDrawerActionMenuDropdown = () => {
}} }}
data-select-disable data-select-disable
clickableComponent={ clickableComponent={
<Button title="Actions" hotkeys={[getOsControlSymbol(), 'O']} /> <Button title="Options" hotkeys={[getOsControlSymbol(), 'O']} />
} }
dropdownPlacement="top-end" dropdownPlacement="top-end"
dropdownOffset={{ y: parseInt(theme.spacing(2), 10) }} dropdownOffset={{ y: parseInt(theme.spacing(2), 10) }}

View File

@ -16,11 +16,11 @@ import { msg } from '@lingui/core/macro';
import { userEvent, waitFor, within } from '@storybook/test'; import { userEvent, waitFor, within } from '@storybook/test';
import { import {
ComponentDecorator, ComponentDecorator,
getCanvasElementForDropdownTesting,
IconFileExport, IconFileExport,
IconHeart, IconHeart,
IconTrash, IconTrash,
MenuItemAccent, MenuItemAccent,
getCanvasElementForDropdownTesting,
} from 'twenty-ui'; } from 'twenty-ui';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator'; import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
@ -124,19 +124,19 @@ export const WithButtonClicks: Story = {
play: async () => { play: async () => {
const canvas = within(getCanvasElementForDropdownTesting()); const canvas = within(getCanvasElementForDropdownTesting());
let actionButton = await canvas.findByText('Actions'); let actionButton = await canvas.findByText('Options');
await userEvent.click(actionButton); await userEvent.click(actionButton);
const deleteButton = await canvas.findByText('Delete'); const deleteButton = await canvas.findByText('Delete');
await userEvent.click(deleteButton); await userEvent.click(deleteButton);
actionButton = await canvas.findByText('Actions'); actionButton = await canvas.findByText('Options');
await userEvent.click(actionButton); await userEvent.click(actionButton);
const addToFavoritesButton = await canvas.findByText('Add to favorites'); const addToFavoritesButton = await canvas.findByText('Add to favorites');
await userEvent.click(addToFavoritesButton); await userEvent.click(addToFavoritesButton);
actionButton = await canvas.findByText('Actions'); actionButton = await canvas.findByText('Options');
await userEvent.click(actionButton); await userEvent.click(actionButton);
const exportButton = await canvas.findByText('Export'); const exportButton = await canvas.findByText('Export');

View File

@ -22,11 +22,14 @@ import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelect
import { isCommandMenuClosingState } from '@/command-menu/states/isCommandMenuClosingState'; import { isCommandMenuClosingState } from '@/command-menu/states/isCommandMenuClosingState';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages'; import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId'; 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 { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState'; import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState'; import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType'; 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 { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState'; import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState';
import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2'; import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
@ -278,6 +281,56 @@ export const useCommandMenu = () => {
) )
.getValue(); .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 const Icon = objectMetadataItem?.icon
? getIcon(objectMetadataItem.icon) ? getIcon(objectMetadataItem.icon)
: getIcon('IconList'); : getIcon('IconList');

View File

@ -4,6 +4,7 @@ import { ActionMenuComponentInstanceContext } from '@/action-menu/states/context
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
import { RecordFilterGroupsComponentInstanceContext } from '@/object-record/record-filter-group/states/context/RecordFilterGroupsComponentInstanceContext'; import { RecordFilterGroupsComponentInstanceContext } from '@/object-record/record-filter-group/states/context/RecordFilterGroupsComponentInstanceContext';
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext'; 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 { isNewViewableRecordLoadingState } from '@/object-record/record-right-drawer/states/isNewViewableRecordLoading';
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState'; import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState'; import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState';
@ -52,11 +53,11 @@ export const RightDrawerRecord = () => {
> >
<ContextStoreComponentInstanceContext.Provider <ContextStoreComponentInstanceContext.Provider
value={{ value={{
instanceId: `record-show-${objectRecordId}`, instanceId: RIGHT_DRAWER_RECORD_INSTANCE_ID,
}} }}
> >
<ActionMenuComponentInstanceContext.Provider <ActionMenuComponentInstanceContext.Provider
value={{ instanceId: `record-show-${objectRecordId}` }} value={{ instanceId: RIGHT_DRAWER_RECORD_INSTANCE_ID }}
> >
<StyledRightDrawerRecord isMobile={isMobile}> <StyledRightDrawerRecord isMobile={isMobile}>
<RecordFieldValueSelectorContextProvider> <RecordFieldValueSelectorContextProvider>

View File

@ -0,0 +1 @@
export const RIGHT_DRAWER_RECORD_INSTANCE_ID = 'right-drawer-record';

View File

@ -1,4 +1,5 @@
import { RecordShowRightDrawerActionMenu } from '@/action-menu/components/RecordShowRightDrawerActionMenu'; import { RecordShowRightDrawerActionMenu } from '@/action-menu/components/RecordShowRightDrawerActionMenu';
import { RecordShowRightDrawerOpenRecordButton } from '@/action-menu/components/RecordShowRightDrawerOpenRecordButton';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { isNewViewableRecordLoadingState } from '@/object-record/record-right-drawer/states/isNewViewableRecordLoading'; import { isNewViewableRecordLoadingState } from '@/object-record/record-right-drawer/states/isNewViewableRecordLoading';
import { CardComponents } from '@/object-record/record-show/components/CardComponents'; import { CardComponents } from '@/object-record/record-show/components/CardComponents';
@ -133,8 +134,16 @@ export const ShowPageSubContainer = ({
<StyledContentContainer isInRightDrawer={isInRightDrawer}> <StyledContentContainer isInRightDrawer={isInRightDrawer}>
{renderActiveTabContent()} {renderActiveTabContent()}
</StyledContentContainer> </StyledContentContainer>
{isInRightDrawer && recordFromStore && !recordFromStore.deletedAt && ( {isInRightDrawer && recordFromStore && (
<RightDrawerFooter actions={[<RecordShowRightDrawerActionMenu />]} /> <RightDrawerFooter
actions={[
<RecordShowRightDrawerActionMenu />,
<RecordShowRightDrawerOpenRecordButton
objectNameSingular={targetableObject.targetObjectNameSingular}
record={recordFromStore}
/>,
]}
/>
)} )}
</StyledShowPageRightContainer> </StyledShowPageRightContainer>
</> </>

View File

@ -3,7 +3,7 @@ import { useTheme } from '@emotion/react';
import IconMicrosoftRaw from '../assets/microsoft.svg?react'; import IconMicrosoftRaw from '../assets/microsoft.svg?react';
interface IconMicrosoftProps { interface IconMicrosoftProps {
size?: number; size?: number | string;
} }
export const IconMicrosoft = (props: IconMicrosoftProps) => { export const IconMicrosoft = (props: IconMicrosoftProps) => {

View File

@ -3,7 +3,7 @@ import { useTheme } from '@emotion/react';
import IconMicrosoftCalendarRaw from '../assets/microsoft-calendar.svg?react'; import IconMicrosoftCalendarRaw from '../assets/microsoft-calendar.svg?react';
interface IconMicrosoftCalendarProps { interface IconMicrosoftCalendarProps {
size?: number; size?: number | string;
} }
export const IconMicrosoftCalendar = (props: IconMicrosoftCalendarProps) => { export const IconMicrosoftCalendar = (props: IconMicrosoftCalendarProps) => {

View File

@ -3,7 +3,7 @@ import { useTheme } from '@emotion/react';
import IconMicrosoftOutlookRaw from '../assets/microsoft-outlook.svg?react'; import IconMicrosoftOutlookRaw from '../assets/microsoft-outlook.svg?react';
interface IconMicrosoftOutlookProps { interface IconMicrosoftOutlookProps {
size?: number; size?: number | string;
} }
export const IconMicrosoftOutlook = (props: IconMicrosoftOutlookProps) => { export const IconMicrosoftOutlook = (props: IconMicrosoftOutlookProps) => {

View File

@ -12,9 +12,10 @@ export {
IconArrowDown, IconArrowDown,
IconArrowLeft, IconArrowLeft,
IconArrowRight, IconArrowRight,
IconArrowsVertical,
IconArrowUp, IconArrowUp,
IconArrowUpRight, IconArrowUpRight,
IconArrowsDiagonal,
IconArrowsVertical,
IconAt, IconAt,
IconBaselineDensitySmall, IconBaselineDensitySmall,
IconBell, IconBell,
@ -30,6 +31,7 @@ export {
IconBrandLinkedin, IconBrandLinkedin,
IconBrandX, IconBrandX,
IconBriefcase, IconBriefcase,
IconBrowserMaximize,
IconBuildingSkyscraper, IconBuildingSkyscraper,
IconCalendar, IconCalendar,
IconCalendarDue, IconCalendarDue,
@ -42,8 +44,8 @@ export {
IconChevronDown, IconChevronDown,
IconChevronLeft, IconChevronLeft,
IconChevronRight, IconChevronRight,
IconChevronsRight,
IconChevronUp, IconChevronUp,
IconChevronsRight,
IconCircleDot, IconCircleDot,
IconCircleOff, IconCircleOff,
IconCirclePlus, IconCirclePlus,
@ -53,19 +55,15 @@ export {
IconClockPlay, IconClockPlay,
IconClockShare, IconClockShare,
IconCode, IconCode,
IconStepInto,
IconLogin2,
IconLogout,
IconCodeCircle, IconCodeCircle,
IconCoins, IconCoins,
IconColorSwatch, IconColorSwatch,
IconMessageCircle as IconComment, IconMessageCircle as IconComment,
IconCube,
IconTypography,
IconCopy, IconCopy,
IconCreativeCommonsSa, IconCreativeCommonsSa,
IconCreditCard, IconCreditCard,
IconCsv, IconCsv,
IconCube,
IconCurrencyAfghani, IconCurrencyAfghani,
IconCurrencyBahraini, IconCurrencyBahraini,
IconCurrencyBaht, IconCurrencyBaht,
@ -186,6 +184,8 @@ export {
IconLoader, IconLoader,
IconLock, IconLock,
IconLockOpen, IconLockOpen,
IconLogin2,
IconLogout,
IconMail, IconMail,
IconMailCog, IconMailCog,
IconMap, IconMap,
@ -250,6 +250,7 @@ export {
IconSquareKey, IconSquareKey,
IconSquareRoundedCheck, IconSquareRoundedCheck,
IconSquareRoundedX, IconSquareRoundedX,
IconStepInto,
IconTable, IconTable,
IconTag, IconTag,
IconTags, IconTags,
@ -263,6 +264,7 @@ export {
IconTimelineEvent, IconTimelineEvent,
IconTrash, IconTrash,
IconTrashX, IconTrashX,
IconTypography,
IconUnlink, IconUnlink,
IconUpload, IconUpload,
IconUser, IconUser,
@ -278,4 +280,4 @@ export {
IconX, IconX,
} from '@tabler/icons-react'; } from '@tabler/icons-react';
export type { TablerIconsProps } from '@tabler/icons-react'; export type { IconProps as TablerIconsProps } from '@tabler/icons-react';

View File

@ -893,6 +893,7 @@ import {
IconBroadcastOff, IconBroadcastOff,
IconBrowser, IconBrowser,
IconBrowserCheck, IconBrowserCheck,
IconBrowserMaximize,
IconBrowserOff, IconBrowserOff,
IconBrowserPlus, IconBrowserPlus,
IconBrowserX, IconBrowserX,
@ -8393,4 +8394,5 @@ export const ALL_ICONS = {
IconZoomReset, IconZoomReset,
IconZzz, IconZzz,
IconZzzOff, IconZzzOff,
IconBrowserMaximize,
}; };

View File

@ -1,9 +1,10 @@
// eslint-disable-next-line no-restricted-imports
import { FunctionComponent } from 'react'; import { FunctionComponent } from 'react';
export type IconComponentProps = { export type IconComponentProps = {
className?: string; className?: string;
size?: number; size?: number | string;
stroke?: number; stroke?: number | string;
color?: string; color?: string;
}; };

View File

@ -1,5 +1,5 @@
import React, { useEffect, useMemo } from 'react';
import { motion, useAnimation } from 'framer-motion'; import { motion, useAnimation } from 'framer-motion';
import { useEffect, useMemo } from 'react';
interface CircularProgressBarProps { interface CircularProgressBarProps {
size?: number; size?: number;

View File

@ -1,7 +1,7 @@
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { IconComponent } from '@ui/display'; import { IconComponent } from '@ui/display';
import React from 'react'; import React, { FunctionComponent } from 'react';
export type MainButtonVariant = 'primary' | 'secondary'; export type MainButtonVariant = 'primary' | 'secondary';
@ -103,7 +103,7 @@ const StyledButton = styled.button<
`; `;
type MainButtonProps = Props & { type MainButtonProps = Props & {
Icon?: IconComponent; Icon?: IconComponent | FunctionComponent<{ size: number }>;
}; };
export const MainButton = ({ export const MainButton = ({

View File

@ -1,7 +1,7 @@
export type { TablerIconsProps } from '@tabler/icons-react';
export { export {
IconBook, IconBook,
IconChevronDown, IconChevronDown,
IconChevronLeft, IconChevronLeft,
IconChevronRight, IconChevronRight,
} from '@tabler/icons-react'; } from '@tabler/icons-react';
export type { IconProps as TablerIconsProps } from '@tabler/icons-react';

View File

@ -16553,22 +16553,21 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@tabler/icons-react@npm:^2.44.0": "@tabler/icons-react@npm:^3.31.0":
version: 2.47.0 version: 3.31.0
resolution: "@tabler/icons-react@npm:2.47.0" resolution: "@tabler/icons-react@npm:3.31.0"
dependencies: dependencies:
"@tabler/icons": "npm:2.47.0" "@tabler/icons": "npm:3.31.0"
prop-types: "npm:^15.7.2"
peerDependencies: peerDependencies:
react: ^16.5.1 || ^17.0.0 || ^18.0.0 react: ">= 16"
checksum: 10c0/09a78a1b88aab69a11cd783c7d1bb75aeabe2ed03e3f957d00113dfde974ec73ee850551c430664634469ebcf32db749497a81bd30c01f2fb3425884cba7429c checksum: 10c0/aceefe1b6c12a2e64921900f49c09c0c24bcd837dfb3d4274914065e33a5175944f1e25ce9a80d110272e5ae0fefc24062a38f1ec75a4ec79c63d40acb9d1d12
languageName: node languageName: node
linkType: hard linkType: hard
"@tabler/icons@npm:2.47.0": "@tabler/icons@npm:3.31.0":
version: 2.47.0 version: 3.31.0
resolution: "@tabler/icons@npm:2.47.0" resolution: "@tabler/icons@npm:3.31.0"
checksum: 10c0/ffdffa1289ca958f7d7c06c19e35c51a664d916df510fb44fb673abc2031747f2b3c47e21ba4c1ca07a6872b793f2063179bbbfb6afd1c68b522ca333460a7f3 checksum: 10c0/03fcfff0b705e1474030acee2ff0523bfa1fda7a29a33d031a9fd8e3b52ecc2e0380f9cf5e81bd054b30dc0acdb819b1e08ab746805be8d660934a08b2c3545e
languageName: node languageName: node
linkType: hard linkType: hard
@ -47190,7 +47189,7 @@ __metadata:
"@swc/core": "npm:1.7.42" "@swc/core": "npm:1.7.42"
"@swc/helpers": "npm:~0.5.2" "@swc/helpers": "npm:~0.5.2"
"@swc/jest": "npm:^0.2.29" "@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/jest-dom": "npm:^6.1.5"
"@testing-library/react": "npm:14.0.0" "@testing-library/react": "npm:14.0.0"
"@types/addressparser": "npm:^1.0.3" "@types/addressparser": "npm:^1.0.3"