From c50388ebfdeebb05d3ac029a08167ed415c4aabe Mon Sep 17 00:00:00 2001 From: Jordan Lawrence Date: Mon, 28 Oct 2024 16:59:13 +0000 Subject: [PATCH] feat: supporting toggling perspective layer visibility --- .../sanity/src/core/i18n/bundles/studio.ts | 2 + .../core/releases/hooks/usePerspective.tsx | 52 +++++++- .../releases/navbar/GlobalPerspectiveMenu.tsx | 1 - .../navbar/GlobalPerspectiveMenuItem.tsx | 122 +++++++++++------- .../navbar/PerspectiveLayerIndicator.tsx | 11 ++ .../navbar/ReleaseTypeMenuSection.tsx | 1 - packages/sanity/src/router/RouterProvider.tsx | 4 +- packages/sanity/src/router/stickyParams.ts | 2 +- packages/sanity/src/router/types.ts | 1 + 9 files changed, 138 insertions(+), 58 deletions(-) diff --git a/packages/sanity/src/core/i18n/bundles/studio.ts b/packages/sanity/src/core/i18n/bundles/studio.ts index fd83a4ca6d1..4200e3c7edf 100644 --- a/packages/sanity/src/core/i18n/bundles/studio.ts +++ b/packages/sanity/src/core/i18n/bundles/studio.ts @@ -1189,6 +1189,8 @@ export const studioLocaleStrings = defineLocalesResources('studio', { 'release.form.search-icon-tooltip': 'Select release icon', /** Label for the title form field when creating releases */ 'release.form.title': 'Title', + /** Tooltip for button to hide release visibility */ + 'release.layer.hide': 'Hide release', /** Tooltip for releases navigation in navbar */ 'release.navbar.tooltip': 'Releases', /** Label for the release type 'as soon as possible' */ diff --git a/packages/sanity/src/core/releases/hooks/usePerspective.tsx b/packages/sanity/src/core/releases/hooks/usePerspective.tsx index ed76e9cb30c..ff79e6f33eb 100644 --- a/packages/sanity/src/core/releases/hooks/usePerspective.tsx +++ b/packages/sanity/src/core/releases/hooks/usePerspective.tsx @@ -1,4 +1,4 @@ -import {useMemo} from 'react' +import {useCallback, useMemo} from 'react' import {useRouter} from 'sanity/router' import {type ReleaseType, useReleases} from '../../store/release' @@ -20,9 +20,13 @@ export interface PerspectiveValue { /* Return the current global release */ currentGlobalBundle: CurrentPerspective /* Change the perspective in the studio based on the perspective name */ - setPerspective: (releaseId: string) => void + setPerspective: (perspectiveId: string) => void /* change the perspective in the studio based on a release ID */ setPerspectiveFromRelease: (releaseId: string) => void + /* Add/remove excluded perspectives */ + toggleExcludedPerspective: (perspectiveId: string) => void + /* Check if a perspective is excluded */ + isPerspectiveExcluded: (perspectiveId: string) => boolean /** * The stacked array of releases ids ordered chronologically to represent the state of documents at the given point in time. */ @@ -38,6 +42,7 @@ export function usePerspective(): PerspectiveValue { const router = useRouter() const {data: releases} = useReleases() const perspective = router.stickyParams.perspective + const excludedPerspectives = router.stickyParams.excludedPerspectives?.split(',') // TODO: Should it be possible to set the perspective within a pane, rather than globally? const setPerspective = (releaseId: string | undefined) => { @@ -49,7 +54,13 @@ export function usePerspective(): PerspectiveValue { perspectiveParam = `bundle.${releaseId}` } - router.navigateStickyParam('perspective', perspectiveParam) + router.navigate({ + ...router.state, + _searchParams: [ + ['excludedPerspectives', ''], + ['perspective', perspectiveParam], + ], + }) } const selectedBundle = @@ -79,16 +90,45 @@ export function usePerspective(): PerspectiveValue { getReleasesPerspective({ releases, perspective, - // TODO: Implement excluded perspectives - excluded: [], + excluded: excludedPerspectives || [], }), - [releases, perspective], + [releases, perspective, excludedPerspectives], + ) + + const toggleExcludedPerspective = useCallback( + (excluded: string) => { + if (excluded === LATEST._id) return + const nextExcludedPerspectives: string[] = excludedPerspectives || [] + + const excludedPerspectiveId = excluded === 'published' ? 'published' : `bundle.${excluded}` + + if (excludedPerspectives?.includes(excludedPerspectiveId)) { + nextExcludedPerspectives.splice(nextExcludedPerspectives.indexOf(excludedPerspectiveId), 1) + } else { + nextExcludedPerspectives.push(excludedPerspectiveId) + } + + router.navigateStickyParam('excludedPerspectives', nextExcludedPerspectives.toString()) + }, + [excludedPerspectives, router], + ) + + const isPerspectiveExcluded = useCallback( + (perspectiveId: string) => + Boolean( + excludedPerspectives?.includes( + perspectiveId === 'published' ? 'published' : `bundle.${perspectiveId}`, + ), + ), + [excludedPerspectives], ) return { setPerspective, setPerspectiveFromRelease, + toggleExcludedPerspective, currentGlobalBundle: currentGlobalBundle, bundlesPerspective, + isPerspectiveExcluded, } } diff --git a/packages/sanity/src/core/releases/navbar/GlobalPerspectiveMenu.tsx b/packages/sanity/src/core/releases/navbar/GlobalPerspectiveMenu.tsx index 1efe8f789b7..03dbc625a24 100644 --- a/packages/sanity/src/core/releases/navbar/GlobalPerspectiveMenu.tsx +++ b/packages/sanity/src/core/releases/navbar/GlobalPerspectiveMenu.tsx @@ -139,7 +139,6 @@ export function GlobalPerspectiveMenu(): JSX.Element { <> diff --git a/packages/sanity/src/core/releases/navbar/GlobalPerspectiveMenuItem.tsx b/packages/sanity/src/core/releases/navbar/GlobalPerspectiveMenuItem.tsx index 147b5b4f1d4..e5de5f9a652 100644 --- a/packages/sanity/src/core/releases/navbar/GlobalPerspectiveMenuItem.tsx +++ b/packages/sanity/src/core/releases/navbar/GlobalPerspectiveMenuItem.tsx @@ -1,10 +1,16 @@ -import {EyeOpenIcon} from '@sanity/icons' +import {DotIcon, EyeClosedIcon, EyeOpenIcon} from '@sanity/icons' // eslint-disable-next-line no-restricted-imports -- custom use for MenuItem & Button not supported by ui-components import {Box, Button, Flex, MenuItem, Stack, Text} from '@sanity/ui' import {formatRelative} from 'date-fns' -import {forwardRef, type MouseEvent, useCallback} from 'react' -import {getReleaseTone, ReleaseAvatar, type ReleaseDocument} from 'sanity' -import {styled} from 'styled-components' +import {type CSSProperties, forwardRef, type MouseEvent, useCallback} from 'react' +import { + getBundleIdFromReleaseId, + getReleaseTone, + ReleaseAvatar, + type ReleaseDocument, + useTranslation, +} from 'sanity' +import {css, styled} from 'styled-components' import {Tooltip} from '../../../ui-components/tooltip' import {usePerspective} from '../hooks/usePerspective' @@ -21,24 +27,40 @@ export interface LayerRange { } } -const ToggleLayerButton = styled(Button)` - --card-fg-color: inherit; - --card-icon-color: inherit; +const ToggleLayerButton = styled(Button)<{$visible: boolean}>( + ({$visible}) => css` + --card-fg-color: inherit; + --card-icon-color: inherit; - background-color: inherit; - opacity: 0; + background-color: inherit; + opacity: ${$visible ? 0 : 1}; - @media (hover: hover) { - &:not([data-disabled='true']):hover { - --card-fg-color: inherit; - --card-icon-color: inherit; + @media (hover: hover) { + &:not([data-disabled='true']):hover { + --card-fg-color: inherit; + --card-icon-color: inherit; + } } - } - [data-ui='MenuItem']:hover & { - opacity: 1; - } -` + [data-ui='MenuItem']:hover & { + opacity: 1; + } + `, +) + +const ExcludedLayerDot = () => ( + + + + + +) type rangePosition = 'first' | 'within' | 'last' | undefined @@ -58,21 +80,34 @@ export const GlobalPerspectiveMenuItem = forwardRef< { release: ReleaseDocument rangePosition: rangePosition - toggleable: boolean } >((props, ref) => { - const {release, rangePosition, toggleable} = props - // const {current, replace: replaceVersion, replaceToggle} = usePerspective() - const {currentGlobalBundle, setPerspectiveFromRelease, setPerspective} = usePerspective() + const {release, rangePosition} = props + const { + currentGlobalBundle, + setPerspectiveFromRelease, + setPerspective, + toggleExcludedPerspective, + isPerspectiveExcluded, + } = usePerspective() const active = release._id === currentGlobalBundle._id const first = rangePosition === 'first' const within = rangePosition === 'within' const last = rangePosition === 'last' const inRange = first || within || last - const handleToggleReleaseVisibility = useCallback((event: MouseEvent) => { - event.stopPropagation() - }, []) + const {t} = useTranslation() + + const releasePerspectiveId = + release._id === 'published' ? 'published' : getBundleIdFromReleaseId(release._id) + + const handleToggleReleaseVisibility = useCallback( + (event: MouseEvent) => { + event.stopPropagation() + toggleExcludedPerspective(releasePerspectiveId) + }, + [toggleExcludedPerspective, releasePerspectiveId], + ) const handleOnReleaseClick = useCallback( () => @@ -82,6 +117,9 @@ export const GlobalPerspectiveMenuItem = forwardRef< [release._id, setPerspective, setPerspectiveFromRelease], ) + const isReleasePerspectiveExcluded = isPerspectiveExcluded(releasePerspectiveId) + const canReleaseBeExcluded = inRange || (release._id === 'published' && !!currentGlobalBundle) + return ( - {/* {release.hidden ? ( - - ) : ( */} - + {isReleasePerspectiveExcluded ? ( + + ) : ( + + )} {/* )} */} @@ -118,11 +151,9 @@ export const GlobalPerspectiveMenuItem = forwardRef< paddingY={2} paddingRight={2} space={2} - style={ - { - // opacity: release.hidden ? 0.5 : undefined, - } - } + style={{ + opacity: isReleasePerspectiveExcluded ? 0.5 : undefined, + }} > {release.metadata.title} @@ -135,15 +166,12 @@ export const GlobalPerspectiveMenuItem = forwardRef< )} - {inRange && ( - // eslint-disable-next-line @sanity/i18n/no-attribute-string-literals - + {canReleaseBeExcluded && ( + [data-ui='MenuItem']:after { + margin-top: -3px; + border-top-left-radius: ${INDICATOR_WIDTH}px; + border-top-right-radius: ${INDICATOR_WIDTH}px; + } > [data-ui='MenuItem']:before { display: none; } @@ -75,6 +80,12 @@ export const GlobalPerspectiveMenuItemIndicator = styled.div<{ ${$last && css` + > [data-ui='MenuItem']:before { + // dot diameter (5px) - 1.6px stroke divided by 2 + padding-bottom: 1.7px; + border-bottom-left-radius: ${INDICATOR_WIDTH}px; + border-bottom-right-radius: ${INDICATOR_WIDTH}px; + } > [data-ui='MenuItem']:after { display: none; } diff --git a/packages/sanity/src/core/releases/navbar/ReleaseTypeMenuSection.tsx b/packages/sanity/src/core/releases/navbar/ReleaseTypeMenuSection.tsx index 003c730f101..e0657bd0fc4 100644 --- a/packages/sanity/src/core/releases/navbar/ReleaseTypeMenuSection.tsx +++ b/packages/sanity/src/core/releases/navbar/ReleaseTypeMenuSection.tsx @@ -62,7 +62,6 @@ export function ReleaseTypeMenuSection({ key={release._id} ref={getMenuItemRef(release._id)} rangePosition={getRangePosition(range, releaseTypeOffset + index)} - toggleable={releaseTypeOffset < lastIndex} /> ))} diff --git a/packages/sanity/src/router/RouterProvider.tsx b/packages/sanity/src/router/RouterProvider.tsx index a7da157755d..627ca5c4220 100644 --- a/packages/sanity/src/router/RouterProvider.tsx +++ b/packages/sanity/src/router/RouterProvider.tsx @@ -166,12 +166,12 @@ export function RouterProvider(props: RouterProviderProps): ReactElement { // separately to the entire router context object, which changes more frequently than the relevant // sticky parameters. // - // TODO: Add omitted bundles. const perspectiveState = useMemo(() => { return { perspective: stickyParamsByName.perspective, + excludedPerspectives: stickyParamsByName.excludedPerspectives, } - }, [stickyParamsByName.perspective]) + }, [stickyParamsByName.excludedPerspectives, stickyParamsByName.perspective]) const router: RouterContextValue = useMemo( () => ({ diff --git a/packages/sanity/src/router/stickyParams.ts b/packages/sanity/src/router/stickyParams.ts index 7236e2fd786..e823190279a 100644 --- a/packages/sanity/src/router/stickyParams.ts +++ b/packages/sanity/src/router/stickyParams.ts @@ -1 +1 @@ -export const STICKY_PARAMS: string[] = ['perspective'] +export const STICKY_PARAMS: string[] = ['perspective', 'excludedPerspectives'] diff --git a/packages/sanity/src/router/types.ts b/packages/sanity/src/router/types.ts index fc57eab441f..0e738d56ebe 100644 --- a/packages/sanity/src/router/types.ts +++ b/packages/sanity/src/router/types.ts @@ -296,5 +296,6 @@ export interface RouterContextValue { */ perspectiveState: { perspective: string | undefined + excludedPerspectives: string | undefined } }