-
Notifications
You must be signed in to change notification settings - Fork 428
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(core): Studio announcements (#7515)
* feat(core): update UpsellDescriptionSerializer to support h3, images & lists * feat(core): studio announcements card and dialog gro-2493, gro-2498 * feat(core): add studio announcements telemetry events * feat(core): add studioAnnouncement provider with unseen modals check * feat(core): add studioAnnouncement menu item to Resources menu items * chore(core): improvements to studio announcements * chore(core): add tests for studio announcements * chore(core): add tests to save seen announcements actions * feat(core): update telemetry events for studioAnnouncements * fix(core): update useSeenAnnouncements to handle state reset * feat(core): add telemetry logs to announcement viewed and resources menu clicked * chore(core): remove translations resources in tests * fix(core): update query to check expiry date * feat(core): add studioAnnouncements audienceRole check * feat(core): replace client.fetch for internal api * fix(core): move cardSeen telemetry log to card * fix(core): add h2 to announcement dialog * chore(core): add divider fade threshold details * chore(core): refactor announcements provider fetch, use useObservable * chore(core): update useSeenAnnouncements, handle seen and unseen through rxjs * feat(core): update product announcement audience, (greater|less)-than-or-equal-version * feat(core): add support for card preHeader * fix(core): reduce studio announcements dialog height * chore(core): update studio announcements telemetry, add internal_name
- Loading branch information
1 parent
a218c88
commit 3593bf5
Showing
25 changed files
with
2,371 additions
and
23 deletions.
There are no files selected for viewing
11 changes: 11 additions & 0 deletions
11
packages/sanity/src/_singletons/context/StudioAnnouncementsContext.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
62 changes: 62 additions & 0 deletions
62
packages/sanity/src/core/studio/studioAnnouncements/Divider.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
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> | ||
) | ||
} |
167 changes: 167 additions & 0 deletions
167
packages/sanity/src/core/studio/studioAnnouncements/StudioAnnouncementsCard.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
} | ||
/> | ||
) | ||
} |
Oops, something went wrong.