Svelte: more welcome banner behavior updates (#64311)

This makes some requested changes to the welcome banner. 

Notably:
- We no longer show the dialog unless the user switches into the svelte
version from react
- We don't show the dialog on every switch
- The dialog is accessible in the React via the "Learn more" button in
the menu
- Copy is updated to be less verbose
- The request for feedback is only shown once when switching out of the
svelte webapp
This commit is contained in:
Camden Cheek 2024-08-07 08:55:57 -06:00 committed by GitHub
parent 61b41814bd
commit 3ff0f07646
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 3765 additions and 83 deletions

View File

@ -98,7 +98,9 @@ export interface TemporarySettingsSchema {
'openCodeGraph.annotations.visible': boolean
'webNext.welcomeOverlay.dismissed': boolean
'webNext.welcomeOverlay.show': boolean
'webNext.departureMessage.dismissed': boolean
'webNext.departureMessage.show': boolean
}
/**
@ -165,7 +167,9 @@ const TEMPORARY_SETTINGS: Record<keyof TemporarySettings, null> = {
'cody.onboarding.completed': null,
'openCodeGraph.annotations.visible': null,
'webNext.welcomeOverlay.dismissed': null,
'webNext.welcomeOverlay.show': null,
'webNext.departureMessage.dismissed': null,
'webNext.departureMessage.show': null,
}
export const TEMPORARY_SETTINGS_KEYS = Object.keys(TEMPORARY_SETTINGS) as readonly (keyof TemporarySettings)[]

View File

@ -13,13 +13,7 @@
{/if}
</div>
<div class="section">
<p class="m0">
You're currently on the new, faster Code Search user experience. It's in beta, and is our effort to rebuild
the tool from the ground up for performance.
</p>
</div>
<div class="section">
<p>Got feedback for us on the beta? We'd love to hear from you.</p>
<p>Have feedback for us on the beta? Wed love to hear from you.</p>
<a
class={getButtonClassName({ variant: 'secondary' })}
href="https://community.sourcegraph.com/c/code-search/9"

View File

@ -125,21 +125,14 @@
<div class="web-next-notice">
<ProductStatusBadge status="beta" />
<a
class={getButtonClassName({ variant: 'secondary', size: 'sm' })}
href="https://community.sourcegraph.com/c/code-search/9"
target="_blank"
rel="noreferrer noopener"
>
Feedback
</a>
<Popover let:registerTrigger let:toggle placement="bottom-end">
<button
use:registerTrigger
class={getButtonClassName({ variant: 'secondary', size: 'sm' })}
on:click={() => toggle()}
>
<Icon icon={ILucideEllipsis} inline />
New UI Feedback
<Icon icon={ILucideChevronDown} inline />
</button>
<FeedbackDialog slot="content" {handleOptOut} />
</Popover>

View File

@ -83,17 +83,11 @@
$: handleOptOut = currentUserID
? async (): Promise<void> => {
// Show departure message after switching off
$temporarySettingsStorage.set('webNext.departureMessage.dismissed', false)
$temporarySettingsStorage.set('webNext.departureMessage.show', true)
await data.disableSvelteFeatureFlags(currentUserID)
window.location.reload()
}
: undefined
$: welcomeOverlayDismissed = $temporarySettingsStorage.get('webNext.welcomeOverlay.dismissed', false)
$: showWelcomeOverlay = !($welcomeOverlayDismissed ?? true)
function handleDismissWelcomeOverlay() {
$temporarySettingsStorage.set('webNext.welcomeOverlay.dismissed', true)
}
</script>
<svelte:head>
@ -112,7 +106,7 @@
<slot />
</main>
<WelcomeOverlay show={showWelcomeOverlay} handleDismiss={handleDismissWelcomeOverlay} />
<WelcomeOverlay />
<FuzzyFinderContainer />

View File

@ -2,6 +2,7 @@
import { allHotkey } from '$lib/fuzzyfinder/keys'
import Icon from '$lib/Icon.svelte'
import KeyboardShortcut from '$lib/KeyboardShortcut.svelte'
import { temporarySetting } from '$lib/temporarySettings'
import { isLightTheme } from '$lib/theme'
import Button from '$lib/wildcard/Button.svelte'
import ProductStatusBadge from '$lib/wildcard/ProductStatusBadge.svelte'
@ -9,11 +10,16 @@
import WelcomeOverlayScreenshotDark from './WelcomeOverlayScreenshotDark.svelte'
import WelcomeOverlayScreenshotLight from './WelcomeOverlayScreenshotLight.svelte'
export let show: boolean
export let handleDismiss: () => void
let dialog: HTMLDialogElement | undefined
let inner: HTMLDivElement | undefined
$: activated = temporarySetting('webNext.welcomeOverlay.show', false)
$: dismissed = temporarySetting('webNext.welcomeOverlay.dismissed', false)
$: show = !$activated.loading && $activated.data && !$dismissed.loading && !$dismissed.data
function handleDismiss() {
dismissed.setValue(true)
}
function handleClickOutside(event: MouseEvent) {
// Use an inner div because the whole backdrop registers as part of the dialog
if (inner && !inner.contains(event.target as Node)) {

View File

@ -1651,6 +1651,7 @@ ts_project(
"src/storm/pages/SearchPageWrapper/SearchPageWrapper.tsx",
"src/storm/pages/SearchPageWrapper/index.ts",
"src/storm/routes.tsx",
"src/sveltekit/LearnMoreOverlay.tsx",
"src/sveltekit/SvelteKitNavItem.tsx",
"src/sveltekit/WebNextAwareLink.tsx",
"src/sveltekit/routes.ts",

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.5 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

@ -0,0 +1,182 @@
@import 'wildcard/src/global-styles/breakpoints';
.dialog {
position: absolute;
margin: 0 auto;
top: min(10vh, 10rem);
width: min(80vw, 100rem);
padding: 2rem;
overflow: hidden;
border-radius: 0.75rem;
border: 1px solid var(--border-color);
background-color: var(--color-bg-1);
:global(.theme-light) & {
box-shadow: 0 186px 52px 0 rgba(0, 0, 0, 0), 0 119px 48px 0 rgba(0, 0, 0, 0.01),
0 67px 40px 0 rgba(0, 0, 0, 0.02), 0 30px 30px 0 rgba(0, 0, 0, 0.03), 0 7px 16px 0 rgba(0, 0, 0, 0.04);
}
:global(.theme-dark) & {
box-shadow: 0 186px 52px 0 rgba(0, 0, 0, 0.01), 0 119px 48px 0 rgba(0, 0, 0, 0.03),
0 67px 40px 0 rgba(0, 0, 0, 0.06), 0 30px 30px 0 rgba(0, 0, 0, 0.12), 0 7px 16px 0 rgba(0, 0, 0, 0.24);
}
&::backdrop {
opacity: 0.48;
:global(.theme-light) & {
background: var(--color-background, #f9fafb);
}
:global(.theme-dark) & {
background: var(--color-background, #0f111a);
}
}
/* stylelint-disable-next-line property-no-unknown */
container-type: inline-size;
@media (--xs-breakpoint-down) {
margin: 0;
border-radius: 0;
border: none;
position: fixed;
top: 0;
width: 100vw;
height: 100vh;
max-height: 100vh;
max-width: 100vw;
}
}
.inner {
> :global(img) {
position: absolute;
right: 0;
bottom: 0;
filter: drop-shadow(0 25px 50px rgba(15, 17, 26, 0.25));
/* stylelint-disable-next-line scss/at-rule-no-unknown */
@container (width < 975px) {
display: none;
}
}
> :global(button) {
position: absolute;
top: 1rem;
right: 1rem;
}
}
.content {
// TODO: import this from shadcn color library (once it exists)
:global(.theme-light) & {
--color-text-subtle: var(--text-body);
}
:global(.theme-dark) & {
--color-text-subtle: #a6b6d9;
}
width: calc(100% - 24rem);
/* stylelint-disable-next-line scss/at-rule-no-unknown */
@container (width < 975px) {
width: 100%;
}
display: flex;
gap: 1rem;
flex-direction: column;
.logo {
display: flex;
gap: 1rem;
align-items: center;
img {
width: 2rem;
height: 2rem;
}
}
.message {
h1 {
text-wrap: balance;
span {
background: linear-gradient(90deg, #00cbec 0%, #a112ff 48.53%, #ff5543 97.06%);
color: transparent;
background-clip: text;
}
}
}
.subtitle {
margin: 0;
font-size: var(--font-size-large);
font-weight: 500;
color: var(--color-text-subtle);
text-wrap: wrap;
}
.features {
display: grid;
max-width: 700px;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1rem 0.75rem;
padding: 1rem 0;
> div {
display: grid;
grid-template-columns: min-content auto;
gap: 0.25rem 0.75rem;
svg {
grid-column: 1;
grid-row: 1;
width: 1.25rem;
height: 1.25rem;
}
h5 {
all: unset;
font-weight: 600;
grid-column: 2;
grid-row: 1;
}
p {
all: unset;
font-size: var(--font-size-small);
font-weight: 400;
color: var(--color-text-subtle);
grid-column: 2;
grid-row: 2;
text-wrap: wrap;
}
}
}
.cta {
display: flex;
gap: 1rem;
flex-direction: column;
h3 {
color: #ff5644;
}
div {
grid-column: 1 / -1;
display: flex;
gap: 1rem;
align-items: center;
}
button {
width: 15rem;
}
p {
grid-column: 1 / -1;
color: var(--text-muted);
font-size: var(--font-size-small);
font-weight: 400;
margin: 0;
text-wrap: wrap;
}
}
}

View File

@ -0,0 +1,107 @@
import { useEffect, useRef, forwardRef, useImperativeHandle } from 'react'
import { mdiFilePlusOutline, mdiGraphOutline, mdiMagnifyScan, mdiClose } from '@mdi/js'
import { useIsLightTheme } from '@sourcegraph/shared/src/theme'
import { Button, H1, H3, H5, Text, Icon, ProductStatusBadge } from '@sourcegraph/wildcard'
import { BrandLogo } from '../components/branding/BrandLogo'
import styles from './LearnMoreOverlay.module.scss'
export const LearnMoreOverlay = forwardRef<{ show: () => void; hide: () => void }, { handleEnable: () => void }>(
({ handleEnable }, forwardedRef) => {
const innerRef = useRef<HTMLDivElement | null>(null)
const dialogRef = useRef<HTMLDialogElement | null>(null)
const isLightTheme = useIsLightTheme()
const show = (): void => dialogRef.current?.showModal()
const hide = (): void => dialogRef.current?.close()
useImperativeHandle(forwardedRef, () => ({ show, hide }))
const handleClickOutside = (event: MouseEvent): void => {
// Use an inner div because the whole backdrop registers as part of the dialog
if (innerRef.current && !innerRef.current.contains(event.target as Node)) {
hide()
}
}
useEffect(() => {
document.body.addEventListener('mousedown', handleClickOutside)
return () => {
document.body.removeEventListener('mousedown', handleClickOutside)
}
})
return (
<dialog ref={dialogRef} className={styles.dialog}>
<div className={styles.inner} ref={innerRef}>
<div className={styles.content}>
<div className={styles.logo}>
<BrandLogo variant="symbol" isLightTheme={false} disableSymbolSpin={true} />
<ProductStatusBadge status="beta" />
</div>
<div className={styles.message}>
<H1>
<span>Try a new, faster experience</span>
</H1>
<Text className={styles.subtitle}>
Get ready for a new Code Search experience: rewritten from the ground-up for performance
to empower your workflow.
</Text>
</div>
<div className={styles.features}>
<div>
<Icon svgPath={mdiFilePlusOutline} aria-hidden={true} />
<H5>New in-line diff view</H5>
<Text>Easily compare commits and see how a file changed over time, all in-line</Text>
</div>
<div>
<Icon svgPath={mdiGraphOutline} aria-hidden={true} />
<H5>Revamped code navigation</H5>
<Text>
Quickly find a list of references of a given symbol, or immediately jump to the
definition
</Text>
</div>
<div>
<Icon svgPath={mdiMagnifyScan} aria-hidden={true} />
{/* TODO: add keyboard shortcut */}
<H5>Reworked fuzzy finder</H5>
<Text>Find files and symbols quickly and easily with our whole new fuzzy finder.</Text>
</div>
</div>
<div className={styles.cta}>
<H3>Enable the new UI</H3>
<div>
<Button variant="primary" onClick={handleEnable}>
Enable
</Button>
<Button variant="secondary" onClick={hide}>
No thanks
</Button>
</div>
<Text>You can opt out at any time by using the toggle at the top of the screen. </Text>
<Text>
Whilst exploring the new experience, consider leaving us some feedback via the button at
the top. We'd love to hear from you!
</Text>
</div>
</div>
<img
src={`/.assets/img/welcome-overlay-screenshot-${isLightTheme ? 'light' : 'dark'}.svg`}
aria-hidden="true"
alt="Example screenshot of beta web app"
/>
<Button variant="icon" aria-label="Close welcome overlay" onClick={hide}>
<Icon svgPath={mdiClose} aria-hidden="true" />
</Button>
</div>
</dialog>
)
}
)

View File

@ -54,3 +54,16 @@
.help-icon {
color: var(--icon-color);
}
.colorful {
background: linear-gradient(90deg, #00cbec 0%, #a112ff 48.53%, #ff5543 97.06%);
color: transparent;
background-clip: text;
}
.enable-toggle {
display: flex;
align-items: center;
font-weight: normal;
gap: 0.5rem;
}

View File

@ -1,13 +1,14 @@
import { FC, useRef, useEffect, useCallback } from 'react'
import { useApolloClient } from '@apollo/client'
import { mdiHelpCircleOutline, mdiClose } from '@mdi/js'
import { mdiChevronDown, mdiClose } from '@mdi/js'
import { useLocation } from 'react-router-dom'
import { Toggle } from '@sourcegraph/branded/src/components/Toggle'
import { useTemporarySetting } from '@sourcegraph/shared/src/settings/temporary'
import { Text, H3, Popover, PopoverTrigger, PopoverContent, Icon, Button } from '@sourcegraph/wildcard'
import { LearnMoreOverlay } from './LearnMoreOverlay'
import { enableSvelteAndReload, canEnableSvelteKit } from './util'
import styles from './SvelteKitNavItem.module.scss'
@ -16,17 +17,17 @@ export const SvelteKitNavItem: FC<{ userID?: string }> = ({ userID }) => {
const location = useLocation()
const client = useApolloClient()
const [departureDismissed, setDepartureDismissed] = useTemporarySetting('webNext.departureMessage.dismissed', false)
const [welcomeDismissed, setWelcomeDismissed] = useTemporarySetting('webNext.welcomeOverlay.dismissed', false)
const [showDeparture, _setDepartureDismissed] = useTemporarySetting('webNext.departureMessage.show', false)
const [_showWelcomeMessage, setShowWelcomeMessage] = useTemporarySetting('webNext.welcomeOverlay.show', false)
const departureRef = useRef<HTMLDivElement | null>(null)
const handleClickOutside = useCallback(
(event: MouseEvent) => {
if (departureRef.current && !departureRef.current.contains(event.target as Node)) {
setDepartureDismissed(true)
}
},
[departureRef, setDepartureDismissed]
[setDepartureDismissed]
)
useEffect(() => {
@ -36,71 +37,82 @@ export const SvelteKitNavItem: FC<{ userID?: string }> = ({ userID }) => {
}
}, [handleClickOutside])
const learnMoreRef = useRef<{ show: () => void; hide: () => void } | null>(null)
if (!userID || !canEnableSvelteKit(location.pathname)) {
return null
}
// only show if the welcome message has been dismissed so we know they have been introduced to the new webapp
const showDeparture = !departureDismissed && welcomeDismissed
const popoverProps = showDeparture ? { isOpen: true, onOpenChange: () => {} } : {}
const departureVisible = !departureDismissed && showDeparture
const popoverProps = departureVisible ? { isOpen: true, onOpenChange: () => {} } : {}
return (
<Popover {...popoverProps}>
<PopoverTrigger className={styles.badge}>
<div className={styles.container}>
<Icon className={styles.helpIcon} svgPath={mdiHelpCircleOutline} aria-hidden={true} />
<Text>New, faster UX</Text>
<Toggle
value={false}
onToggle={() => {
setWelcomeDismissed(false) // Show welcome after switching on
enableSvelteAndReload(client, userID)
}}
title="Enable new, faster UX"
className={styles.toggle}
/>
</div>
</PopoverTrigger>
<PopoverContent className={styles.popover} position="bottomEnd">
{showDeparture ? (
<div ref={departureRef}>
<>
<LearnMoreOverlay ref={learnMoreRef} handleEnable={() => enableSvelteAndReload(client, userID)} />
<Popover {...popoverProps}>
<PopoverTrigger className={styles.badge}>
<Button>
Try a new, faster UX
<Icon svgPath={mdiChevronDown} aria-hidden="true" />
</Button>
</PopoverTrigger>
<PopoverContent className={styles.popover} position="bottomEnd">
{departureVisible ? (
<div ref={departureRef}>
<div className={styles.section}>
<H3>
<span>Switched out of the new experience?</span>
<Button variant="icon" onClick={() => setDepartureDismissed(true)}>
<Icon svgPath={mdiClose} inline={true} aria-label="close" />
</Button>
</H3>
<Text>
Remember, you can always switch back using the toggle above. We're still working on
it, so check back soon.
</Text>
</div>
<div className={styles.section}>
<Text>Got feedback for us on the beta? Wed love to hear from you.</Text>
<Button
as="a"
variant="secondary"
href="https://community.sourcegraph.com/c/code-search/9"
target="_blank"
rel="noreferrer noopener"
>
Leave feedback
</Button>
<Text className={styles.small}>It only takes two minutes and helps a ton!</Text>
</div>
</div>
) : (
<div className={styles.section}>
<H3>
<span>Switched out of the new experience?</span>
<Button variant="icon" onClick={() => setDepartureDismissed(true)}>
<Icon svgPath={mdiClose} inline={true} aria-label="close" />
</Button>
<span className={styles.colorful}>Try a new, faster UX (Beta)</span>
<span className={styles.enableToggle}>
<Toggle
value={false}
onToggle={() => {
setShowWelcomeMessage(true)
enableSvelteAndReload(client, userID)
}}
title="Enable new, faster UX"
className={styles.toggle}
/>
Enable
</span>
</H3>
<Text>
Remember, you can always switch back using the toggle above. We're still working on it,
so check back soon.
We've rewritten Code Search from the ground-up for performance to empower your workflow.
</Text>
</div>
<div className={styles.section}>
<Text>Got feedback for us on the beta? Wed love to hear from you.</Text>
<Button
as="a"
variant="secondary"
href="https://community.sourcegraph.com/c/code-search/9"
target="_blank"
rel="noreferrer noopener"
>
Leave feedback
<Button variant="secondary" onClick={() => learnMoreRef.current?.show()}>
Learn more
</Button>
<Text className={styles.small}>It only takes two minutes and helps a ton!</Text>
</div>
</div>
) : (
<div className={styles.section}>
<H3>What's this "New, faster UX"?</H3>
<Text>
We've been busy at work on a new Code Search experience, built from the ground up for
performance, which is now available in beta.
</Text>
<Text>Simply activate the toggle to get it.</Text>
</div>
)}
</PopoverContent>
</Popover>
)}
</PopoverContent>
</Popover>
</>
)
}