Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): Studio announcements #7515

Open
wants to merge 15 commits into
base: next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {createContext} from 'sanity/_createContext'

import type {StudioAnnouncementsContextValue} from '../../core/studio/studioAnnouncements/types'

/**
* @internal
*/
export const StudioAnnouncementContext = createContext<StudioAnnouncementsContextValue | undefined>(
'sanity/_singletons/context/studioAnnouncements',
undefined,
)
1 change: 1 addition & 0 deletions packages/sanity/src/_singletons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export * from './context/SearchContext'
export * from './context/SortableItemIdContext'
export * from './context/SourceContext'
export * from './context/StructureToolContext'
export * from './context/StudioAnnouncementsContext'
export * from './context/TasksContext'
export * from './context/TasksEnabledContext'
export * from './context/TasksNavigationContext'
Expand Down
10 changes: 10 additions & 0 deletions packages/sanity/src/core/i18n/bundles/studio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ import {type LocaleResourceBundle} from '../types'
* @hidden
*/
export const studioLocaleStrings = defineLocalesResources('studio', {
/** The text used in the tooltip shown in the dialog close button */
'announcement.dialog.close': 'Close',
/** Aria label to be used in the dialog close button */
'announcement.dialog.close-label': 'Close dialog',
/**Text to be used in the tooltip in the button in the studio announcement card */
'announcement.floating-button.dismiss': 'Close',
/**Aria label to be used in the floating button in the studio announcement card, to dismiss the card */
'announcement.floating-button.dismiss-label': 'Dismiss announcements',
/**Aria label to be used in the floating button in the studio announcement card */
'announcement.floating-button.open-label': 'Open announcements',
/** Menu item for deleting the asset */
'asset-source.asset-list.menu.delete': 'Delete',
/** Menu item for showing where a particular asset is used */
Expand Down
5 changes: 4 additions & 1 deletion packages/sanity/src/core/studio/StudioProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
NotFoundScreen,
} from './screens'
import {type StudioProps} from './Studio'
import {StudioAnnouncementsProvider} from './studioAnnouncements/StudioAnnouncementsProvider'
import {StudioErrorBoundary} from './StudioErrorBoundary'
import {StudioTelemetryProvider} from './StudioTelemetryProvider'
import {StudioThemeProvider} from './StudioThemeProvider'
Expand Down Expand Up @@ -69,7 +70,9 @@ export function StudioProvider({
<LocaleProvider>
<PackageVersionStatusProvider>
<MaybeEnableErrorReporting errorReporter={errorReporter} />
<ResourceCacheProvider>{children}</ResourceCacheProvider>
<ResourceCacheProvider>
<StudioAnnouncementsProvider>{children}</StudioAnnouncementsProvider>
</ResourceCacheProvider>
</PackageVersionStatusProvider>
</LocaleProvider>
</StudioTelemetryProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {LoadingBlock} from '../../../../components/loadingBlock'
import {hasSanityPackageInImportMap} from '../../../../environment/hasSanityPackageInImportMap'
import {useTranslation} from '../../../../i18n'
import {SANITY_VERSION} from '../../../../version'
import {StudioAnnouncementsMenuItem} from '../../../studioAnnouncements/StudioAnnouncementsMenuItem'
import {type ResourcesResponse, type Section} from './helper-functions/types'

interface ResourcesMenuItemProps {
Expand Down Expand Up @@ -97,6 +98,8 @@ function SubSection({subSection}: {subSection: Section}) {
)
case 'internalAction': // TODO: Add support for internal actions (MVI-2)
if (!item.type) return null
if (item.type === 'studio-announcements-modal')
return <StudioAnnouncementsMenuItem text={item.title} />
return (
item.type === 'show-welcome-modal' && <MenuItem key={item._key} text={item.title} />
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ interface InternalAction extends Item {
type?: InternalActionType
}

type InternalActionType = 'show-welcome-modal'
type InternalActionType = 'show-welcome-modal' | 'studio-announcements-modal'

/**
* @hidden
Expand Down
1 change: 1 addition & 0 deletions packages/sanity/src/core/studio/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export * from './copyPaste'
export * from './renderStudio'
export * from './source'
export * from './Studio'
export * from './studioAnnouncements'
export * from './StudioLayout'
export * from './StudioProvider'
export * from './upsell'
Expand Down
53 changes: 53 additions & 0 deletions packages/sanity/src/core/studio/studioAnnouncements/Divider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {Box} from '@sanity/ui'
import {useEffect, useRef, useState} from 'react'
import {styled} from 'styled-components'

const Hr = styled.hr<{$show: boolean}>`
height: 1px;
background: var(--card-border-color);
width: 100%;
opacity: ${({$show}) => ($show ? 1 : 0)};
transition: opacity 0.3s ease;
margin: 0;
border: none;
`

interface DividerProps {
parentRef: React.RefObject<HTMLDivElement>
}

/**
* A divider that fades when reaching the top of the parent.
*/
export function Divider({parentRef}: DividerProps): JSX.Element {
const itemRef = useRef<HTMLHRElement | null>(null)
const [show, setShow] = useState(true)

useEffect(() => {
const item = itemRef.current
const parent = parentRef.current

if (!item || !parent) return
const observer = new IntersectionObserver(
([entry]) => {
setShow(entry.isIntersecting)
},
{root: parent, threshold: 0, rootMargin: '-60px 0px 0px 0px'},
)

observer.observe(item)

// eslint-disable-next-line consistent-return
return () => {
observer.disconnect()
}
}, [parentRef])

return (
<Box paddingBottom={4}>
<Box paddingY={3} paddingX={3}>
<Hr ref={itemRef} $show={show} />
</Box>
</Box>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import {RemoveIcon} from '@sanity/icons'
import {Box, Card, Stack, Text} from '@sanity/ui'
// eslint-disable-next-line camelcase
import {getTheme_v2} from '@sanity/ui/theme'
import {useTranslation} from 'sanity'
import {css, keyframes, styled} from 'styled-components'

import {Button, Popover} from '../../../ui-components'
import {type StudioAnnouncementDocument} from './types'

const TYPE_DICTIONARY: {
[key in StudioAnnouncementDocument['announcementType']]: string
} = {
'whats-new': "What's new",
}

const keyframe = keyframes`
0% {
background-position: 100%;
}
100% {
background-position: -100%;
}
`

const Root = styled.div((props) => {
const theme = getTheme_v2(props.theme)
const cardHoverBg = theme.color.selectable.default.hovered.bg
const cardNormalBg = theme.color.selectable.default.enabled.bg

return css`
position: relative;
cursor: pointer;
// hide the close button
#close-floating-button {
opacity: 0;
transition: opacity 0.2s;
}

&:hover {
> [data-ui='whats-new-card'] {
--card-bg-color: ${cardHoverBg};
box-shadow: inset 0 0 2px 1px var(--card-skeleton-color-to);
background-image: linear-gradient(
to right,
var(--card-bg-color),
var(--card-bg-color),
${cardNormalBg},
var(--card-bg-color),
var(--card-bg-color),
var(--card-bg-color)
);
background-position: 100%;
background-size: 200% 100%;
background-attachment: fixed;
animation-name: ${keyframe};
animation-timing-function: ease-in;
animation-iteration-count: infinite;
animation-duration: 2000ms;

/* --card-bg-color: var(--card-badge-default-bg-color); */
}
#close-floating-button {
opacity: 1;
background: transparent;

&:hover {
transition: all 0.2s;
box-shadow: 0 0 0 1px ${theme.color.selectable.default.hovered.border};
}
}
}
`
})

const FloatingCard = styled(Card)`
max-width: 320px;
`
const ButtonRoot = styled.div`
z-index: 1;
position: absolute;
top: 4px;
right: 6px;
`

interface StudioAnnouncementCardProps {
title: string
isOpen: boolean
announcementType: StudioAnnouncementDocument['announcementType']
onCardClick: () => void
onCardDismiss: () => void
}

/**
* @internal
* @hidden
*/
export function StudioAnnouncementsCard({
title,
isOpen,
announcementType,
onCardClick,
onCardDismiss,
}: StudioAnnouncementCardProps) {
const {t} = useTranslation()
return (
<Popover
open={isOpen}
shadow={3}
portal
style={{
bottom: 12,
left: 12,
top: 'none',
}}
width={0}
placement="bottom-start"
content={
<Root data-ui="whats-new-root">
<FloatingCard
data-ui="whats-new-card"
padding={3}
radius={3}
onClick={onCardClick}
role="button"
aria-label={t('announcement.floating-button.open-label')}
>
<Stack space={3}>
<Box marginRight={6}>
<Text as={'h3'} size={1} muted>
{TYPE_DICTIONARY[announcementType]}
</Text>
</Box>
<Text size={1} weight="medium">
{title}
</Text>
</Stack>
</FloatingCard>
<ButtonRoot>
<Button
id="close-floating-button"
mode="bleed"
onClick={onCardDismiss}
icon={RemoveIcon}
tone="default"
aria-label={t('announcement.floating-button.dismiss-label')}
tooltipProps={{
content: t('announcement.floating-button.dismiss'),
}}
/>
</ButtonRoot>
</Root>
}
/>
)
}
Loading
Loading