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

Merged
merged 24 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
4f0fee8
feat(core): update UpsellDescriptionSerializer to support h3, images …
pedrobonamin Sep 12, 2024
62d06f2
feat(core): studio announcements card and dialog gro-2493, gro-2498
pedrobonamin Sep 12, 2024
450f606
feat(core): add studio announcements telemetry events
pedrobonamin Sep 12, 2024
bc867f4
feat(core): add studioAnnouncement provider with unseen modals check
pedrobonamin Sep 13, 2024
716a903
feat(core): add studioAnnouncement menu item to Resources menu items
pedrobonamin Sep 13, 2024
bf18f68
chore(core): improvements to studio announcements
pedrobonamin Sep 13, 2024
9ec11ae
chore(core): add tests for studio announcements
pedrobonamin Sep 16, 2024
9e666d5
chore(core): add tests to save seen announcements actions
pedrobonamin Sep 16, 2024
85080f4
feat(core): update telemetry events for studioAnnouncements
pedrobonamin Sep 16, 2024
96d9922
fix(core): update useSeenAnnouncements to handle state reset
pedrobonamin Sep 17, 2024
412c3b4
feat(core): add telemetry logs to announcement viewed and resources m…
pedrobonamin Sep 17, 2024
441c739
chore(core): remove translations resources in tests
pedrobonamin Sep 17, 2024
f57bc16
fix(core): update query to check expiry date
pedrobonamin Sep 17, 2024
28ca4fe
feat(core): add studioAnnouncements audienceRole check
pedrobonamin Sep 18, 2024
eebccbb
feat(core): replace client.fetch for internal api
pedrobonamin Sep 19, 2024
829bf53
fix(core): move cardSeen telemetry log to card
pedrobonamin Sep 23, 2024
85636cd
fix(core): add h2 to announcement dialog
pedrobonamin Sep 23, 2024
c375cc1
chore(core): add divider fade threshold details
pedrobonamin Sep 23, 2024
53c933c
chore(core): refactor announcements provider fetch, use useObservable
pedrobonamin Sep 23, 2024
7886599
chore(core): update useSeenAnnouncements, handle seen and unseen thro…
pedrobonamin Sep 23, 2024
7fedf77
feat(core): update product announcement audience, (greater|less)-than…
pedrobonamin Sep 23, 2024
6b02efd
feat(core): add support for card preHeader
pedrobonamin Sep 23, 2024
ef18a1e
fix(core): reduce studio announcements dialog height
pedrobonamin Sep 23, 2024
8d265b1
chore(core): update studio announcements telemetry, add internal_name
pedrobonamin Sep 23, 2024
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
62 changes: 62 additions & 0 deletions packages/sanity/src/core/studio/studioAnnouncements/Divider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a really nice touch 🙌.

margin: 0;
border: none;
`

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

/**
* This is the threshold for the divider to start fading
* uses a negative value to start fading before reaching the top
* of the parent.
* We want to fade out the divider so it doesn't overlap with the close icon when reaching the top.
* It's the sum of the title height (48px) and the divider padding top (12px)
*/
const DIVIDER_FADE_THRESHOLD = '-60px 0px 0px 0px'

/**
* 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: DIVIDER_FADE_THRESHOLD},
)

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,167 @@
/* eslint-disable camelcase */
import {RemoveIcon} from '@sanity/icons'
import {useTelemetry} from '@sanity/telemetry/react'
import {Box, Card, Stack, Text} from '@sanity/ui'
// eslint-disable-next-line camelcase
import {getTheme_v2} from '@sanity/ui/theme'
import {useEffect} from 'react'
import {useTranslation} from 'sanity'
import {css, keyframes, styled} from 'styled-components'

import {Button, Popover} from '../../../ui-components'
import {SANITY_VERSION} from '../../version'
import {ProductAnnouncementCardSeen} from './__telemetry__/studioAnnouncements.telemetry'

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;
}
#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 ButtonRoot = styled.div`
z-index: 1;
position: absolute;
top: 4px;
right: 6px;
`

interface StudioAnnouncementCardProps {
title: string
id: string
name: string
isOpen: boolean
preHeader: string
onCardClick: () => void
onCardDismiss: () => void
}

/**
* @internal
* @hidden
*/
export function StudioAnnouncementsCard({
title,
id,
isOpen,
name,
preHeader,
onCardClick,
onCardDismiss,
}: StudioAnnouncementCardProps) {
const {t} = useTranslation()
const telemetry = useTelemetry()

useEffect(() => {
if (isOpen) {
telemetry.log(ProductAnnouncementCardSeen, {
announcement_id: id,
announcement_title: title,
announcement_internal_name: name,
source: 'studio',
studio_version: SANITY_VERSION,
})
}
}, [telemetry, id, title, isOpen, name])

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">
<Card
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>
{preHeader}
</Text>
</Box>
<Text size={1} weight="medium">
{title}
</Text>
</Stack>
</Card>
<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