Files
bsky-social-app/src/components/Layout
Samuel Newman ad7d49ef8f [Contacts] Contacts matching flow (#9486)
* add expo-contacts

* add expo-sms

* update copy

* add basic settings screen

* state machine, flow screen

* phone input screen

* otp screen

* tweak spacing

* resend code logic

* add layoutanimationconfig

* check availablility in settings screen

* get contacts

* matches screen

* search, temp setup for matches UI

* add a bunch of number parsing logic with libphonenumber

* cast to looser type

* rename sync to find

FCF (Find Contacts Flow)

* update geolocation hook

* nicer design for settings screen

* add completed state

* up border contrast

* update expo deps

* add pending spinner

* add country whitelist

* add empty state screen

* drop add more functionality

* upload -> import

* fix typo

* fix permission string

* copy updates

* rm envelope icon

* update sms copy

* add inviteinfo component

* woke is back

* [Contacts] NUXes (#9515)

* add a bunch of number parsing logic with libphonenumber

* add banner nux

* add announcement nux

* native only nux

* rm shitty animation

* move isNative check

* [Contacts] Onboarding step (#9489)

* add a bunch of number parsing logic with libphonenumber

* restructure onboarding to better support dynamic screens

* integrate existing flow into onboarding

* add intro step

* lift state up to allow going back freely

* gate onboarding by geo if unsupported country

* add done button to standalone flow

* [Contacts] Add `contact-match` notification type to feed (#9519)

* add a bunch of number parsing logic with libphonenumber

* add contact-joined notif type

* update string, api package

* Update NotificationFeedItem.tsx

* Update NotificationFeedItem.tsx

* Delete SyncContactsFlow.web.tsx

* fix follow back btn for this case

* [Contacts] API integration (#9487)

* api integration for flow

* copy tweak

* tweaks after running it

* wire up status page

* rename toast

* use api lib

* rm temp code

* maybe fix otp error

* clear code on error/resend

* add 1s delay to verify success

* update package versions

* Delete SyncContactsFlow.web.tsx

* try and fix yarn.lock lint

* even woker

* fix uppercase friends

* surfdude feedback

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>

* rm log

* allow resend on invalid code

* Update GetContacts.tsx

* overwrite country code if possible

* fix Apple's Best Feature

* devenv latest

* disable bounces

* interactive keyboard dismiss

* copy changes

* national format

* allow resending immediately

* move success state down a bit

* Update FindContactsSettings.tsx

* [Contacts] More onboarding changes (#9491)

* integrate existing flow into onboarding

* refreshed onboarding styles, rm stepper

* center content on web

* Add back dismiss button for internal onboarding

* Import sort

---------

Co-authored-by: Eric Bailey <git@esb.lol>

* add matches query to shadow

* add clockwise arrow

* update status design, fix dismiss, fix queries

* add metrics

* add more comments

* Update metrics.ts

* refetch both queries on PTR

* fix shadow state in matches page

* reduce empty space at bottom

* show contact info on matches

* filter out contacts without numbers at an earlier stage

* Error handling 

* get notifs working

* filter out businesses

* rm log

* remove TODO from learn more links

* try and exclude from web bundle

---------

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>
Co-authored-by: Eric Bailey <git@esb.lol>
2025-12-12 10:32:06 -08:00
..
2025-08-14 01:12:31 +03:00
2024-12-05 18:59:26 -06:00

Layout

This directory contains our core layout components. Use these when creating new screens, or when supplementing other components with functionality like centering.

Usage

If we aren't talking about the shell components, layouts on individual screens look like more or less like this:

<Outer>
  <Header>...</Header>
  <Content>...</Content>
</Outer>

I'll map these words to real components.

Layout.Screen

Provides the "Outer" functionality for a screen, like taking up the full height of the screen. All screens should be wrapped with this component, probably as the outermost component.

Note

On web, Layout.Screen also provides the side borders on our central content column. These borders are fixed position, 1px outside our center column width of 600px.

What this effectively means is that nothing inside the center content column needs (or should) define left/right borders. That is now handled in one place: within Layout.Screen.

Layout.Header.*

The Layout.Header component actually contains multiple sub-components. Use this to compose different versions of the header. The most basic version looks like this:

<Layout.Header.Outer>
  <Layout.Header.BackButton /> {/* or <Layout.Header.MenuButton /> */}

  <Layout.Header.Content>
    <Layout.Header.TitleText>Account</Layout.Header.TitleText>

    {/* Optional subtitle */}
    <Layout.Header.SubtitleText>Settings for @esb.lol</Layout.Header.SubtitleText>
  </Layout.Header.Content>

  <Layout.Header.Slot />
</Layout.Header.Outer>

Note the additional Slot component. This is here to keep the header balanced and provide correct spacing on all platforms. The Slot is 34px wide, which matches the BackButton and MenuButton.

If anyone has better ideas, I'm all ears, but this was simple and the small amount of boilerplate is only incurred when creating a new screen, which is infrequent.

It can also function as a "slot" for a button positioned on the right side. See the Hashtag screen for an example, abbreviated below:

<Layout.Header.Slot>
  <Button size='small' shape='round'>...</Button>
</Layout.Header.Slot>

If you need additional customization, simply use the components that are helpful and create new ones as needed. A good example is the SavedFeeds screen, which looks roughly like this:

<Layout.Header.Outer>
  <Layout.Header.BackButton />

  {/* Override to align content to the left, making room for the button */}
  <Layout.Header.Content align='left'>
    <Layout.Header.TitleText>Edit My Feeds</Layout.Header.TitleText>
  </Layout.Header.Content>

  {/* Custom button, wider than 34px */}
  <Button size='small'>...</Button>
</Layout.Header.Outer>

Tip

The Header should be outside the Content component in order to be fixed on scroll on native. Placing it inside will make it scroll with the rest of the page.

Layout.Content

This provides the "Content" functionality for a screen. This component is actually an Animated.ScrollView, and accepts props for that component. It provides a little default styling as well. On web, it also centers the content inside our center content column of 600px.

Note

What about flatlists or pagers? Those components are not colocated here (yet). But those components serve the same purpose of "Content".

Examples

The most basic layout available to us looks like this:

<Layout.Screen>
  <Layout.Header.Outer>
    <Layout.Header.BackButton /> {/* or <Layout.Header.MenuButton /> */}

    <Layout.Header.Content>
      <Layout.Header.TitleText>Account</Layout.Header.TitleText>

      {/* Optional subtitle */}
      <Layout.Header.SubtitleText>Settings for @esb.lol</Layout.Header.SubtitleText>
    </Layout.Header.Content>

    <Layout.Header.Slot />
  </Layout.Header.Outer>

  <Layout.Content>
    ...
  </Layout.Content>
</Layout.Screen>

For List views, you'd sub in List for Layout.Content and it will function the same. See Feeds screen for an example.

For Pager views, including PagerWithHeader, do the same. See Hashtag screen for an example.

Utilities

Layout.Center

This component behaves like our old CenteredView component.

Layout.SCROLLBAR_OFFSET and Layout.SCROLLBAR_OFFSET_POSITIVE

Provide a pre-configured CSS vars for use when aligning fixed position elements. More on this below.

Scrollbar gutter handling

Operating systems allow users to configure if their browser always shows scrollbars not. Some OSs also don't allow configuration.

The presence of scrollbars affects layout, particularly fixed position elements. Browsers support scrollbar-gutter, but each behaves differently. Our approach is to use the default scrollbar-gutter: auto. Basically, we start from a clean slate.

This handling becomes particularly thorny when we need to lock scroll, like when opening a dialog or dropdown. Radix uses the library react-remove-scroll internally, which in turn depends on react-remove-scroll-bar. We've opted to rely on this transient dependency. This library adds some utility classes and CSS vars to the page when scroll is locked.

It is this CSS variable that we use in SCROLLBAR_OFFSET values. This ensures that elements do not shift relative to the screen when opening a dropdown or dialog.

These styles are applied where needed and we should have very little need of adjusting them often.