feat(web): present onboarding as a continuous page

This commit is contained in:
NI0317
2026-07-31 00:28:17 +08:00
parent 04f8b30db1
commit 75a0366a52
25 changed files with 475 additions and 228 deletions
@@ -209,3 +209,33 @@
clip: rect(0 0 0 0);
white-space: nowrap;
}
/* First-run stage: keep the product top bar visible, then let onboarding own
the complete workspace instead of presenting another settings modal. */
.onboardingOverlay {
position: fixed;
inset: 0;
z-index: 1100;
}
/* Mask */
.onboardingMask {
position: absolute;
left: 0px;
right: 0px;
top: 80px;
bottom: 0px;
background: rgba(0, 0, 0, 0.24);
/* Mask-blur */
backdrop-filter: blur(2px);
}
.onboardingStage {
position: absolute;
z-index: 1;
inset: 0;
display: flex;
justify-content: center;
overflow: hidden;
background: var(--dsw-alias-bg-layer-1);
}
@@ -10,6 +10,7 @@
* sessions-derived empty-Hero fact is active.
*/
import { useCallback, useEffect, useId, useRef, useState } from 'react'
import { createPortal } from 'react-dom'
import clsx from 'clsx'
import { IconCloseOutline16, IconDataOutline16, IconSettingsOutline16 } from '@deepseek-ai/dsh-client-ui-primitives'
import type { SettingsRootComponentProps, SettingsSectionRow } from './contract/slots.ts'
@@ -132,6 +133,14 @@ export function SettingsRoot(props: SettingsRootComponentProps) {
})
}, [])
useEffect(() => {
if (onboardingStep === undefined) return
const appRoot = document.getElementById('root')
if (appRoot === null) return
appRoot.inert = true
return () => { appRoot.inert = false }
}, [onboardingStep])
return (
<>
<button
@@ -152,11 +161,18 @@ export function SettingsRoot(props: SettingsRootComponentProps) {
onClose={close}
/>
)}
{onboardingStep !== undefined && renderSlot('settings.onboarding', {
stepId: onboardingStep.id,
complete: () => { completeOnboardingStep(onboardingStep.id) },
openSection,
}, { only: onboardingStep.id })}
{onboardingStep !== undefined && createPortal((
<div className={css.onboardingOverlay} role="presentation">
<div className={css.onboardingMask} aria-hidden="true" />
<div className={css.onboardingStage}>
{renderSlot('settings.onboarding', {
stepId: onboardingStep.id,
complete: () => { completeOnboardingStep(onboardingStep.id) },
openSection,
}, { only: onboardingStep.id })}
</div>
</div>
), document.body)}
</>
)
}