diff --git a/packages/sanity/src/core/bundles/components/BundleMenu.tsx b/packages/sanity/src/core/bundles/components/BundlesMenu.tsx similarity index 92% rename from packages/sanity/src/core/bundles/components/BundleMenu.tsx rename to packages/sanity/src/core/bundles/components/BundlesMenu.tsx index ca4309c9821..a9e6bd65772 100644 --- a/packages/sanity/src/core/bundles/components/BundleMenu.tsx +++ b/packages/sanity/src/core/bundles/components/BundlesMenu.tsx @@ -1,9 +1,10 @@ import {CheckmarkIcon} from '@sanity/icons' -import {Box, Flex, Menu, MenuButton, MenuDivider, MenuItem, Spinner, Text} from '@sanity/ui' -import {type ReactElement, useCallback, useMemo} from 'react' +// eslint-disable-next-line no-restricted-imports -- MenuItem requires props, only supported by @sanity/ui +import {Box, Flex, Menu, MenuDivider, MenuItem, Spinner, Text} from '@sanity/ui' +import {memo, type ReactElement, useCallback, useMemo} from 'react' import {styled} from 'styled-components' -import {Tooltip} from '../../../ui-components' +import {MenuButton, Tooltip} from '../../../ui-components' import {useTranslation} from '../../i18n' import {type BundleDocument} from '../../store/bundles/types' import {useBundles} from '../../store/bundles/useBundles' @@ -32,7 +33,7 @@ interface BundleListProps { /** * @internal */ -export function BundleMenu(props: BundleListProps): JSX.Element { +export const BundlesMenu = memo(function BundlesMenu(props: BundleListProps): ReactElement { const {bundles, loading, actions, button, perspective} = props const {deletedBundles} = useBundles() const {currentGlobalBundle, setPerspective} = usePerspective(perspective) @@ -166,4 +167,4 @@ export function BundleMenu(props: BundleListProps): JSX.Element { /> ) -} +}) diff --git a/packages/sanity/src/core/bundles/components/__tests__/BundleMenu.test.tsx b/packages/sanity/src/core/bundles/components/__tests__/BundlesMenu.test.tsx similarity index 89% rename from packages/sanity/src/core/bundles/components/__tests__/BundleMenu.test.tsx rename to packages/sanity/src/core/bundles/components/__tests__/BundlesMenu.test.tsx index 5654ddb71ea..fa853d565ea 100644 --- a/packages/sanity/src/core/bundles/components/__tests__/BundleMenu.test.tsx +++ b/packages/sanity/src/core/bundles/components/__tests__/BundlesMenu.test.tsx @@ -1,14 +1,14 @@ import {beforeEach, describe, expect, it, jest} from '@jest/globals' -import {Button} from '@sanity/ui' import {fireEvent, render, screen, within} from '@testing-library/react' import userEvent from '@testing-library/user-event' import {act} from 'react' import {type BundleDocument, useBundles} from 'sanity' import {createTestProvider} from '../../../../../test/testUtils/TestProvider' +import {Button} from '../../../../ui-components' import {usePerspective} from '../../hooks/usePerspective' import {LATEST} from '../../util/const' -import {BundleMenu} from '../BundleMenu' +import {BundlesMenu} from '../BundlesMenu' jest.mock('../../hooks/usePerspective', () => ({ usePerspective: jest.fn().mockReturnValue({ @@ -27,9 +27,9 @@ jest.mock('../../../store/bundles/useBundles', () => ({ const mockUseBundles = useBundles as jest.Mock -describe('BundleMenu', () => { +describe('BundlesMenu', () => { const mockUsePerspective = usePerspective as jest.Mock - const ButtonTest = + const ButtonTest = - ))} + + {COLOR_HUES.map((hue) => ( + + ))} + @@ -101,18 +98,22 @@ export function BundleIconEditorPicker(props: { /> - {Object.entries(icons) - .filter(([key]) => !iconSearchQuery || key.includes(iconSearchQuery.toLowerCase())) - .map(([key, icon]) => ( - diff --git a/packages/sanity/src/core/bundles/components/index.ts b/packages/sanity/src/core/bundles/components/index.ts index c526de6e9d8..1d65239d954 100644 --- a/packages/sanity/src/core/bundles/components/index.ts +++ b/packages/sanity/src/core/bundles/components/index.ts @@ -1,3 +1,3 @@ export * from './BundleBadge' -export * from './BundleMenu' +export * from './BundlesMenu' export * from './panes/BundleActions' diff --git a/packages/sanity/src/core/i18n/bundles/studio.ts b/packages/sanity/src/core/i18n/bundles/studio.ts index 63399d091da..dd7b3526546 100644 --- a/packages/sanity/src/core/i18n/bundles/studio.ts +++ b/packages/sanity/src/core/i18n/bundles/studio.ts @@ -125,14 +125,15 @@ export const studioLocaleStrings = defineLocalesResources('studio', { /** Text shown in usage dialog for an image asset when there are zero, one or more documents using the *unnamed* image **/ 'asset-source.usage-list.documents-using-image_unnamed_zero': 'No documents are using this image', + /** Label when a release has been deleted by a different user */ + 'banners.deleted-bundle-banner.text': + "The '{{title}}' release has been deleted.", /** Action message to add document to release */ 'bundle.action.add-to-release': 'Add to {{title}}', /** Action message for when document is already in release */ 'bundle.action.already-in-release': 'Already in release {{title}}', /** Action message for creating releases */ 'bundle.action.create': 'Create release', - /** Label when a release has been deleted by a different user */ - 'bundle.deleted-toast-title': "The '{{title}}' release has been deleted", /** Label for tooltip on deleted release */ 'bundle.deleted-tooltip': 'This release has been deleted', /** Title for creating releases dialog */ @@ -143,8 +144,12 @@ export const studioLocaleStrings = defineLocalesResources('studio', { 'bundle.form.description': 'Description', /** Placeholder for the icon and colour picker */ 'bundle.form.search-icon': 'Search icons', + /** Tooltip label for the icon display */ + 'bundle.form.search-icon-tooltip': 'Select release icon', /** Label for the title form field when creating releases */ 'bundle.form.title': 'Title', + /** Tooltip for the dropdown to show all versions of document */ + 'bundle.version-list.tooltip': 'See all document versions', /** Action message for navigating to next month */ 'calendar.action.go-to-next-month': 'Go to next month', diff --git a/packages/sanity/src/core/index.ts b/packages/sanity/src/core/index.ts index 1444a8c0046..08cf8423ffa 100644 --- a/packages/sanity/src/core/index.ts +++ b/packages/sanity/src/core/index.ts @@ -1,7 +1,7 @@ export { BundleActions, BundleBadge, - BundleMenu, + BundlesMenu, getBundleSlug, getDocumentIsInPerspective, LATEST, diff --git a/packages/sanity/src/core/releases/components/BundleMenuButton/BundleMenuButton.tsx b/packages/sanity/src/core/releases/components/BundleMenuButton/BundleMenuButton.tsx index 891772ca52d..2497678af8d 100644 --- a/packages/sanity/src/core/releases/components/BundleMenuButton/BundleMenuButton.tsx +++ b/packages/sanity/src/core/releases/components/BundleMenuButton/BundleMenuButton.tsx @@ -5,12 +5,12 @@ import { TrashIcon, UnarchiveIcon, } from '@sanity/icons' -import {Button, Menu, MenuButton, Spinner, Text, useToast} from '@sanity/ui' +import {Menu, Spinner, Text, useToast} from '@sanity/ui' import {useState} from 'react' import {useTranslation} from 'sanity' import {useRouter} from 'sanity/router' -import {Dialog, MenuItem} from '../../../../ui-components' +import {Button, Dialog, MenuButton, MenuItem} from '../../../../ui-components' import {BundleDetailsDialog} from '../../../bundles/components/dialog/BundleDetailsDialog' import {type BundleDocument} from '../../../store/bundles/types' import {useBundleOperations} from '../../../store/bundles/useBundleOperations' @@ -80,7 +80,7 @@ export const BundleMenuButton = ({disabled, bundle, documentCount}: BundleMenuBu disabled={bundleMenuDisabled || isPerformingOperation} icon={isPerformingOperation ? Spinner : EllipsisHorizontalIcon} mode="bleed" - padding={2} + tooltipProps={{content: t('menu.tooltip')}} aria-label={t('menu.label')} data-testid="release-menu-button" /> diff --git a/packages/sanity/src/core/releases/components/ReleaseDocumentPreview.tsx b/packages/sanity/src/core/releases/components/ReleaseDocumentPreview.tsx index c38c3c3aee5..132b6b25b09 100644 --- a/packages/sanity/src/core/releases/components/ReleaseDocumentPreview.tsx +++ b/packages/sanity/src/core/releases/components/ReleaseDocumentPreview.tsx @@ -1,10 +1,11 @@ import {ErrorOutlineIcon} from '@sanity/icons' import {type PreviewValue} from '@sanity/types' -import {Card, Text, Tooltip} from '@sanity/ui' +import {Card, Text} from '@sanity/ui' import {type ForwardedRef, forwardRef, useMemo} from 'react' import {DocumentPreviewPresence, useDocumentPresence} from 'sanity' import {IntentLink} from 'sanity/router' +import {Tooltip} from '../../../ui-components' import {useTranslation} from '../../i18n' import {SanityDefaultPreview} from '../../preview/components/SanityDefaultPreview' import {getPublishedId} from '../../util/draftUtils' diff --git a/packages/sanity/src/core/releases/components/Table/TableHeader.tsx b/packages/sanity/src/core/releases/components/Table/TableHeader.tsx index 2ebc5c87e96..620eebfa2ad 100644 --- a/packages/sanity/src/core/releases/components/Table/TableHeader.tsx +++ b/packages/sanity/src/core/releases/components/Table/TableHeader.tsx @@ -1,10 +1,17 @@ import {ArrowDownIcon, ArrowUpIcon, SearchIcon} from '@sanity/icons' -import {Button, type ButtonProps, Card, Flex, Stack, TextInput} from '@sanity/ui' +import {Card, Flex, Stack, TextInput} from '@sanity/ui' +import {Button, type ButtonProps} from '../../../../ui-components' import {useTableContext} from './TableProvider' import {type HeaderProps, type TableHeaderProps} from './types' -const SortHeaderButton = ({header, text}: ButtonProps & HeaderProps) => { +const SortHeaderButton = ({ + header, + text, +}: Omit & + HeaderProps & { + text: string + }) => { const {sort, setSortColumn} = useTableContext() const sortIcon = sort?.direction === 'asc' ? ArrowUpIcon : ArrowDownIcon @@ -13,9 +20,7 @@ const SortHeaderButton = ({header, text}: ButtonProps & HeaderProps) => { iconRight={header.sorting && sort?.column === header.id ? sortIcon : undefined} onClick={() => setSortColumn(String(header.id))} mode="bleed" - padding={2} - radius={3} - space={1} + size="default" text={text} /> ) diff --git a/packages/sanity/src/core/releases/i18n/resources.ts b/packages/sanity/src/core/releases/i18n/resources.ts index 2e40a931db1..b1e3752c908 100644 --- a/packages/sanity/src/core/releases/i18n/resources.ts +++ b/packages/sanity/src/core/releases/i18n/resources.ts @@ -64,6 +64,8 @@ const releasesLocaleStrings = { /** Label for the release menu */ 'menu.label': 'Release menu', + /** Tooltip for the release menu */ + 'menu.tooltip': 'Actions', /** Text for when no archived releases are found */ 'no-archived-release': 'No archived releases', diff --git a/packages/sanity/src/core/releases/tool/detail/__tests__/ReleaseReview.test.tsx b/packages/sanity/src/core/releases/tool/detail/__tests__/ReleaseReview.test.tsx index 45e29451e0b..f876aecaf8a 100644 --- a/packages/sanity/src/core/releases/tool/detail/__tests__/ReleaseReview.test.tsx +++ b/packages/sanity/src/core/releases/tool/detail/__tests__/ReleaseReview.test.tsx @@ -131,8 +131,8 @@ jest.mock('sanity/router', () => ({ }), })) -jest.mock('../../../../bundles/components/BundleMenu', () => ({ - BundleMenu: () =>
BundleMenu
, +jest.mock('../../../../bundles/components/BundlesMenu', () => ({ + BundlesMenu: () =>
BundlesMenu
, })) jest.mock('../../../../preview/useObserveDocument', () => { diff --git a/packages/sanity/src/core/releases/tool/detail/__tests__/ReleaseSummary.test.tsx b/packages/sanity/src/core/releases/tool/detail/__tests__/ReleaseSummary.test.tsx index 34148f7b2c6..b740ca92459 100644 --- a/packages/sanity/src/core/releases/tool/detail/__tests__/ReleaseSummary.test.tsx +++ b/packages/sanity/src/core/releases/tool/detail/__tests__/ReleaseSummary.test.tsx @@ -23,8 +23,8 @@ jest.mock('../../../../user-color', () => ({ useUserColor: jest.fn().mockReturnValue('red'), })) -jest.mock('../../../../bundles/components/BundleMenu', () => ({ - BundleMenu: () =>
BundleMenu
, +jest.mock('../../../../bundles/components/BundlesMenu', () => ({ + BundlesMenu: () =>
BundlesMenu
, })) const timeNow = new Date() diff --git a/packages/sanity/src/core/releases/tool/overview/ReleasesOverview.tsx b/packages/sanity/src/core/releases/tool/overview/ReleasesOverview.tsx index bc8d7786965..289b7b26539 100644 --- a/packages/sanity/src/core/releases/tool/overview/ReleasesOverview.tsx +++ b/packages/sanity/src/core/releases/tool/overview/ReleasesOverview.tsx @@ -1,9 +1,9 @@ import {AddIcon} from '@sanity/icons' -import {Box, Button, type ButtonMode, Container, Flex, Heading, Stack, Text} from '@sanity/ui' +import {Box, type ButtonMode, Container, Flex, Heading, Stack, Text} from '@sanity/ui' import {isBefore} from 'date-fns' import {type MouseEventHandler, useCallback, useEffect, useMemo, useRef, useState} from 'react' -import {Button as StudioButton} from '../../../../ui-components' +import {Button, Button as StudioButton} from '../../../../ui-components' import {BundleDetailsDialog} from '../../../bundles/components/dialog/BundleDetailsDialog' import {useTranslation} from '../../../i18n' import {type BundleDocument, useBundles} from '../../../store' @@ -134,8 +134,6 @@ export function ReleasesOverview() { icon={AddIcon} disabled={isCreateBundleDialogOpen} onClick={() => setIsCreateBundleDialogOpen(true)} - padding={2} - space={2} text={tCore('bundle.action.create')} /> ), diff --git a/packages/sanity/src/core/studio/components/navbar/perspective/GlobalPerspectiveMenu.tsx b/packages/sanity/src/core/studio/components/navbar/perspective/GlobalPerspectiveMenu.tsx index dbafa590a71..8ee36b4335b 100644 --- a/packages/sanity/src/core/studio/components/navbar/perspective/GlobalPerspectiveMenu.tsx +++ b/packages/sanity/src/core/studio/components/navbar/perspective/GlobalPerspectiveMenu.tsx @@ -1,10 +1,12 @@ import {AddIcon} from '@sanity/icons' -import {Button, MenuItem} from '@sanity/ui' +// eslint-disable-next-line no-restricted-imports -- Bundle Button requires more fine-grained styling than studio button +import {Button} from '@sanity/ui' import {useCallback, useMemo, useState} from 'react' import {useTranslation} from 'sanity' +import {MenuItem} from '../../../../../ui-components' import {BundleBadge} from '../../../../bundles/components/BundleBadge' -import {BundleMenu} from '../../../../bundles/components/BundleMenu' +import {BundlesMenu} from '../../../../bundles/components/BundlesMenu' import {BundleDetailsDialog} from '../../../../bundles/components/dialog/BundleDetailsDialog' import {usePerspective} from '../../../../bundles/hooks/usePerspective' import {useBundles} from '../../../../store/bundles' @@ -32,23 +34,29 @@ export function GlobalPerspectiveMenu(): JSX.Element { [bundles, deletedBundles], ) + const bundleMenuButton = useMemo( + () => ( + + ), + [hue, icon, title], + ) + + const bundleMenuActions = useMemo( + () => ( + + ), + [handleCreateBundleClick, t], + ) + return ( <> - - - - } + - } + actions={bundleMenuActions} /> {createBundleDialogOpen && ( diff --git a/packages/sanity/src/structure/panes/document/documentPanel/DocumentPanel.tsx b/packages/sanity/src/structure/panes/document/documentPanel/DocumentPanel.tsx index dac52251855..8076d7f30d9 100644 --- a/packages/sanity/src/structure/panes/document/documentPanel/DocumentPanel.tsx +++ b/packages/sanity/src/structure/panes/document/documentPanel/DocumentPanel.tsx @@ -1,6 +1,6 @@ import {BoundaryElementProvider, Box, Flex, PortalProvider, usePortal} from '@sanity/ui' import {createElement, useEffect, useMemo, useRef, useState} from 'react' -import {ScrollContainer, useTimelineSelector, VirtualizerScrollInstanceProvider} from 'sanity' +import {ScrollContainer, VirtualizerScrollInstanceProvider} from 'sanity' import {css, styled} from 'styled-components' import {PaneContent, usePane, usePaneLayout} from '../../../components' @@ -10,7 +10,7 @@ import {DocumentInspectorPanel} from '../documentInspector' import {InspectDialog} from '../inspectDialog' import {useDocumentPane} from '../useDocumentPane' import { - DeletedDocumentBanner, + DeletedDocumentBanners, DeprecatedDocumentTypeBanner, PermissionCheckBanner, ReferenceChangedBanner, @@ -51,7 +51,6 @@ export const DocumentPanel = function DocumentPanel(props: DocumentPanelProps) { activeViewId, displayed, documentId, - documentType, editState, inspector, value, @@ -60,10 +59,6 @@ export const DocumentPanel = function DocumentPanel(props: DocumentPanelProps) { schemaType, permissions, isPermissionsLoading, - isDeleting, - isDeleted, - timelineStore, - onChange, } = useDocumentPane() const {collapsed: layoutCollapsed} = usePaneLayout() const {collapsed} = usePane() @@ -114,11 +109,6 @@ export const DocumentPanel = function DocumentPanel(props: DocumentPanelProps) { [activeView, displayed, documentId, editState?.draft, editState?.published, schemaType, value], ) - const lastNonDeletedRevId = useTimelineSelector( - timelineStore, - (state) => state.lastNonDeletedRevId, - ) - const isLiveEdit = isLiveEditEnabled(schemaType) // Scroll to top as `documentId` changes @@ -140,6 +130,42 @@ export const DocumentPanel = function DocumentPanel(props: DocumentPanelProps) { const showInspector = Boolean(!collapsed && inspector) + const banners = useMemo(() => { + if (activeView.type === 'form' && isLiveEdit && ready) { + return ( + + ) + } + + if (activeView.type !== 'form' || isPermissionsLoading || !ready) return null + + return ( + <> + + + + + + ) + }, [ + activeView.type, + displayed, + documentId, + isLiveEdit, + isPermissionsLoading, + permissions?.granted, + ready, + requiredPermission, + schemaType, + ]) + return ( @@ -154,28 +180,7 @@ export const DocumentPanel = function DocumentPanel(props: DocumentPanelProps) { scrollElement={documentScrollElement} containerElement={formContainerElement} > - {activeView.type === 'form' && isLiveEdit && ready && ( - - )} - - {activeView.type === 'form' && !isPermissionsLoading && ready && ( - <> - - {!isDeleting && isDeleted && ( - - )} - - - - )} - + {banners} { - if (revisionId) { - restore.execute(revisionId) - navigateIntent('edit', {id: documentId, type: documentType}) - } - }, [documentId, documentType, navigateIntent, restore, revisionId]) - const {t} = useTranslation(structureLocaleNamespace) - - return ( - - {t('banners.deleted-document-banner.text')} - - } - data-testid="deleted-document-banner" - icon={ReadOnlyIcon} - /> - ) -} diff --git a/packages/sanity/src/structure/panes/document/documentPanel/banners/DeletedDocumentBanners.tsx b/packages/sanity/src/structure/panes/document/documentPanel/banners/DeletedDocumentBanners.tsx new file mode 100644 index 00000000000..0fbde4aeed4 --- /dev/null +++ b/packages/sanity/src/structure/panes/document/documentPanel/banners/DeletedDocumentBanners.tsx @@ -0,0 +1,112 @@ +import {DocumentRemoveIcon, ReadOnlyIcon} from '@sanity/icons' +import {Text} from '@sanity/ui' +import {useCallback, useEffect, useState} from 'react' +import { + type BundleDocument, + LATEST, + Translate, + useBundles, + useDocumentOperation, + usePerspective, + useTimelineSelector, + useTranslation, +} from 'sanity' +import {useRouter} from 'sanity/router' + +import {structureLocaleNamespace} from '../../../../i18n' +import {useDocumentPane} from '../../useDocumentPane' +import {Banner} from './Banner' + +const useIsLocaleBundleDeleted = () => { + const {currentGlobalBundle} = usePerspective() + const {data: bundles, deletedBundles} = useBundles() + const {slug: currentGlobalBundleSlug} = currentGlobalBundle + const [checkedOutBundleSlug, setCheckedOutBundleSlug] = useState( + currentGlobalBundleSlug, + ) + + useEffect(() => { + /** + * only named versions other than default (drafts and published) are considered checked-out + */ + if (currentGlobalBundleSlug !== LATEST.slug) { + setCheckedOutBundleSlug(currentGlobalBundleSlug) + } + }, [currentGlobalBundleSlug, setCheckedOutBundleSlug]) + + if (!checkedOutBundleSlug || !Object.keys(deletedBundles).length || !bundles?.length) { + return null + } + return deletedBundles[checkedOutBundleSlug] +} + +export function DeletedDocumentBanners() { + const {isDeleted, isDeleting} = useDocumentPane() + const deletedCheckedOutBundle = useIsLocaleBundleDeleted() + + if (deletedCheckedOutBundle) + return + + if (isDeleted && !isDeleting) return +} + +function DeletedDocumentBanner() { + const {documentId, documentType, timelineStore} = useDocumentPane() + const {restore} = useDocumentOperation(documentId, documentType) + const {navigateIntent} = useRouter() + const lastNonDeletedRevId = useTimelineSelector( + timelineStore, + (state) => state.lastNonDeletedRevId, + ) + const handleRestore = useCallback(() => { + if (lastNonDeletedRevId) { + restore.execute(lastNonDeletedRevId) + navigateIntent('edit', {id: documentId, type: documentType}) + } + }, [documentId, documentType, navigateIntent, restore, lastNonDeletedRevId]) + + const {t} = useTranslation(structureLocaleNamespace) + + return ( + + {t('banners.deleted-document-banner.text')} + + } + data-testid="deleted-document-banner" + icon={ReadOnlyIcon} + /> + ) +} + +const DeletedBundleBanner = ({deletedBundle}: {deletedBundle: BundleDocument}) => { + const {t} = useTranslation() + + const {title: deletedBundleTitle} = deletedBundle + + return ( + + + + } + data-testid="deleted-bundle-banner" + icon={DocumentRemoveIcon} + /> + ) +} diff --git a/packages/sanity/src/structure/panes/document/documentPanel/banners/__tests__/DeletedDocumentBanners.test.tsx b/packages/sanity/src/structure/panes/document/documentPanel/banners/__tests__/DeletedDocumentBanners.test.tsx new file mode 100644 index 00000000000..ff1ac9f4386 --- /dev/null +++ b/packages/sanity/src/structure/panes/document/documentPanel/banners/__tests__/DeletedDocumentBanners.test.tsx @@ -0,0 +1,108 @@ +import {describe, expect, it, jest} from '@jest/globals' +import {render, screen} from '@testing-library/react' +import {type BundleDocument, LATEST, useBundles, usePerspective} from 'sanity' +import {useDocumentPane} from 'sanity/structure' + +import {createTestProvider} from '../../../../../../../test/testUtils/TestProvider' +import {structureUsEnglishLocaleBundle} from '../../../../../i18n' +import {DeletedDocumentBanners} from '../DeletedDocumentBanners' + +jest.mock('../../../useDocumentPane', () => ({ + useDocumentPane: jest.fn(), +})) + +jest.mock('../../../../../../core/bundles/hooks/usePerspective', () => ({ + usePerspective: jest.fn(), +})) + +jest.mock('../../../../../../core/store/bundles/useBundles', () => ({ + useBundles: jest.fn(), +})) + +jest.mock('../../../../../../core/store/_legacy/history/useTimelineSelector', () => ({ + useTimelineSelector: jest.fn(), +})) + +const mockUseDocumentPane = useDocumentPane as jest.Mock +const mockUseBundles = useBundles as jest.Mock +const mockUsePerspective = usePerspective as jest.Mock + +const renderTest = async () => { + const wrapper = await createTestProvider({resources: [structureUsEnglishLocaleBundle]}) + + return render(, {wrapper}) +} + +describe('DeletedDocumentBanners', () => { + it('does not show either banner when document is not deleted', async () => { + mockUsePerspective.mockReturnValue({currentGlobalBundle: {slug: 'test'}} as ReturnType< + typeof usePerspective + >) + mockUseBundles.mockReturnValue({ + data: [], + deletedBundles: {}, + dispatch: jest.fn(), + loading: false, + }) + mockUseDocumentPane.mockReturnValue({ + isDeleted: false, + isDeleting: false, + documentId: 'test', + } as ReturnType) + + await renderTest() + expect(screen.queryByTestId('deleted-document-banner')).toBeNull() + expect(screen.queryByTestId('deleted-bundle-banner')).toBeNull() + }) + + it('prefers to show bundle deleted banner when document was in a bundle', async () => { + const mockBundleDocument: BundleDocument = {slug: 'test'} as BundleDocument + mockUsePerspective.mockReturnValue({currentGlobalBundle: mockBundleDocument} as ReturnType< + typeof usePerspective + >) + mockUseBundles.mockReturnValue({ + data: [mockBundleDocument], + deletedBundles: {test: mockBundleDocument}, + dispatch: jest.fn(), + loading: false, + }) + mockUseDocumentPane.mockReturnValue({ + isDeleted: true, + isDeleting: false, + } as ReturnType) + + await renderTest() + + const fallbackBanner = screen.queryByTestId('deleted-document-banner') + const bundleBanner = screen.queryByTestId('deleted-bundle-banner') + expect(fallbackBanner).toBeNull() + expect(bundleBanner).toBeInTheDocument() + }) + + it('shows the fallback document deleted banner when document was not in a bundle', async () => { + const mockBundleDocument: BundleDocument = {slug: 'test'} as BundleDocument + + mockUsePerspective.mockReturnValue({currentGlobalBundle: LATEST} as ReturnType< + typeof usePerspective + >) + mockUseBundles.mockReturnValue({ + data: [mockBundleDocument], + deletedBundles: {test: mockBundleDocument}, + dispatch: jest.fn(), + loading: false, + }) + mockUseDocumentPane.mockReturnValue({ + isDeleted: true, + isDeleting: false, + documentId: 'versions.test-version.test-document', + } as ReturnType) + + await renderTest() + + const fallbackBanner = screen.queryByTestId('deleted-document-banner') + const bundleBanner = screen.queryByTestId('deleted-bundle-banner') + + expect(bundleBanner).toBeNull() + expect(fallbackBanner).toBeInTheDocument() + }) +}) diff --git a/packages/sanity/src/structure/panes/document/documentPanel/banners/index.ts b/packages/sanity/src/structure/panes/document/documentPanel/banners/index.ts index 10f706b9937..ad88037494c 100644 --- a/packages/sanity/src/structure/panes/document/documentPanel/banners/index.ts +++ b/packages/sanity/src/structure/panes/document/documentPanel/banners/index.ts @@ -1,4 +1,4 @@ -export * from './DeletedDocumentBanner' +export * from './DeletedDocumentBanners' export * from './DeprecatedDocumentTypeBanner' export * from './PermissionCheckBanner' export * from './ReferenceChangedBanner' diff --git a/packages/sanity/src/structure/panes/document/documentPanel/header/DocumentHeaderTitle.tsx b/packages/sanity/src/structure/panes/document/documentPanel/header/DocumentHeaderTitle.tsx index 9a48a11e5ff..6e05c51324e 100644 --- a/packages/sanity/src/structure/panes/document/documentPanel/header/DocumentHeaderTitle.tsx +++ b/packages/sanity/src/structure/panes/document/documentPanel/header/DocumentHeaderTitle.tsx @@ -1,14 +1,14 @@ import {DocumentIcon} from '@sanity/icons' import {Flex, Text} from '@sanity/ui' -import {createElement, type ReactElement} from 'react' +import {createElement, memo, type ReactElement} from 'react' import {unstable_useValuePreview as useValuePreview, useTranslation} from 'sanity' import {structureLocaleNamespace} from '../../../../i18n' import {useDocumentPane} from '../../useDocumentPane' import {DocumentPerspectiveMenu} from './perspective/DocumentPerspectiveMenu' -export function DocumentHeaderTitle(): ReactElement { - const {documentId, connectionState, schemaType, title, value: documentValue} = useDocumentPane() +export const DocumentHeaderTitle = memo(function DocumentHeaderTitle(): ReactElement { + const {connectionState, schemaType, title, value: documentValue} = useDocumentPane() const subscribed = Boolean(documentValue) && connectionState !== 'connecting' const {error, value} = useValuePreview({ @@ -63,4 +63,4 @@ export function DocumentHeaderTitle(): ReactElement { ) -} +}) diff --git a/packages/sanity/src/structure/panes/document/documentPanel/header/DocumentPanelHeader.tsx b/packages/sanity/src/structure/panes/document/documentPanel/header/DocumentPanelHeader.tsx index 9f19229d337..f65286dfd19 100644 --- a/packages/sanity/src/structure/panes/document/documentPanel/header/DocumentPanelHeader.tsx +++ b/packages/sanity/src/structure/panes/document/documentPanel/header/DocumentPanelHeader.tsx @@ -43,6 +43,8 @@ export interface DocumentPanelHeaderProps { menuItems: PaneMenuItem[] } +const documentPaneHeaderTitle = + export const DocumentPanelHeader = memo( forwardRef(function DocumentPanelHeader( _props: DocumentPanelHeaderProps, @@ -138,7 +140,7 @@ export const DocumentPanelHeader = memo( border ref={ref} loading={connectionState === 'connecting'} - title={} + title={documentPaneHeaderTitle} tabs={showTabs && } tabIndex={tabIndex} backButton={ diff --git a/packages/sanity/src/structure/panes/document/documentPanel/header/perspective/DocumentPerspectiveMenu.tsx b/packages/sanity/src/structure/panes/document/documentPanel/header/perspective/DocumentPerspectiveMenu.tsx index e853a10326c..d01a829db92 100644 --- a/packages/sanity/src/structure/panes/document/documentPanel/header/perspective/DocumentPerspectiveMenu.tsx +++ b/packages/sanity/src/structure/panes/document/documentPanel/header/perspective/DocumentPerspectiveMenu.tsx @@ -1,10 +1,12 @@ import {ChevronDownIcon} from '@sanity/icons' +// eslint-disable-next-line no-restricted-imports -- Bundle Button requires more fine-grained styling than studio button import {Box, Button} from '@sanity/ui' -import {useCallback} from 'react' -import {BundleBadge, BundleMenu, usePerspective} from 'sanity' +import {memo, useCallback, useMemo} from 'react' +import {BundleBadge, BundlesMenu, usePerspective, useTranslation} from 'sanity' import {useRouter} from 'sanity/router' import {styled} from 'styled-components' +import {Button as StudioButton} from '../../../../../../ui-components' import {usePaneRouter} from '../../../../../components' import {useDocumentPane} from '../../../useDocumentPane' @@ -12,8 +14,9 @@ const BadgeButton = styled(Button)({ cursor: 'pointer', }) -export function DocumentPerspectiveMenu(): JSX.Element { +export const DocumentPerspectiveMenu = memo(function DocumentPerspectiveMenu() { const paneRouter = usePaneRouter() + const {t} = useTranslation() const {currentGlobalBundle} = usePerspective(paneRouter.perspective) const {documentVersions, existsInBundle} = useDocumentPane() @@ -25,6 +28,17 @@ export function DocumentPerspectiveMenu(): JSX.Element { router.navigateIntent('release', {slug}) }, [router, slug]) + const bundlesMenuButton = useMemo( + () => ( + + ), + [t], + ) + return ( <> {currentGlobalBundle && existsInBundle && ( @@ -42,8 +56,8 @@ export function DocumentPerspectiveMenu(): JSX.Element { {/** TODO IS THIS STILL NEEDED? VS THE PICKER IN STUDIO NAVBAR? */} - } + ) -} +})