refactor(stage-ui): improve onboarding, scrollable, and animated (#2215)

This commit is contained in:
Neko
2026-08-04 17:51:15 +08:00
committed by GitHub
parent 47c23e6467
commit 7dc8e2c4a3
5 changed files with 47 additions and 53 deletions
@@ -53,10 +53,8 @@ async function handleConfigured() {
:class="bgClass"
>
<div class="min-h-8 w-full flex-shrink-0 select-none drag-region" :class="bgClass" />
<div class="onboarding-scroll min-h-0 w-full flex flex-1 flex-col overflow-hidden px-10">
<div class="onboarding-content min-h-0 flex flex-1 flex-col overflow-hidden">
<OnboardingScreen :extra-steps="extraSteps" @skipped="handleSkipped" @configured="handleConfigured" />
</div>
<div class="mx-8 mb-8 mt-2 min-h-0 flex flex-1 flex-col">
<OnboardingScreen :extra-steps="extraSteps" @skipped="handleSkipped" @configured="handleConfigured" />
</div>
</div>
</template>
@@ -69,15 +67,6 @@ async function handleConfigured() {
.onboarding-root::-webkit-scrollbar {
display: none;
}
.onboarding-content {
padding: 8px 0 20px 0;
}
.onboarding-scroll {
padding-top: 8px;
padding-bottom: 20px;
}
</style>
<route lang="yaml">
@@ -36,7 +36,7 @@ onMounted(() => screenSafeArea.update())
<VisuallyHidden>
<DialogTitle>Onboarding</DialogTitle>
</VisuallyHidden>
<div class="min-h-0 min-w-0 w-full flex flex-1 flex-col overflow-hidden">
<div class="min-h-0 min-w-0 w-full flex flex-1 flex-col">
<Onboarding :extra-steps="props.extraSteps" @configured="emit('configured')" @skipped="emit('skipped')" />
</div>
</DialogContent>
@@ -67,7 +67,7 @@ onMounted(() => screenSafeArea.update())
'[div&]:bg-neutral-400 [div&]:dark:bg-neutral-600',
]"
/>
<div class="min-h-0 min-w-0 w-full flex flex-1 flex-col overflow-hidden">
<div class="min-h-0 min-w-0 w-full flex flex-1 flex-col">
<Onboarding :extra-steps="props.extraSteps" @configured="emit('configured')" @skipped="emit('skipped')" />
</div>
</DrawerContent>
@@ -221,13 +221,13 @@ onMounted(() => {
</script>
<template>
<div class="onboarding-step-container" min-h-0 w-full flex flex-1 flex-col overflow-hidden>
<div class="onboarding-step-container" min-h-0 flex flex-1 flex-col>
<Transition :name="direction === 'next' ? 'slide-next' : 'slide-prev'" mode="out-in">
<component
:is="currentStep.component"
v-if="currentStep"
:key="currentStep.id"
class="min-h-0 min-w-0 flex flex-1 flex-col overflow-hidden"
class="flex flex-1 flex-col"
v-bind="currentStepProps"
:on-next="requestNextStep"
:on-previous="requestPreviousStep"
@@ -237,10 +237,6 @@ onMounted(() => {
</template>
<style scoped>
.onboarding-step-container {
overflow-x: hidden;
}
.slide-next-enter-active,
.slide-next-leave-active,
.slide-prev-enter-active,
@@ -30,7 +30,7 @@ const selectedProviderIdModel = computed({
</script>
<template>
<div h-full flex flex-col gap-4>
<div h-full min-h-0 flex flex-col gap-4>
<div sticky top-0 z-100 flex flex-shrink-0 items-center gap-2>
<button outline-none @click="props.onPrevious">
<div class="i-solar:alt-arrow-left-line-duotone h-5 w-5" />
@@ -40,7 +40,7 @@ const selectedProviderIdModel = computed({
</h2>
<div class="h-5 w-5" />
</div>
<div class="flex-1 overflow-y-auto">
<div class="min-h-0 flex-1 overflow-y-auto">
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2">
<RadioCardDetail
v-for="provider in props.popularProviders"
@@ -56,6 +56,7 @@ const selectedProviderIdModel = computed({
</div>
</div>
<Button
class="flex-shrink-0"
:label="t('settings.dialogs.onboarding.next')"
:disabled="!selectedProviderIdModel"
@click="props.onNext"
@@ -2,7 +2,7 @@
import type { OnboardingStepNextHandler } from './types'
import { all } from '@proj-airi/i18n'
import { Button } from '@proj-airi/ui'
import { AnimatedContent, Button } from '@proj-airi/ui'
import { storeToRefs } from 'pinia'
import {
DropdownMenuContent,
@@ -55,9 +55,11 @@ function handleLocalSetup() {
'h-8 w-8',
'flex items-center justify-center',
'rounded-lg',
'outline-none',
'text-neutral-500 transition-colors duration-200',
'hover:bg-neutral-100/80 hover:text-neutral-700',
'dark:text-neutral-400 dark:hover:bg-neutral-800/80 dark:hover:text-neutral-200',
'data-[state=open]:bg-neutral-100/80 dark:data-[state=open]:bg-neutral-800/80',
]"
:aria-label="t('settings.language.title')"
>
@@ -65,29 +67,32 @@ function handleLocalSetup() {
</DropdownMenuTrigger>
<DropdownMenuPortal>
<DropdownMenuContent
as-child
align="end"
side="bottom"
:side-offset="6"
:class="[
'z-10000 min-w-36 rounded-xl border p-1 shadow-lg outline-none backdrop-blur-md',
'border-neutral-200/80 bg-neutral-100/80 text-neutral-700',
'dark:border-neutral-700/60 dark:bg-neutral-800/80 dark:text-neutral-100',
'border-neutral-100/80 bg-neutral-100/80 text-neutral-700',
'dark:border-neutral-800/60 dark:bg-neutral-800/80 dark:text-neutral-100',
]"
>
<DropdownMenuItem
v-for="lang in languages"
:key="lang.value"
:class="[
'flex cursor-pointer select-none items-center rounded-lg px-3 py-2',
'text-sm leading-none outline-none',
'data-[highlighted]:bg-primary-50/80 dark:data-[highlighted]:bg-primary-900/40',
'transition-colors duration-150 ease-in-out',
lang.value === language ? 'text-primary-500 dark:text-primary-300' : '',
]"
@select="() => language = lang.value"
>
{{ lang.label }}
</DropdownMenuItem>
<AnimatedContent>
<DropdownMenuItem
v-for="lang in languages"
:key="lang.value"
:class="[
'flex cursor-pointer select-none items-center rounded-lg px-3 py-2',
'text-sm leading-none outline-none',
'data-[highlighted]:bg-primary-100/80 dark:data-[highlighted]:bg-primary-900/40',
'transition-colors duration-150 ease-in-out',
lang.value === language ? 'text-primary-500 dark:text-primary-300' : '',
]"
@select="() => language = lang.value"
>
{{ lang.label }}
</DropdownMenuItem>
</AnimatedContent>
</DropdownMenuContent>
</DropdownMenuPortal>
</DropdownMenuRoot>
@@ -100,7 +105,7 @@ function handleLocalSetup() {
:duration="500"
:class="['mb-1', 'flex', 'justify-center', 'md:mb-4', 'md:pt-8', 'lg:pt-16']"
>
<img :src="onboardingLogo" max-h="50" aspect-square h-auto w-auto object-cover>
<img :src="onboardingLogo" max-h="50" aspect-square h-auto w-auto select-none object-cover>
</div>
<h2
v-motion
@@ -122,25 +127,28 @@ function handleLocalSetup() {
{{ t('settings.dialogs.onboarding.description') }}
</p>
</div>
<div :class="['flex', 'flex-col', 'gap-3', 'md:flex-row']">
<div :class="['flex', 'flex-col', 'gap-3', 'md:flex-row', 'm-2']">
<Button
v-motion
:initial="{ opacity: 0 }"
:enter="{ opacity: 1 }"
:duration="500"
:delay="200"
v-motion="{
initial: { opacity: 0 },
enter: { opacity: 1 },
duration: 500,
delay: 200,
}"
color="primary"
variant="secondary"
:label="t('settings.dialogs.onboarding.loginAction')"
:class="['flex-1']"
@click="handleLogin"
/>
<Button
v-if="props.customProviderSetupEnabled"
v-motion
:initial="{ opacity: 0 }"
:enter="{ opacity: 1 }"
:duration="500"
:delay="250"
v-motion="{
initial: { opacity: 0 },
enter: { opacity: 1 },
duration: 500,
delay: 250,
}"
:label="t('settings.dialogs.onboarding.setupWithoutSigningIn')"
:class="['flex-1']"
@click="handleLocalSetup"