Staff picker screen

ProReact Native · Expo

Step two of a booking flow: an Any available card and a card per professional with photo, rating and next free time. One accent ring springs from card to card, its edges on different springs so it stretches toward the pick.

View as Markdown
Try it on your phone
Scan to open this demo in your phone's browser and use it with your fingers.

Installation

$ npx shadcn@latest add @goodui-native-pro/staff-picker-screen

Then run npx expo install --fix so react-native-reanimated match your Expo SDK. Files land in components/goodui-native/staff-picker-screen.

Pro screens install from the license-gated @goodui-native-pro registry. Setup: Pro access.

Usage

import { router, Stack, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import { StaffPickerScreen } from "@/components/goodui-native/staff-picker-screen/staff-picker-screen";
import type { BookingService, BookingStaff } from "@/components/goodui-native/service-picker-screen/booking-types";
import { useMenu } from "@/lib/booking"; // your data

export default function StaffRoute() {
  const params = useLocalSearchParams<{ services: string }>();
  const { services, staff } = useMenu() as { services: BookingService[]; staff: BookingStaff[] };
  const picked = services.filter((s) => params.services?.split(",").includes(s.id));
  const [staffId, setStaffId] = useState<string | null>(null);
  return (
    <>
      <Stack.Screen options={{ headerShown: false }} />
      <StaffPickerScreen
        staff={staff}
        services={picked}
        value={staffId}
        onValueChange={setStaffId}
        onBack={() => router.back()}
        onContinue={(id) => router.push({ pathname: "/book/time", params: { ...params, staff: id ?? "any" } })}
      />
    </>
  );
}

When to use

Use it for
  • Letting people choose who they book with: a stylist, barber, therapist, trainer, tutor or doctor.
  • Offering a no-preference option that books the first free person.
  • Showing who cannot take the picked services without hiding them.
Reach for something else
  • A long searchable directory of people: use a list with search-bar.
  • Picking several people at once: use checkbox rows.
  • Choosing what to book: use service-picker-screen.

Examples

Sample salon, no props

<StaffPickerScreen />

Named professional required

<StaffPickerScreen staff={doctors} services={[consultation]} allowAny={false} title="Choose your doctor" />

API reference

PropTypeDefault
staff
Everyone who can be booked. People who do not perform every picked service show dimmed at the end.
BookingStaff[]sampleStaff
services
The services picked on the previous step. They filter the staff and fill the summary line.
BookingService[][sampleServices[0]]
value
Picked staff id (controlled). `null` is "Any available". Pair it with `onValueChange`.
string | null—
defaultValue
Starting pick when uncontrolled. Default `null` ("Any available").
string | nullnull
onValueChange
Called with the picked staff id, or `null` for "Any available".
(staffId: string | null) => void—
onContinue
Continue button, with the picked id (`null` = anyone). Return a promise to show pending, success and error.
(staffId: string | null) => unknown—
onBack
Shows a back button in the top bar.
() => void—
allowAny
Hide the "Any available" card when every booking needs a named person. Default `true` (shown).
booleantrue
step
Step of the booking flow in the top bar. `null` hides it. Default step 2 of 4.
BookingStep | null{ current: 2, total: 4 }
title
Headline. Default "Choose a professional".
string"Choose a professional"
locale
BCP 47 locale for days and times. Defaults to the device locale.
string—
hour12
Force a 12 or 24 hour clock. Defaults to the locale's.
boolean—
style
Style for the outer container.
StyleProp<ViewStyle>—
testID
Identifier for end-to-end tests.
string—

Motion and haptics

  • One accent ring travels between cards. The edge in the direction of travel moves on the snappy spring and the other on the smooth spring, so the ring stretches toward the new card and settles.
  • Cards sink to 0.98 on touch down; the check in the picked card draws itself in.
  • Reduced motion: the ring jumps to the new card and fades in, cards do not scale, the check shows complete.
  • Haptic: select when the pick changes
  • Haptic: tap on Continue (Button)
  • The ring's position and height are shared values driven on the UI thread; card positions are measured once and only re-read when a card's size really changes (react-native-web reports layout on every render).

Accessibility

  • The cards form a radiogroup; each card is a radio with checked and disabled state (aria-checked and aria-disabled on the web).
  • On native a card reads as one sentence: name, role, rating with review count, next free time. The web names it from its visible text; avatars are hidden from screen readers.
  • A new pick is announced ("Maya Patel selected").
  • People who do not perform the picked services stay listed under a header, dimmed and disabled, with the reason in text.

Notes for AI

  • Pass the services picked on the previous step; staff are filtered by each service's staffIds and the rest are shown disabled.
  • value and onContinue use null for Any available. Map it to your API's 'any' option.
  • nextFree on each BookingStaff is a Date; the screen formats it as Today, Tomorrow or a short date plus time.
  • Avatars come in through BookingStaff.avatar (URL or image source); initials show without one.
  • Hide the navigator header (headerShown: false); the screen draws its own top bar.