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