Skip to content

Commit

Permalink
feat: supporting toggling perspective layer visibility
Browse files Browse the repository at this point in the history
  • Loading branch information
jordanl17 committed Oct 28, 2024
1 parent 81c5fc3 commit c50388e
Show file tree
Hide file tree
Showing 9 changed files with 138 additions and 58 deletions.
2 changes: 2 additions & 0 deletions packages/sanity/src/core/i18n/bundles/studio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' */
Expand Down
52 changes: 46 additions & 6 deletions packages/sanity/src/core/releases/hooks/usePerspective.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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.
*/
Expand All @@ -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) => {
Expand All @@ -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 =
Expand Down Expand Up @@ -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,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,6 @@ export function GlobalPerspectiveMenu(): JSX.Element {
<GlobalPerspectiveMenuItem
rangePosition={isRangeVisible ? getRangePosition(range, 0) : undefined}
release={{_id: 'published', metadata: {title: 'Published'}} as ReleaseDocument}
toggleable
/>
</StyledPublishedBox>
<>
Expand Down
122 changes: 75 additions & 47 deletions packages/sanity/src/core/releases/navbar/GlobalPerspectiveMenuItem.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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 = () => (
<Box padding={3}>
<Text size={1}>
<DotIcon
style={
{
opacity: 0,
} as CSSProperties
}
/>
</Text>
</Box>
)

type rangePosition = 'first' | 'within' | 'last' | undefined

Expand All @@ -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<HTMLDivElement>) => {
event.stopPropagation()
}, [])
const {t} = useTranslation()

const releasePerspectiveId =
release._id === 'published' ? 'published' : getBundleIdFromReleaseId(release._id)

const handleToggleReleaseVisibility = useCallback(
(event: MouseEvent<HTMLDivElement>) => {
event.stopPropagation()
toggleExcludedPerspective(releasePerspectiveId)
},
[toggleExcludedPerspective, releasePerspectiveId],
)

const handleOnReleaseClick = useCallback(
() =>
Expand All @@ -82,6 +117,9 @@ export const GlobalPerspectiveMenuItem = forwardRef<
[release._id, setPerspective, setPerspectiveFromRelease],
)

const isReleasePerspectiveExcluded = isPerspectiveExcluded(releasePerspectiveId)
const canReleaseBeExcluded = inRange || (release._id === 'published' && !!currentGlobalBundle)

return (
<GlobalPerspectiveMenuItemIndicator
$isPublished={release._id === 'published'}
Expand All @@ -100,16 +138,11 @@ export const GlobalPerspectiveMenuItem = forwardRef<
}}
>
<Text size={1}>
{/* {release.hidden ? (
<DotIcon
style={
{
'--card-icon-color': 'var(--card-border-color)',
} as CSSProperties
}
/>
) : ( */}
<ReleaseAvatar tone={getReleaseTone(release)} />
{isReleasePerspectiveExcluded ? (
<ExcludedLayerDot />
) : (
<ReleaseAvatar tone={getReleaseTone(release)} />
)}
{/* )} */}
</Text>
</Box>
Expand All @@ -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,
}}
>
<Text size={1} weight="medium">
{release.metadata.title}
Expand All @@ -135,15 +166,12 @@ export const GlobalPerspectiveMenuItem = forwardRef<
)}
</Stack>
<Box flex="none">
{inRange && (
// eslint-disable-next-line @sanity/i18n/no-attribute-string-literals
<Tooltip portal content="Hide release" placement="bottom">
{canReleaseBeExcluded && (
<Tooltip portal content={t('release.layer.hide')} placement="bottom">
<ToggleLayerButton
// $visible={!release.hidden}
$visible={!isReleasePerspectiveExcluded}
forwardedAs="div"
disabled={!active}
icon={EyeOpenIcon}
// icon={release.hidden ? EyeClosedIcon : EyeOpenIcon}
icon={isReleasePerspectiveExcluded ? EyeClosedIcon : EyeOpenIcon}
mode="bleed"
onClick={handleToggleReleaseVisibility}
padding={2}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,24 @@ export const GlobalPerspectiveMenuItemIndicator = styled.div<{
${$first &&
css`
> [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;
}
`}
${$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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ export function ReleaseTypeMenuSection({
key={release._id}
ref={getMenuItemRef(release._id)}
rangePosition={getRangePosition(range, releaseTypeOffset + index)}
toggleable={releaseTypeOffset < lastIndex}
/>
))}
</Flex>
Expand Down
4 changes: 2 additions & 2 deletions packages/sanity/src/router/RouterProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
() => ({
Expand Down
2 changes: 1 addition & 1 deletion packages/sanity/src/router/stickyParams.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const STICKY_PARAMS: string[] = ['perspective']
export const STICKY_PARAMS: string[] = ['perspective', 'excludedPerspectives']
1 change: 1 addition & 0 deletions packages/sanity/src/router/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,5 +296,6 @@ export interface RouterContextValue {
*/
perspectiveState: {
perspective: string | undefined
excludedPerspectives: string | undefined
}
}

0 comments on commit c50388e

Please sign in to comment.