refreshed onboarding styles, rm stepper

This commit is contained in:
Samuel Newman
2025-12-05 21:12:16 +02:00
committed by Eric Bailey
parent da55868ddc
commit f8e94563cd
12 changed files with 180 additions and 163 deletions
+1
View File
@@ -129,6 +129,7 @@ export function BackButton({onPress, style, ...props}: Partial<ButtonProps>) {
size="small"
variant="ghost"
color="secondary"
shape="round"
onPress={onPressBack}
hitSlop={HITSLOP_30}
style={[
+2 -2
View File
@@ -23,7 +23,7 @@ export function FindContactsFlow({
<PhoneInput
state={state}
dispatch={dispatch}
showSkipButton={context === 'Onboarding'}
context={context}
onSkip={onCancel}
/>
)}
@@ -31,7 +31,7 @@ export function FindContactsFlow({
<VerifyNumber
state={state}
dispatch={dispatch}
showSkipButton={context === 'Onboarding'}
context={context}
onSkip={onCancel}
/>
)}
@@ -21,6 +21,7 @@ import {
useGutters,
useTheme,
} from '#/alf'
import {OnboardingPosition} from '#/screens/Onboarding/Layout'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import * as TextField from '#/components/forms/TextField'
import {InternationalPhoneCodeSelect} from '#/components/InternationalPhoneCodeSelect'
@@ -40,12 +41,12 @@ import {type Action, type State, useOnPressBackButton} from '../state'
export function PhoneInput({
state,
dispatch,
showSkipButton,
context,
onSkip,
}: {
state: Extract<State, {step: '1: phone input'}>
dispatch: React.ActionDispatch<[Action]>
showSkipButton: boolean
context: 'Onboarding' | 'Standalone'
onSkip: () => void
}) {
const {_} = useLingui()
@@ -131,7 +132,7 @@ export function PhoneInput({
<Layout.Header.Outer noBottomBorder>
<Layout.Header.BackButton onPress={onPressBack} />
<Layout.Header.Content />
{showSkipButton ? (
{context === 'Onboarding' ? (
<Button
size="small"
color="secondary"
@@ -149,6 +150,7 @@ export function PhoneInput({
<Layout.Content
contentContainerStyle={[gutters, a.pt_sm, a.flex_1]}
keyboardShouldPersistTaps="handled">
{context === 'Onboarding' && <OnboardingPosition />}
<Text style={[a.font_bold, a.text_3xl]}>
<Trans>Verify phone number</Trans>
</Text>
@@ -8,6 +8,7 @@ import {clamp} from '#/lib/numbers'
import {cleanError, isNetworkError} from '#/lib/strings/errors'
import {logger} from '#/logger'
import {useAgent} from '#/state/session'
import {OnboardingPosition} from '#/screens/Onboarding/Layout'
import {atoms as a, useGutters, useTheme} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded as RetryIcon} from '#/components/icons/ArrowRotateCounterClockwise'
@@ -25,12 +26,12 @@ import {type Action, type State, useOnPressBackButton} from '../state'
export function VerifyNumber({
state,
dispatch,
showSkipButton,
context,
onSkip,
}: {
state: Extract<State, {step: '2: verify number'}>
dispatch: React.ActionDispatch<[Action]>
showSkipButton: boolean
context: 'Onboarding' | 'Standalone'
onSkip: () => void
}) {
const t = useTheme()
@@ -139,7 +140,7 @@ export function VerifyNumber({
<Layout.Header.Outer noBottomBorder>
<Layout.Header.BackButton onPress={onPressBack} />
<Layout.Header.Content />
{showSkipButton ? (
{context === 'Onboarding' ? (
<Button
size="small"
color="secondary"
@@ -157,6 +158,7 @@ export function VerifyNumber({
<Layout.Content
contentContainerStyle={[gutters, a.pt_sm, a.flex_1]}
keyboardShouldPersistTaps="always">
{context === 'Onboarding' && <OnboardingPosition />}
<Text style={[a.font_bold, a.text_3xl]}>
<Trans>Verify phone number</Trans>
</Text>
@@ -18,6 +18,7 @@ import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useAgent, useSession} from '#/state/session'
import {List, type ListMethods} from '#/view/com/util/List'
import {UserAvatar} from '#/view/com/util/UserAvatar'
import {OnboardingPosition} from '#/screens/Onboarding/Layout'
import {bulkWriteFollows} from '#/screens/Onboarding/util'
import {atoms as a, tokens, useGutters, useTheme} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
@@ -323,7 +324,13 @@ export function ViewMatches({
</Layout.Header.Outer>
)}
{!isTotallyEmpty && (
<View style={[gutter, a.mb_md, context === 'Onboarding' && a.mt_sm]}>
<View
style={[
gutter,
a.mb_md,
context === 'Onboarding' && [a.mt_sm, a.gap_sm],
]}>
{context === 'Onboarding' && <OnboardingPosition />}
<SearchInput
placeholder={_(msg`Search contacts`)}
value={search}
+79 -89
View File
@@ -1,8 +1,7 @@
import React, {useState} from 'react'
import {useEffect, useRef, useState} from 'react'
import {ScrollView, View} from 'react-native'
import {Dimensions} from 'react-native'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {msg} from '@lingui/macro'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {isAndroid, isWeb} from '#/platform/detection'
@@ -22,6 +21,7 @@ import {ArrowLeft_Stroke2_Corner0_Rounded as ArrowLeft} from '#/components/icons
import {HEADER_SLOT_SIZE} from '#/components/Layout'
import {createPortalGroup} from '#/components/Portal'
import {P, Text} from '#/components/Typography'
import {IS_INTERNAL} from '#/env'
const ONBOARDING_COL_WIDTH = 420
@@ -35,10 +35,10 @@ export function Layout({children}: React.PropsWithChildren<{}>) {
const {gtMobile} = useBreakpoints()
const onboardDispatch = useOnboardingDispatch()
const {state, dispatch} = useOnboardingInternalState()
const scrollview = React.useRef<ScrollView>(null)
const prevActiveStep = React.useRef<string>(state.activeStep)
const scrollview = useRef<ScrollView>(null)
const prevActiveStep = useRef<string>(state.activeStep)
React.useEffect(() => {
useEffect(() => {
if (state.activeStep !== prevActiveStep.current) {
prevActiveStep.current = state.activeStep
scrollview.current?.scrollTo({y: 0, animated: false})
@@ -48,6 +48,7 @@ export function Layout({children}: React.PropsWithChildren<{}>) {
const paddingTop = gtMobile ? a.py_5xl : a.py_lg
const dialogLabel = _(msg`Set up your account`)
const [headerHeight, setHeaderHeight] = useState(0)
const [footerHeight, setFooterHeight] = useState(0)
return (
@@ -59,33 +60,12 @@ export function Layout({children}: React.PropsWithChildren<{}>) {
accessibilityLabel={dialogLabel}
accessibilityHint={_(msg`Customizes your Bluesky experience`)}
style={[isWeb ? a.fixed : a.absolute, a.inset_0, a.flex_1, t.atoms.bg]}>
{__DEV__ && (
<Button
variant="ghost"
color="negative"
size="tiny"
onPress={() => onboardDispatch({type: 'skip'})}
// DEV ONLY
label="Clear onboarding state"
style={[
a.absolute,
a.z_10,
{
left: isWeb ? '50%' : Dimensions.get('window').width / 2 - 45,
top: insets.top + 2,
},
web({transform: [{translateX: '-50%'}]}),
]}>
<ButtonText>[DEV] Clear</ButtonText>
</Button>
)}
{!gtMobile && (
<View
pointerEvents="box-none"
style={[
web(a.fixed),
native(a.absolute),
a.top_0,
a.left_0,
a.right_0,
a.flex_row,
@@ -93,85 +73,71 @@ export function Layout({children}: React.PropsWithChildren<{}>) {
a.justify_center,
a.z_20,
a.px_xl,
{top: paddingTop.paddingTop + insets.top - 1},
]}>
{paddingTop: (web(paddingTop.paddingTop) ?? 0) + insets.top},
native([t.atoms.bg, a.pb_xs, {minHeight: 48}]),
web(a.pointer_events_box_none),
]}
onLayout={evt => setHeaderHeight(evt.nativeEvent.layout.height)}>
<View
pointerEvents="box-none"
style={[
a.w_full,
a.align_start,
a.align_center,
a.flex_row,
a.justify_between,
{maxWidth: ONBOARDING_COL_WIDTH},
web({maxWidth: ONBOARDING_COL_WIDTH}),
web(a.pointer_events_box_none),
]}>
{state.canGoBack ? (
<HeaderSlot>
{state.canGoBack && (
<Button
key={state.activeStep} // remove focus state on nav
color="secondary"
variant="ghost"
shape="round"
size="small"
label={_(msg`Go back to previous step`)}
onPress={() => dispatch({type: 'prev'})}>
<ButtonIcon icon={ArrowLeft} size="lg" />
</Button>
)}
</HeaderSlot>
{IS_INTERNAL && (
<Button
key={state.activeStep} // remove focus state on nav
color="secondary"
variant="ghost"
shape="square"
size="small"
label={_(msg`Go back to previous step`)}
onPress={() => dispatch({type: 'prev'})}
style={[a.bg_transparent]}>
<ButtonIcon icon={ArrowLeft} size="lg" />
color="negative"
size="tiny"
onPress={() => onboardDispatch({type: 'skip'})}
// DEV ONLY
label="Clear onboarding state">
<ButtonText>[DEV] Clear</ButtonText>
</Button>
) : (
<View />
)}
<OnboardingHeaderSlot.Outlet />
<HeaderSlot>
<OnboardingHeaderSlot.Outlet />
</HeaderSlot>
</View>
</View>
)}
<ScrollView
ref={scrollview}
style={[a.h_full, a.w_full, {paddingTop: insets.top}]}
contentContainerStyle={{borderWidth: 0, minHeight: '100%'}}
style={[a.h_full, a.w_full, {paddingTop: isWeb ? insets.top : 0}]}
contentContainerStyle={{
borderWidth: 0,
minHeight: '100%',
paddingTop: headerHeight,
paddingBottom: footerHeight,
}}
showsVerticalScrollIndicator={!isAndroid}
scrollIndicatorInsets={{bottom: footerHeight - insets.bottom}}
// @ts-expect-error web only --prf
dataSet={{'stable-gutters': 1}}>
<View
style={[a.flex_row, a.justify_center, gtMobile ? a.px_5xl : a.px_xl]}>
<View style={[a.flex_1, {maxWidth: ONBOARDING_COL_WIDTH}]}>
<View style={[a.w_full, a.align_center, paddingTop]}>
<View
style={[
a.flex_row,
a.gap_sm,
a.w_full,
a.align_center,
{height: HEADER_SLOT_SIZE, maxWidth: '60%'},
]}>
{Array(state.totalSteps)
.fill(0)
.map((__, i) => (
<View
key={i}
style={[
a.flex_1,
a.pt_xs,
a.rounded_full,
t.atoms.bg_contrast_50,
{
backgroundColor:
i <= state.activeStepIndex
? t.palette.primary_500
: t.palette.contrast_100,
},
]}
/>
))}
</View>
</View>
<View style={[a.w_full, a.h_full, a.mb_5xl, a.pt_md]}>
{children}
</View>
<View style={{height: 100 + footerHeight}} />
<View style={[a.flex_1, web({maxWidth: ONBOARDING_COL_WIDTH})]}>
<View style={[a.w_full, a.py_md]}>{children}</View>
</View>
</View>
</ScrollView>
@@ -221,22 +187,46 @@ export function Layout({children}: React.PropsWithChildren<{}>) {
)
}
export function TitleText({
function HeaderSlot({children}: {children?: React.ReactNode}) {
return (
<View style={[{minHeight: HEADER_SLOT_SIZE, minWidth: HEADER_SLOT_SIZE}]}>
{children}
</View>
)
}
export function OnboardingPosition() {
const {state} = useOnboardingInternalState()
const t = useTheme()
return (
<Text style={[a.text_sm, a.font_medium, t.atoms.text_contrast_medium]}>
<Trans>
Step {state.activeStepIndex + 1} of {state.totalSteps}
</Trans>
</Text>
)
}
export function OnboardingTitleText({
children,
style,
}: React.PropsWithChildren<TextStyleProp>) {
return (
<Text
style={[a.pb_sm, a.text_4xl, a.font_semi_bold, a.leading_tight, style]}>
<Text style={[a.text_3xl, a.font_bold, a.leading_snug, style]}>
{children}
</Text>
)
}
export function DescriptionText({
export function OnboardingDescriptionText({
children,
style,
}: React.PropsWithChildren<TextStyleProp>) {
const t = useTheme()
return <P style={[t.atoms.text_contrast_medium, style]}>{children}</P>
return (
<P style={[a.text_md, a.leading_snug, t.atoms.text_contrast_medium, style]}>
{children}
</P>
)
}
@@ -4,18 +4,21 @@ import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useQuery} from '@tanstack/react-query'
import {atoms as a, useTheme} from '#/alf'
import {atoms as a} from '#/alf'
import {Admonition} from '#/components/Admonition'
import {Button, ButtonText} from '#/components/Button'
import {ContactsHeroImage} from '#/components/contacts/components/HeroImage'
import {InlineLinkText} from '#/components/Link'
import {Text} from '#/components/Typography'
import {OnboardingControls} from '../Layout'
import {
OnboardingControls,
OnboardingDescriptionText,
OnboardingPosition,
OnboardingTitleText,
} from '../Layout'
import {useOnboardingInternalState} from '../state'
export function StepFindContactsIntro() {
const {_} = useLingui()
const t = useTheme()
const {dispatch} = useOnboardingInternalState()
const {data: isAvailable, isSuccess} = useQuery({
@@ -24,12 +27,13 @@ export function StepFindContactsIntro() {
})
return (
<View style={[a.w_full, a.gap_lg]}>
<View style={[a.w_full, a.gap_sm]}>
<OnboardingPosition />
<ContactsHeroImage />
<Text style={[a.text_3xl, a.leading_snug, a.font_bold]}>
<OnboardingTitleText style={[a.mt_sm]}>
<Trans>Bluesky is more fun with friends</Trans>
</Text>
<Text style={[a.text_md, a.leading_snug, t.atoms.text_contrast_medium]}>
</OnboardingTitleText>
<OnboardingDescriptionText>
<Trans>
Find your friends on Bluesky by verifying your phone number and
matching with your contacts. We protect your information and you
@@ -41,7 +45,7 @@ export function StepFindContactsIntro() {
TODO: Learn more
</InlineLinkText>
</Trans>
</Text>
</OnboardingDescriptionText>
{!isAvailable && isSuccess && (
<Admonition type="error">
<Trans>
@@ -57,7 +61,8 @@ export function StepFindContactsIntro() {
onPress={() => dispatch({type: 'next'})}
label={_(msg`Import contacts`)}
size="large"
color="primary">
color="primary"
disabled={!isAvailable}>
<ButtonText>
<Trans>Import contacts</Trans>
</ButtonText>
+11 -14
View File
@@ -8,18 +8,16 @@ import {logEvent} from '#/lib/statsig/statsig'
import {capitalize} from '#/lib/strings/capitalize'
import {logger} from '#/logger'
import {
DescriptionText,
OnboardingControls,
TitleText,
OnboardingDescriptionText,
OnboardingPosition,
OnboardingTitleText,
} from '#/screens/Onboarding/Layout'
import {useOnboardingInternalState} from '#/screens/Onboarding/state'
import {InterestButton} from '#/screens/Onboarding/StepInterests/InterestButton'
import {atoms as a} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import * as Toggle from '#/components/forms/Toggle'
import {IconCircle} from '#/components/IconCircle'
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron'
import {Hashtag_Stroke2_Corner0_Rounded as Hashtag} from '#/components/icons/Hashtag'
import {Loader} from '#/components/Loader'
export function StepInterests() {
@@ -53,17 +51,16 @@ export function StepInterests() {
}, [selectedInterests, setSaving, dispatch])
return (
<View style={[a.align_start]} testID="onboardingInterests">
<IconCircle icon={Hashtag} style={[a.mb_2xl]} />
<TitleText>
<View style={[a.align_start, a.gap_sm]} testID="onboardingInterests">
<OnboardingPosition />
<OnboardingTitleText>
<Trans>What are your interests?</Trans>
</TitleText>
<DescriptionText>
</OnboardingTitleText>
<OnboardingDescriptionText>
<Trans>We'll use this to help customize your experience.</Trans>
</DescriptionText>
</OnboardingDescriptionText>
<View style={[a.w_full, a.pt_2xl]}>
<View style={[a.w_full, a.pt_lg]}>
<Toggle.Group
values={selectedInterests}
onChange={setSelectedInterests}
@@ -93,7 +90,7 @@ export function StepInterests() {
<ButtonText>
<Trans>Continue</Trans>
</ButtonText>
<ButtonIcon icon={saving ? Loader : ChevronRight} />
{saving && <ButtonIcon icon={Loader} />}
</Button>
</OnboardingControls.Portal>
</View>
+19 -23
View File
@@ -19,9 +19,10 @@ import {isCancelledError} from '#/lib/strings/errors'
import {logger} from '#/logger'
import {isNative, isWeb} from '#/platform/detection'
import {
DescriptionText,
OnboardingControls,
TitleText,
OnboardingDescriptionText,
OnboardingPosition,
OnboardingTitleText,
} from '#/screens/Onboarding/Layout'
import {useOnboardingInternalState} from '#/screens/Onboarding/state'
import {AvatarCircle} from '#/screens/Onboarding/StepProfile/AvatarCircle'
@@ -32,13 +33,10 @@ import {
type PlaceholderCanvasRef,
} from '#/screens/Onboarding/StepProfile/PlaceholderCanvas'
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {Button, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
import {useSheetWrapper} from '#/components/Dialog/sheet-wrapper'
import {IconCircle} from '#/components/IconCircle'
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron'
import {CircleInfo_Stroke2_Corner0_Rounded} from '#/components/icons/CircleInfo'
import {StreamingLive_Stroke2_Corner0_Rounded as StreamingLive} from '#/components/icons/StreamingLive'
import {Text} from '#/components/Typography'
import {type AvatarColor, avatarColors, type Emoji, emojiItems} from './types'
@@ -237,19 +235,21 @@ export function StepProfile() {
return (
<AvatarContext.Provider value={value}>
<View style={[a.align_start, t.atoms.bg, a.justify_between]}>
<IconCircle icon={StreamingLive} style={[a.mb_2xl]} />
<TitleText>
<Trans>Give your profile a face</Trans>
</TitleText>
<DescriptionText>
<Trans>
Help people know you're not a bot by uploading a picture or creating
an avatar.
</Trans>
</DescriptionText>
<View style={[a.align_start]}>
<View style={[a.gap_sm]}>
<OnboardingPosition />
<OnboardingTitleText>
<Trans>Give your profile a face</Trans>
</OnboardingTitleText>
<OnboardingDescriptionText>
<Trans>
Help people know you're not a bot by uploading a picture or
creating an avatar.
</Trans>
</OnboardingDescriptionText>
</View>
<View
style={[a.w_full, a.align_center, {paddingTop: gtMobile ? 80 : 40}]}>
style={[a.w_full, a.align_center, {paddingTop: gtMobile ? 80 : 60}]}>
<AvatarCircle
openLibrary={openLibrary}
openCreator={creatorControl.open}
@@ -279,7 +279,6 @@ export function StepProfile() {
<View style={[a.gap_md, gtMobile && a.flex_row_reverse]}>
<Button
testID="onboardingContinue"
variant="solid"
color="primary"
size="large"
label={_(msg`Continue to next step`)}
@@ -287,12 +286,10 @@ export function StepProfile() {
<ButtonText>
<Trans>Continue</Trans>
</ButtonText>
<ButtonIcon icon={ChevronRight} position="right" />
</Button>
<Button
testID="onboardingAvatarCreator"
variant="ghost"
color="primary"
color="primary_subtle"
size="large"
label={_(msg`Open avatar creator`)}
onPress={onSecondaryPress}>
@@ -335,7 +332,6 @@ export function StepProfile() {
</View>
<View style={[a.pt_4xl]}>
<Button
variant="solid"
color="primary"
size="large"
label={_(msg`Done`)}
@@ -15,7 +15,11 @@ import {updateProfileShadow} from '#/state/cache/profile-shadow'
import {useLanguagePrefs} from '#/state/preferences'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useAgent, useSession} from '#/state/session'
import {OnboardingControls} from '#/screens/Onboarding/Layout'
import {
OnboardingControls,
OnboardingPosition,
OnboardingTitleText,
} from '#/screens/Onboarding/Layout'
import {useOnboardingInternalState} from '#/screens/Onboarding/state'
import {useSuggestedUsers} from '#/screens/Search/util/useSuggestedUsers'
import {atoms as a, tokens, useBreakpoints, useTheme} from '#/alf'
@@ -27,7 +31,6 @@ import {boostInterests, InterestTabs} from '#/components/InterestTabs'
import {Loader} from '#/components/Loader'
import * as ProfileCard from '#/components/ProfileCard'
import * as toast from '#/components/Toast'
import {Text} from '#/components/Typography'
import type * as bsky from '#/types/bsky'
import {bulkWriteFollows} from '../util'
@@ -146,17 +149,18 @@ export function StepSuggestedAccounts() {
)
return (
<View style={[a.align_start]} testID="onboardingInterests">
<Text style={[a.font_bold, a.text_3xl]}>
<View style={[a.align_start, a.gap_sm]} testID="onboardingInterests">
<OnboardingPosition />
<OnboardingTitleText>
<Trans comment="Accounts suggested to the user for them to follow">
Suggested for you
</Trans>
</Text>
</OnboardingTitleText>
<View
style={[
a.overflow_hidden,
a.mt_lg,
a.mt_sm,
isWeb ? a.max_w_full : {marginHorizontal: tokens.space.xl * -1},
a.flex_1,
a.justify_start,
@@ -4,14 +4,17 @@ import {useLingui} from '@lingui/react'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useOnboardingSuggestedStarterPacksQuery} from '#/state/queries/useOnboardingSuggestedStarterPacksQuery'
import {OnboardingControls} from '#/screens/Onboarding/Layout'
import {
OnboardingControls,
OnboardingPosition,
OnboardingTitleText,
} from '#/screens/Onboarding/Layout'
import {useOnboardingInternalState} from '#/screens/Onboarding/state'
import {atoms as a, useBreakpoints} from '#/alf'
import {Admonition} from '#/components/Admonition'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded as ArrowRotateCounterClockwiseIcon} from '#/components/icons/ArrowRotateCounterClockwise'
import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography'
import {StarterPackCard} from './StarterPackCard'
export function StepSuggestedStarterpacks() {
@@ -33,26 +36,26 @@ export function StepSuggestedStarterpacks() {
})
return (
<View style={[a.align_start]} testID="onboardingInterests">
<Text style={[a.font_bold, a.text_3xl]}>
<Trans comment="Accounts suggested to the user for them to follow">
Suggested for you
<View style={[a.align_start, a.gap_sm]} testID="onboardingInterests">
<OnboardingPosition />
<OnboardingTitleText>
<Trans comment="Starter packs suggested to the user for them to follow">
Find people to follow
</Trans>
</Text>
</OnboardingTitleText>
<View
style={[
a.overflow_hidden,
a.mt_lg,
a.flex_1,
a.justify_start,
a.w_full,
a.mt_sm,
]}>
{isLoading || !moderationOpts ? (
<View
style={[
a.flex_1,
a.mt_md,
a.align_center,
a.justify_center,
{minHeight: 400},
@@ -68,7 +71,7 @@ export function StepSuggestedStarterpacks() {
</Admonition>
</View>
) : (
<View style={[a.flex_1, a.mt_md]}>
<View style={[a.flex_1]}>
{suggestedStarterPacks?.starterPacks.map(starterPack => (
<View style={[a.pb_lg]} key={starterPack.uri}>
<StarterPackCard view={starterPack} />
+12 -2
View File
@@ -224,13 +224,23 @@ export function useOnboardingInternalState() {
return {
state: useMemo(() => {
const stepOrder = getStepOrder(state)
const stepOrder = getStepOrder(state).filter(
x => x !== 'find-contacts' && x !== 'finished',
) as string[]
const canGoBack = state.activeStep !== stepOrder[0]
return {
...state,
canGoBack,
/**
* Note: for *display* purposes only, do not lean on this
* for navigation purposes! we merge certain steps!
*/
activeStepIndex: stepOrder.indexOf(
state.activeStep === 'find-contacts'
? 'find-contacts-intro'
: state.activeStep,
),
totalSteps: stepOrder.length,
activeStepIndex: stepOrder.indexOf(state.activeStep),
}
}, [state]),
dispatch,