about summary refs log tree commit diff
path: root/src/screens/Onboarding/Layout.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/screens/Onboarding/Layout.tsx')
-rw-r--r--src/screens/Onboarding/Layout.tsx231
1 files changed, 231 insertions, 0 deletions
diff --git a/src/screens/Onboarding/Layout.tsx b/src/screens/Onboarding/Layout.tsx
new file mode 100644
index 000000000..50487c189
--- /dev/null
+++ b/src/screens/Onboarding/Layout.tsx
@@ -0,0 +1,231 @@
+import React from 'react'
+import {View} from 'react-native'
+import {useSafeAreaInsets} from 'react-native-safe-area-context'
+import {useLingui} from '@lingui/react'
+import {msg} from '@lingui/macro'
+
+import {IS_DEV} from '#/env'
+import {isWeb} from '#/platform/detection'
+import {useOnboardingDispatch} from '#/state/shell'
+
+import {
+  useTheme,
+  atoms as a,
+  useBreakpoints,
+  web,
+  native,
+  flatten,
+  TextStyleProp,
+} from '#/alf'
+import {H2, P, leading} from '#/components/Typography'
+import {ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft} from '#/components/icons/Chevron'
+import {Button, ButtonIcon} from '#/components/Button'
+import {ScrollView} from '#/view/com/util/Views'
+import {createPortalGroup} from '#/components/Portal'
+
+import {Context} from '#/screens/Onboarding/state'
+
+const COL_WIDTH = 500
+
+export const OnboardingControls = createPortalGroup()
+
+export function Layout({children}: React.PropsWithChildren<{}>) {
+  const {_} = useLingui()
+  const t = useTheme()
+  const insets = useSafeAreaInsets()
+  const {gtMobile} = useBreakpoints()
+  const onboardDispatch = useOnboardingDispatch()
+  const {state, dispatch} = React.useContext(Context)
+  const scrollview = React.useRef<ScrollView>(null)
+  const prevActiveStep = React.useRef<string>(state.activeStep)
+
+  React.useEffect(() => {
+    if (state.activeStep !== prevActiveStep.current) {
+      prevActiveStep.current = state.activeStep
+      scrollview.current?.scrollTo({y: 0, animated: false})
+    }
+  }, [state])
+
+  const paddingTop = gtMobile ? a.py_5xl : a.py_lg
+  const dialogLabel = _(msg`Set up your account`)
+
+  return (
+    <View
+      aria-modal
+      role="dialog"
+      aria-role="dialog"
+      aria-label={dialogLabel}
+      accessibilityLabel={dialogLabel}
+      accessibilityHint={_(
+        msg`The following steps will help customize your Bluesky experience.`,
+      )}
+      style={[
+        // @ts-ignore web only -prf
+        isWeb ? a.fixed : a.absolute,
+        a.inset_0,
+        a.flex_1,
+        t.atoms.bg,
+      ]}>
+      {IS_DEV && (
+        <View style={[a.absolute, a.p_xl, a.z_10, {right: 0, top: insets.top}]}>
+          <Button
+            variant="ghost"
+            color="negative"
+            size="small"
+            onPress={() => onboardDispatch({type: 'skip'})}
+            // DEV ONLY
+            label="Clear onboarding state">
+            Clear
+          </Button>
+        </View>
+      )}
+
+      {!gtMobile && state.hasPrev && (
+        <View
+          style={[
+            web(a.fixed),
+            native(a.absolute),
+            a.flex_row,
+            a.w_full,
+            a.justify_center,
+            a.z_20,
+            a.px_xl,
+            {
+              top: paddingTop.paddingTop + insets.top - 1,
+            },
+          ]}>
+          <View style={[a.w_full, a.align_start, {maxWidth: COL_WIDTH}]}>
+            <Button
+              key={state.activeStep} // remove focus state on nav
+              variant="ghost"
+              color="secondary"
+              size="small"
+              shape="round"
+              label={_(msg`Go back to previous step`)}
+              style={[a.absolute]}
+              onPress={() => dispatch({type: 'prev'})}>
+              <ButtonIcon icon={ChevronLeft} />
+            </Button>
+          </View>
+        </View>
+      )}
+
+      <ScrollView
+        ref={scrollview}
+        style={[a.h_full, a.w_full, {paddingTop: insets.top}]}
+        contentContainerStyle={{borderWidth: 0}}
+        // @ts-ignore 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: COL_WIDTH}]}>
+            <View style={[a.w_full, a.align_center, paddingTop]}>
+              <View
+                style={[
+                  a.flex_row,
+                  a.gap_sm,
+                  a.w_full,
+                  {paddingTop: 17, 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 + 1 <= state.activeStepIndex
+                              ? t.palette.primary_500
+                              : t.palette.contrast_100,
+                        },
+                      ]}
+                    />
+                  ))}
+              </View>
+            </View>
+
+            <View
+              style={[a.w_full, a.mb_5xl, {paddingTop: gtMobile ? 20 : 40}]}>
+              {children}
+            </View>
+
+            <View style={{height: 200}} />
+          </View>
+        </View>
+      </ScrollView>
+
+      <View
+        style={[
+          // @ts-ignore web only -prf
+          isWeb ? a.fixed : a.absolute,
+          {bottom: 0, left: 0, right: 0},
+          t.atoms.bg,
+          t.atoms.border,
+          a.border_t,
+          a.align_center,
+          gtMobile ? a.px_5xl : a.px_xl,
+          isWeb
+            ? a.py_2xl
+            : {
+                paddingTop: a.pt_lg.paddingTop,
+                paddingBottom: insets.bottom,
+              },
+        ]}>
+        <View
+          style={[
+            a.w_full,
+            {maxWidth: COL_WIDTH},
+            gtMobile && [a.flex_row, a.justify_between],
+          ]}>
+          {gtMobile &&
+            (state.hasPrev ? (
+              <Button
+                key={state.activeStep} // remove focus state on nav
+                variant="solid"
+                color="secondary"
+                size="large"
+                shape="round"
+                label={_(msg`Go back to previous step`)}
+                onPress={() => dispatch({type: 'prev'})}>
+                <ButtonIcon icon={ChevronLeft} />
+              </Button>
+            ) : (
+              <View style={{height: 54}} />
+            ))}
+          <OnboardingControls.Outlet />
+        </View>
+      </View>
+    </View>
+  )
+}
+
+export function Title({
+  children,
+  style,
+}: React.PropsWithChildren<TextStyleProp>) {
+  return (
+    <H2
+      style={[
+        a.pb_sm,
+        {
+          lineHeight: leading(a.text_4xl, a.leading_tight),
+        },
+        flatten(style),
+      ]}>
+      {children}
+    </H2>
+  )
+}
+
+export function Description({
+  children,
+  style,
+}: React.PropsWithChildren<TextStyleProp>) {
+  const t = useTheme()
+  return <P style={[t.atoms.text_contrast_700, flatten(style)]}>{children}</P>
+}