Add a "back" button to the onboarding buttons, move the dismiss button to a little corner x button (#5296)

* Add previous button to OnboardingButtons, move dismiss to popover corner

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* Clean up diagnostics

I am thoroughly enjoying nvim now

* Amend "click through" test to also click back

* fmt

* Set this test back to fixme, that work should be its own PR

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Frank Noirot
2025-02-07 11:30:36 -05:00
committed by GitHub
parent b82eec85fd
commit f6e975db84
14 changed files with 146 additions and 187 deletions

View File

@ -27,7 +27,7 @@ test.describe('Onboarding tests', () => {
}, },
cleanProjectDir: true, cleanProjectDir: true,
}, },
async ({ context, page, homePage }) => { async ({ page, homePage }) => {
const u = await getUtils(page) const u = await getUtils(page)
await page.setBodyDimensions({ width: 1200, height: 500 }) await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
@ -68,7 +68,7 @@ test.describe('Onboarding tests', () => {
}, },
cleanProjectDir: true, cleanProjectDir: true,
}, },
async ({ page, homePage }, testInfo) => { async ({ page }) => {
const u = await getUtils(page) const u = await getUtils(page)
const viewportSize = { width: 1200, height: 500 } const viewportSize = { width: 1200, height: 500 }
@ -154,7 +154,7 @@ test.describe('Onboarding tests', () => {
) )
test( test(
'Click through each onboarding step', 'Click through each onboarding step and back',
{ {
appSettings: { appSettings: {
app: { app: {
@ -187,15 +187,21 @@ test.describe('Onboarding tests', () => {
).toBeVisible() ).toBeVisible()
const nextButton = page.getByTestId('onboarding-next') const nextButton = page.getByTestId('onboarding-next')
const prevButton = page.getByTestId('onboarding-prev')
while ((await nextButton.innerText()) !== 'Finish') { while ((await nextButton.innerText()) !== 'Finish') {
await nextButton.hover() await nextButton.hover()
await nextButton.click() await nextButton.click()
} }
// Finish the onboarding while ((await prevButton.innerText()) !== 'Dismiss') {
await nextButton.hover() await prevButton.hover()
await nextButton.click() await prevButton.click()
}
// Dismiss the onboarding
await prevButton.hover()
await prevButton.click()
// Test that the onboarding pane is gone // Test that the onboarding pane is gone
await expect(page.getByTestId('onboarding-content')).not.toBeVisible() await expect(page.getByTestId('onboarding-content')).not.toBeVisible()
@ -269,7 +275,7 @@ test.describe('Onboarding tests', () => {
cleanProjectDir: true, cleanProjectDir: true,
}, },
async ({ context, page, homePage }) => { async ({ page, homePage }) => {
const u = await getUtils(page) const u = await getUtils(page)
const badCode = `// This is bad code we shouldn't see` const badCode = `// This is bad code we shouldn't see`
@ -336,10 +342,10 @@ test.describe('Onboarding tests', () => {
await homePage.goToModelingScene() await homePage.goToModelingScene()
// Test that the text in this step is correct // Test that the text in this step is correct
const avatarLocator = await page const avatarLocator = page
.getByTestId('user-sidebar-toggle') .getByTestId('user-sidebar-toggle')
.locator('img') .locator('img')
const onboardingOverlayLocator = await page const onboardingOverlayLocator = page
.getByTestId('onboarding-content') .getByTestId('onboarding-content')
.locator('div') .locator('div')
.nth(1) .nth(1)
@ -447,7 +453,7 @@ test.fixme(
}, },
cleanProjectDir: true, cleanProjectDir: true,
}, },
async ({ context, page, homePage }, testInfo) => { async ({ context, page }) => {
await context.folderSetupFn(async (dir) => { await context.folderSetupFn(async (dir) => {
const routerTemplateDir = join(dir, 'router-template-slate') const routerTemplateDir = join(dir, 'router-template-slate')
await fsp.mkdir(routerTemplateDir, { recursive: true }) await fsp.mkdir(routerTemplateDir, { recursive: true })
@ -486,10 +492,6 @@ test.fixme(
}) })
await test.step('Navigate into project', async () => { await test.step('Navigate into project', async () => {
await page.setBodyDimensions({ width: 1200, height: 500 })
page.on('console', console.log)
await expect( await expect(
page.getByRole('heading', { name: 'Your Projects' }) page.getByRole('heading', { name: 'Your Projects' })
).toBeVisible() ).toBeVisible()

View File

@ -26,7 +26,7 @@ export default function Units() {
<div className="fixed inset-0 z-50 grid items-end justify-start px-4 pointer-events-none"> <div className="fixed inset-0 z-50 grid items-end justify-start px-4 pointer-events-none">
<div <div
className={ className={
'pointer-events-auto max-w-2xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' 'relative pointer-events-auto max-w-2xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded'
} }
> >
<SettingsSection <SettingsSection
@ -72,9 +72,7 @@ export default function Units() {
</SettingsSection> </SettingsSection>
<OnboardingButtons <OnboardingButtons
currentSlug={onboardingPaths.CAMERA} currentSlug={onboardingPaths.CAMERA}
dismiss={dismiss} dismissClassName="right-auto left-full"
next={next}
nextText="Next: Streaming"
/> />
</div> </div>
</div> </div>

View File

@ -1,19 +1,17 @@
import usePlatform from 'hooks/usePlatform' import usePlatform from 'hooks/usePlatform'
import { OnboardingButtons, kbdClasses, useDismiss, useNextClick } from '.' import { OnboardingButtons, kbdClasses } from '.'
import { onboardingPaths } from 'routes/Onboarding/paths' import { onboardingPaths } from 'routes/Onboarding/paths'
import { hotkeyDisplay } from 'lib/hotkeyWrapper' import { hotkeyDisplay } from 'lib/hotkeyWrapper'
import { COMMAND_PALETTE_HOTKEY } from 'components/CommandBar/CommandBar' import { COMMAND_PALETTE_HOTKEY } from 'components/CommandBar/CommandBar'
export default function CmdK() { export default function CmdK() {
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.USER_MENU)
const platformName = usePlatform() const platformName = usePlatform()
return ( return (
<div className="fixed inset-0 z-50 grid items-end justify-center pointer-events-none"> <div className="fixed inset-0 z-50 grid items-end justify-center pointer-events-none">
<div <div
className={ className={
'pointer-events-auto max-w-full xl:max-w-4xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' 'relative pointer-events-auto max-w-full xl:max-w-4xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded'
} }
> >
<h2 className="text-2xl font-bold">Command Bar</h2> <h2 className="text-2xl font-bold">Command Bar</h2>
@ -38,12 +36,7 @@ export default function CmdK() {
. You can control settings, authentication, and file management from . You can control settings, authentication, and file management from
the command bar, as well as a growing number of modeling commands. the command bar, as well as a growing number of modeling commands.
</p> </p>
<OnboardingButtons <OnboardingButtons currentSlug={onboardingPaths.COMMAND_K} />
currentSlug={onboardingPaths.COMMAND_K}
dismiss={dismiss}
next={next}
nextText="Next: User Menu"
/>
</div> </div>
</div> </div>
) )

View File

@ -1,22 +1,14 @@
import { import { kbdClasses, OnboardingButtons, useDemoCode } from '.'
kbdClasses,
OnboardingButtons,
useDemoCode,
useDismiss,
useNextClick,
} from '.'
import { onboardingPaths } from 'routes/Onboarding/paths' import { onboardingPaths } from 'routes/Onboarding/paths'
export default function OnboardingCodeEditor() { export default function OnboardingCodeEditor() {
useDemoCode() useDemoCode()
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.PARAMETRIC_MODELING)
return ( return (
<div className="fixed grid justify-end items-center inset-0 z-50 pointer-events-none"> <div className="fixed grid justify-end items-center inset-0 z-50 pointer-events-none">
<div <div
className={ className={
'pointer-events-auto z-10 max-w-xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg h-[75vh] flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' 'relative pointer-events-auto z-10 max-w-xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg h-[75vh] flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded'
} }
> >
<section className="flex-1 overflow-y-auto"> <section className="flex-1 overflow-y-auto">
@ -73,12 +65,7 @@ export default function OnboardingCodeEditor() {
pressing <kbd className={kbdClasses}>Shift + C</kbd>. pressing <kbd className={kbdClasses}>Shift + C</kbd>.
</p> </p>
</section> </section>
<OnboardingButtons <OnboardingButtons currentSlug={onboardingPaths.EDITOR} />
currentSlug={onboardingPaths.EDITOR}
dismiss={dismiss}
next={next}
nextText="Next: Parametric Modeling"
/>
</div> </div>
</div> </div>
) )

View File

@ -1,16 +1,13 @@
import { APP_NAME } from 'lib/constants' import { APP_NAME } from 'lib/constants'
import { OnboardingButtons, useDismiss, useNextClick } from '.' import { OnboardingButtons } from '.'
import { onboardingPaths } from 'routes/Onboarding/paths' import { onboardingPaths } from 'routes/Onboarding/paths'
export default function Export() { export default function Export() {
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.SKETCHING)
return ( return (
<div className="fixed grid justify-center items-end inset-0 z-50 pointer-events-none"> <div className="fixed grid justify-center items-end inset-0 z-50 pointer-events-none">
<div <div
className={ className={
'pointer-events-auto max-w-full xl:max-w-2xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' 'relative pointer-events-auto max-w-full xl:max-w-2xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded'
} }
> >
<section className="flex-1"> <section className="flex-1">
@ -52,12 +49,7 @@ export default function Export() {
! !
</p> </p>
</section> </section>
<OnboardingButtons <OnboardingButtons currentSlug={onboardingPaths.EXPORT} />
currentSlug={onboardingPaths.EXPORT}
next={next}
dismiss={dismiss}
nextText="Next: Sketching"
/>
</div> </div>
</div> </div>
) )

View File

@ -1,4 +1,4 @@
import { OnboardingButtons, useDemoCode, useDismiss } from '.' import { OnboardingButtons, useDemoCode } from '.'
import { useEffect } from 'react' import { useEffect } from 'react'
import { useModelingContext } from 'hooks/useModelingContext' import { useModelingContext } from 'hooks/useModelingContext'
import { APP_NAME } from 'lib/constants' import { APP_NAME } from 'lib/constants'
@ -7,7 +7,6 @@ import { sceneInfra } from 'lib/singletons'
export default function FutureWork() { export default function FutureWork() {
const { send } = useModelingContext() const { send } = useModelingContext()
const dismiss = useDismiss()
// Reset the code, the camera, and the modeling state // Reset the code, the camera, and the modeling state
useDemoCode() useDemoCode()
@ -19,7 +18,7 @@ export default function FutureWork() {
return ( return (
<div className="fixed grid justify-center items-center inset-0 bg-chalkboard-100/50 z-50"> <div className="fixed grid justify-center items-center inset-0 bg-chalkboard-100/50 z-50">
<div className="max-w-full xl:max-w-2xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded"> <div className="relative max-w-full xl:max-w-2xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded">
<h1 className="text-2xl font-bold">Future Work</h1> <h1 className="text-2xl font-bold">Future Work</h1>
<p className="my-4"> <p className="my-4">
We have curves, cuts, multi-profile sketch mode, and many more CAD We have curves, cuts, multi-profile sketch mode, and many more CAD
@ -59,9 +58,6 @@ export default function FutureWork() {
<OnboardingButtons <OnboardingButtons
currentSlug={onboardingPaths.FUTURE_WORK} currentSlug={onboardingPaths.FUTURE_WORK}
className="mt-6" className="mt-6"
dismiss={dismiss}
next={dismiss}
nextText="Finish"
/> />
</div> </div>
</div> </div>

View File

@ -1,23 +1,15 @@
import { import { OnboardingButtons, kbdClasses, useDemoCode } from '.'
OnboardingButtons,
kbdClasses,
useDemoCode,
useDismiss,
useNextClick,
} from '.'
import { onboardingPaths } from 'routes/Onboarding/paths' import { onboardingPaths } from 'routes/Onboarding/paths'
import { bracketWidthConstantLine } from 'lib/exampleKcl' import { bracketWidthConstantLine } from 'lib/exampleKcl'
export default function OnboardingInteractiveNumbers() { export default function OnboardingInteractiveNumbers() {
useDemoCode() useDemoCode()
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.COMMAND_K)
return ( return (
<div className="fixed grid justify-end items-center inset-0 z-50 pointer-events-none"> <div className="fixed grid justify-end items-center inset-0 z-50 pointer-events-none">
<div <div
className={ className={
'pointer-events-auto z-10 max-w-xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg h-[75vh] flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' 'relative pointer-events-auto z-10 max-w-xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg h-[75vh] flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded'
} }
> >
<section className="flex-1 overflow-y-auto mb-6"> <section className="flex-1 overflow-y-auto mb-6">
@ -88,12 +80,7 @@ export default function OnboardingInteractiveNumbers() {
your ideas for how to make it better. your ideas for how to make it better.
</p> </p>
</section> </section>
<OnboardingButtons <OnboardingButtons currentSlug={onboardingPaths.INTERACTIVE_NUMBERS} />
currentSlug={onboardingPaths.INTERACTIVE_NUMBERS}
dismiss={dismiss}
next={next}
nextText="Next: Command Bar"
/>
</div> </div>
</div> </div>
) )

View File

@ -1,4 +1,4 @@
import { OnboardingButtons, useDemoCode, useDismiss, useNextClick } from '.' import { OnboardingButtons, useDemoCode } from '.'
import { onboardingPaths } from 'routes/Onboarding/paths' import { onboardingPaths } from 'routes/Onboarding/paths'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext' import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { Themes, getSystemTheme } from 'lib/theme' import { Themes, getSystemTheme } from 'lib/theme'
@ -8,13 +8,12 @@ import { isDesktop } from 'lib/isDesktop'
import { useNavigate, useRouteLoaderData } from 'react-router-dom' import { useNavigate, useRouteLoaderData } from 'react-router-dom'
import { codeManager, kclManager } from 'lib/singletons' import { codeManager, kclManager } from 'lib/singletons'
import { APP_NAME } from 'lib/constants' import { APP_NAME } from 'lib/constants'
import { useState } from 'react' import { useEffect, useState } from 'react'
import { IndexLoaderData } from 'lib/types' import { IndexLoaderData } from 'lib/types'
import { PATHS } from 'lib/paths' import { PATHS } from 'lib/paths'
import { useFileContext } from 'hooks/useFileContext' import { useFileContext } from 'hooks/useFileContext'
import { useLspContext } from 'components/LspProvider' import { useLspContext } from 'components/LspProvider'
import { reportRejection } from 'lib/trap' import { reportRejection } from 'lib/trap'
import { toSync } from 'lib/utils'
/** /**
* Show either a welcome screen or a warning screen * Show either a welcome screen or a warning screen
@ -39,7 +38,7 @@ interface OnboardingResetWarningProps {
function OnboardingResetWarning(props: OnboardingResetWarningProps) { function OnboardingResetWarning(props: OnboardingResetWarningProps) {
return ( return (
<div className="fixed inset-0 z-50 grid place-content-center bg-chalkboard-110/50"> <div className="fixed inset-0 z-50 grid place-content-center bg-chalkboard-110/50">
<div className="max-w-3xl p-8 rounded bg-chalkboard-10 dark:bg-chalkboard-90"> <div className="relative max-w-3xl p-8 rounded bg-chalkboard-10 dark:bg-chalkboard-90">
{!isDesktop() ? ( {!isDesktop() ? (
<OnboardingWarningWeb {...props} /> <OnboardingWarningWeb {...props} />
) : ( ) : (
@ -52,7 +51,6 @@ function OnboardingResetWarning(props: OnboardingResetWarningProps) {
function OnboardingWarningDesktop(props: OnboardingResetWarningProps) { function OnboardingWarningDesktop(props: OnboardingResetWarningProps) {
const navigate = useNavigate() const navigate = useNavigate()
const dismiss = useDismiss()
const loaderData = useRouteLoaderData(PATHS.FILE) as IndexLoaderData const loaderData = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
const { context: fileContext } = useFileContext() const { context: fileContext } = useFileContext()
const { onProjectClose, onProjectOpen } = useLspContext() const { onProjectClose, onProjectOpen } = useLspContext()
@ -81,17 +79,28 @@ function OnboardingWarningDesktop(props: OnboardingResetWarningProps) {
</section> </section>
<OnboardingButtons <OnboardingButtons
className="mt-6" className="mt-6"
dismiss={dismiss} onNextOverride={() => {
next={toSync(onAccept, reportRejection)} onAccept().catch(reportRejection)
nextText="Make a new project" }}
/> />
</> </>
) )
} }
function OnboardingWarningWeb(props: OnboardingResetWarningProps) { function OnboardingWarningWeb(props: OnboardingResetWarningProps) {
const dismiss = useDismiss() useEffect(() => {
async function beforeNavigate() {
// We do want to update both the state and editor here.
codeManager.updateCodeStateEditor(bracket)
await codeManager.writeToFile()
await kclManager.executeCode(true)
props.setShouldShowWarning(false)
}
return () => {
beforeNavigate().catch(reportRejection)
}
}, [])
return ( return (
<> <>
<h1 className="text-3xl font-bold text-warn-80 dark:text-warn-10"> <h1 className="text-3xl font-bold text-warn-80 dark:text-warn-10">
@ -101,19 +110,7 @@ function OnboardingWarningWeb(props: OnboardingResetWarningProps) {
We see you have some of your own code written in this project. Please We see you have some of your own code written in this project. Please
save it somewhere else before continuing the onboarding. save it somewhere else before continuing the onboarding.
</p> </p>
<OnboardingButtons <OnboardingButtons className="mt-6" />
className="mt-6"
dismiss={dismiss}
next={toSync(async () => {
// We do want to update both the state and editor here.
codeManager.updateCodeStateEditor(bracket)
await codeManager.writeToFile()
await kclManager.executeCode(true)
props.setShouldShowWarning(false)
}, reportRejection)}
nextText="Overwrite code and continue"
/>
</> </>
) )
} }
@ -136,12 +133,10 @@ function OnboardingIntroductionInner() {
(theme.current === Themes.System && getSystemTheme() === Themes.Light) (theme.current === Themes.System && getSystemTheme() === Themes.Light)
? '-dark' ? '-dark'
: '' : ''
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.CAMERA)
return ( return (
<div className="fixed inset-0 z-50 grid place-content-center bg-chalkboard-110/50"> <div className="fixed inset-0 z-50 grid place-content-center bg-chalkboard-110/50">
<div className="max-w-3xl p-8 rounded bg-chalkboard-10 dark:bg-chalkboard-90"> <div className="relative max-w-3xl p-8 rounded bg-chalkboard-10 dark:bg-chalkboard-90">
<h1 className="flex flex-wrap items-center gap-4 text-3xl font-bold"> <h1 className="flex flex-wrap items-center gap-4 text-3xl font-bold">
<img <img
src={`${isDesktop() ? '.' : ''}/zma-logomark${getLogoTheme()}.svg`} src={`${isDesktop() ? '.' : ''}/zma-logomark${getLogoTheme()}.svg`}
@ -192,9 +187,6 @@ function OnboardingIntroductionInner() {
<OnboardingButtons <OnboardingButtons
currentSlug={onboardingPaths.INDEX} currentSlug={onboardingPaths.INDEX}
className="mt-6" className="mt-6"
dismiss={dismiss}
next={next}
nextText="Mouse Controls"
/> />
</div> </div>
</div> </div>

View File

@ -1,4 +1,4 @@
import { OnboardingButtons, useDemoCode, useDismiss, useNextClick } from '.' import { OnboardingButtons, useDemoCode } from '.'
import { onboardingPaths } from 'routes/Onboarding/paths' import { onboardingPaths } from 'routes/Onboarding/paths'
import { Themes, getSystemTheme } from 'lib/theme' import { Themes, getSystemTheme } from 'lib/theme'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext' import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
@ -21,14 +21,12 @@ export default function OnboardingParametricModeling() {
(theme === Themes.System && getSystemTheme() === Themes.Light) (theme === Themes.System && getSystemTheme() === Themes.Light)
? '-dark' ? '-dark'
: '' : ''
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.INTERACTIVE_NUMBERS)
return ( return (
<div className="fixed grid justify-end items-center inset-0 z-50 pointer-events-none"> <div className="fixed grid justify-end items-center inset-0 z-50 pointer-events-none">
<div <div
className={ className={
'pointer-events-auto z-10 max-w-xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg h-[75vh] flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' 'relative pointer-events-auto z-10 max-w-xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg h-[75vh] flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded'
} }
> >
<section className="flex-1 overflow-y-auto mb-6"> <section className="flex-1 overflow-y-auto mb-6">
@ -77,12 +75,7 @@ export default function OnboardingParametricModeling() {
</figcaption> </figcaption>
</figure> </figure>
</section> </section>
<OnboardingButtons <OnboardingButtons currentSlug={onboardingPaths.PARAMETRIC_MODELING} />
currentSlug={onboardingPaths.PARAMETRIC_MODELING}
dismiss={dismiss}
next={next}
nextText="Next: Interactive Numbers"
/>
</div> </div>
</div> </div>
) )

View File

@ -1,17 +1,15 @@
import { OnboardingButtons, useDismiss, useNextClick } from '.' import { OnboardingButtons } from '.'
import { onboardingPaths } from 'routes/Onboarding/paths' import { onboardingPaths } from 'routes/Onboarding/paths'
import { isDesktop } from 'lib/isDesktop' import { isDesktop } from 'lib/isDesktop'
export default function ProjectMenu() { export default function ProjectMenu() {
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.EXPORT)
const onDesktop = isDesktop() const onDesktop = isDesktop()
return ( return (
<div className="fixed grid justify-center items-start inset-0 z-50 pointer-events-none"> <div className="fixed grid justify-center items-start inset-0 z-50 pointer-events-none">
<div <div
className={ className={
'pointer-events-auto max-w-xl flex flex-col border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' 'relative pointer-events-auto max-w-xl flex flex-col border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded'
} }
> >
<section className="flex-1"> <section className="flex-1">
@ -57,12 +55,7 @@ export default function ProjectMenu() {
</> </>
)} )}
</section> </section>
<OnboardingButtons <OnboardingButtons currentSlug={onboardingPaths.PROJECT_MENU} />
currentSlug={onboardingPaths.PROJECT_MENU}
next={next}
dismiss={dismiss}
nextText="Next: Export"
/>
</div> </div>
</div> </div>
) )

View File

@ -1,12 +1,9 @@
import { OnboardingButtons, useDismiss, useNextClick } from '.' import { OnboardingButtons } from '.'
import { onboardingPaths } from 'routes/Onboarding/paths' import { onboardingPaths } from 'routes/Onboarding/paths'
import { useEffect } from 'react' import { useEffect } from 'react'
import { codeManager, kclManager } from 'lib/singletons' import { codeManager, kclManager } from 'lib/singletons'
export default function Sketching() { export default function Sketching() {
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.FUTURE_WORK)
useEffect(() => { useEffect(() => {
async function clearEditor() { async function clearEditor() {
// We do want to update both the state and editor here. // We do want to update both the state and editor here.
@ -22,7 +19,7 @@ export default function Sketching() {
<div className="fixed grid justify-center items-end inset-0 z-50 pointer-events-none"> <div className="fixed grid justify-center items-end inset-0 z-50 pointer-events-none">
<div <div
className={ className={
'pointer-events-auto max-w-full xl:max-w-2xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' 'relative pointer-events-auto max-w-full xl:max-w-2xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded'
} }
> >
<h1 className="text-2xl font-bold">Sketching</h1> <h1 className="text-2xl font-bold">Sketching</h1>
@ -45,9 +42,6 @@ export default function Sketching() {
<OnboardingButtons <OnboardingButtons
currentSlug={onboardingPaths.SKETCHING} currentSlug={onboardingPaths.SKETCHING}
className="mt-6" className="mt-6"
next={next}
dismiss={dismiss}
nextText="Next: Future Work"
/> />
</div> </div>
</div> </div>

View File

@ -1,15 +1,12 @@
import { OnboardingButtons, useDismiss, useNextClick } from '.' import { OnboardingButtons } from '.'
import { onboardingPaths } from 'routes/Onboarding/paths' import { onboardingPaths } from 'routes/Onboarding/paths'
export default function Streaming() { export default function Streaming() {
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.EDITOR)
return ( return (
<div className="fixed grid justify-start items-center inset-0 z-50 pointer-events-none"> <div className="fixed grid justify-start items-center inset-0 z-50 pointer-events-none">
<div <div
className={ className={
'pointer-events-auto max-w-xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg h-[75vh] flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' 'relative pointer-events-auto max-w-xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg h-[75vh] flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded'
} }
> >
<section className="flex-1 overflow-y-auto"> <section className="flex-1 overflow-y-auto">
@ -44,9 +41,7 @@ export default function Streaming() {
</section> </section>
<OnboardingButtons <OnboardingButtons
currentSlug={onboardingPaths.STREAMING} currentSlug={onboardingPaths.STREAMING}
dismiss={dismiss} dismissClassName="right-auto left-full"
next={next}
nextText="Next: Code Editor"
/> />
</div> </div>
</div> </div>

View File

@ -1,12 +1,10 @@
import { OnboardingButtons, useDismiss, useNextClick } from '.' import { OnboardingButtons } from '.'
import { onboardingPaths } from 'routes/Onboarding/paths' import { onboardingPaths } from 'routes/Onboarding/paths'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { useUser } from 'machines/appMachine' import { useUser } from 'machines/appMachine'
export default function UserMenu() { export default function UserMenu() {
const user = useUser() const user = useUser()
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.PROJECT_MENU)
const [avatarErrored, setAvatarErrored] = useState(false) const [avatarErrored, setAvatarErrored] = useState(false)
const errorOrNoImage = !user?.image || avatarErrored const errorOrNoImage = !user?.image || avatarErrored
@ -32,7 +30,7 @@ export default function UserMenu() {
<div className="fixed grid justify-center items-start inset-0 z-50 pointer-events-none"> <div className="fixed grid justify-center items-start inset-0 z-50 pointer-events-none">
<div <div
className={ className={
'pointer-events-auto max-w-xl flex flex-col border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' 'relative pointer-events-auto max-w-xl flex flex-col border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded'
} }
> >
<section className="flex-1"> <section className="flex-1">
@ -48,12 +46,7 @@ export default function UserMenu() {
only apply to the current project. only apply to the current project.
</p> </p>
</section> </section>
<OnboardingButtons <OnboardingButtons currentSlug={onboardingPaths.USER_MENU} />
currentSlug={onboardingPaths.USER_MENU}
dismiss={dismiss}
next={next}
nextText="Next: Project Menu"
/>
</div> </div>
</div> </div>
) )

View File

@ -26,6 +26,9 @@ import { reportRejection } from 'lib/trap'
import { useNetworkContext } from 'hooks/useNetworkContext' import { useNetworkContext } from 'hooks/useNetworkContext'
import { NetworkHealthState } from 'hooks/useNetworkStatus' import { NetworkHealthState } from 'hooks/useNetworkStatus'
import { EngineConnectionStateType } from 'lang/std/engineConnection' import { EngineConnectionStateType } from 'lang/std/engineConnection'
import { CustomIcon } from 'components/CustomIcon'
import Tooltip from 'components/Tooltip'
import { commandBarActor } from 'machines/commandBarMachine'
export const kbdClasses = export const kbdClasses =
'py-0.5 px-1 text-sm rounded bg-chalkboard-10 dark:bg-chalkboard-100 border border-chalkboard-50 border-b-2' 'py-0.5 px-1 text-sm rounded bg-chalkboard-10 dark:bg-chalkboard-100 border border-chalkboard-50 border-b-2'
@ -163,58 +166,99 @@ export function useStepNumber(
: onboardingRoutes.findIndex( : onboardingRoutes.findIndex(
(r) => r.path === makeUrlPathRelative(slug) (r) => r.path === makeUrlPathRelative(slug)
) + 1 ) + 1
: undefined : 1
} }
export function OnboardingButtons({ export function OnboardingButtons({
next,
nextText,
dismiss,
currentSlug, currentSlug,
className, className,
dismissClassName,
onNextOverride,
...props ...props
}: { }: {
next: () => void
nextText?: string
dismiss: () => void
currentSlug?: (typeof onboardingPaths)[keyof typeof onboardingPaths] currentSlug?: (typeof onboardingPaths)[keyof typeof onboardingPaths]
className?: string className?: string
dismissClassName?: string
onNextOverride?: () => void
} & React.HTMLAttributes<HTMLDivElement>) { } & React.HTMLAttributes<HTMLDivElement>) {
const dismiss = useDismiss()
const stepNumber = useStepNumber(currentSlug) const stepNumber = useStepNumber(currentSlug)
const previousStep =
!stepNumber || stepNumber === 0 ? null : onboardingRoutes[stepNumber - 2]
const goToPrevious = useNextClick(
onboardingPaths.INDEX + (previousStep?.path ?? '')
)
const nextStep =
!stepNumber || stepNumber === onboardingRoutes.length
? null
: onboardingRoutes[stepNumber]
const goToNext = useNextClick(onboardingPaths.INDEX + (nextStep?.path ?? ''))
return ( return (
<div <>
className={'flex items-center justify-between ' + (className ?? '')} <button
{...props}
>
<ActionButton
Element="button"
onClick={dismiss} onClick={dismiss}
iconStart={{ className={
icon: 'close', 'group block !absolute left-auto right-full top-[-3px] m-2.5 p-0 border-none bg-transparent hover:bg-transparent ' +
className: 'text-chalkboard-10', dismissClassName
bgClassName: 'bg-destroy-80 group-hover:bg-destroy-80', }
}} data-testid="onboarding-dismiss"
className="hover:border-destroy-40 hover:bg-destroy-10/50 dark:hover:bg-destroy-80/50"
> >
Dismiss <CustomIcon
</ActionButton> name="close"
{stepNumber !== undefined && ( className="w-5 h-5 rounded-sm bg-destroy-10 text-destroy-80 dark:bg-destroy-80 dark:text-destroy-10 group-hover:brightness-110"
<p className="font-mono text-xs text-center m-0"> />
{stepNumber} / {onboardingRoutes.length} <Tooltip position="bottom" delay={500}>
</p> Dismiss <kbd className="hotkey ml-4 dark:!bg-chalkboard-80">esc</kbd>
)} </Tooltip>
<ActionButton </button>
autoFocus <div
Element="button" className={'flex items-center justify-between ' + (className ?? '')}
onClick={next} {...props}
iconStart={{ icon: 'arrowRight', bgClassName: 'dark:bg-chalkboard-80' }}
className="dark:hover:bg-chalkboard-80/50"
data-testid="onboarding-next"
> >
{nextText ?? 'Next'} <ActionButton
</ActionButton> Element="button"
</div> onClick={() =>
previousStep?.path || previousStep?.index
? goToPrevious()
: dismiss()
}
iconStart={{
icon: previousStep ? 'arrowLeft' : 'close',
className: 'text-chalkboard-10',
bgClassName: 'bg-destroy-80 group-hover:bg-destroy-80',
}}
className="hover:border-destroy-40 hover:bg-destroy-10/50 dark:hover:bg-destroy-80/50"
data-testid="onboarding-prev"
>
{previousStep ? `Back` : 'Dismiss'}
</ActionButton>
{stepNumber !== undefined && (
<p className="font-mono text-xs text-center m-0">
{stepNumber} / {onboardingRoutes.length}
</p>
)}
<ActionButton
autoFocus
Element="button"
onClick={() => {
if (nextStep?.path) {
onNextOverride ? onNextOverride() : goToNext()
} else {
dismiss()
}
}}
iconStart={{
icon: nextStep ? 'arrowRight' : 'checkmark',
bgClassName: 'dark:bg-chalkboard-80',
}}
className="dark:hover:bg-chalkboard-80/50"
data-testid="onboarding-next"
>
{nextStep ? `Next` : 'Finish'}
</ActionButton>
</div>
</>
) )
} }