Booking details screen

ProReact Native · Expo

One appointment in full: status badge and reference, the day and time with how far away it is, the professional, the services and total, a map card with Directions and Call, Add to calendar, Reschedule, and a hold-to-cancel that names the free cancellation window.

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/booking-details-screen

Then run npx expo install --fix so react-native-reanimated, react-native-svg match your Expo SDK. Files land in components/goodui-native/booking-details-screen.

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

Usage

import { router, Stack, useLocalSearchParams } from "expo-router";
import { Linking } from "react-native";
import { BookingDetailsScreen } from "@/components/goodui-native/booking-details-screen/booking-details-screen";
import { api } from "@/lib/api";
import { addToCalendar } from "@/lib/calendar";
import { useAppointment } from "@/lib/booking"; // your data, an Appointment

export default function AppointmentRoute() {
  const { id } = useLocalSearchParams<{ id: string }>();
  const appointment = useAppointment(id);
  if (!appointment) return null;
  return (
    <>
      <Stack.Screen options={{ headerShown: false }} />
      <BookingDetailsScreen
        appointment={appointment}
        onBack={() => router.back()}
        onDirections={(a) => {
          const c = a.business.coordinates;
          Linking.openURL(c ? `https://maps.google.com/?daddr=${c.lat},${c.lng}` : `https://maps.google.com/?q=${encodeURIComponent(a.business.address)}`);
        }}
        onCall={(phone) => Linking.openURL(`tel:${phone.replace(/\s/g, "")}`)}
        onAddToCalendar={(a) => addToCalendar(a)}
        onReschedule={(a) => router.push({ pathname: "/book/time", params: { reschedule: a.id } })}
        onCancel={(a) => api.cancel(a.id)}
        onBookAgain={(a) => router.push({ pathname: "/book", params: { again: a.id } })}
      />
    </>
  );
}

When to use

Use it for
  • Opening a single appointment from a list or a notification.
  • Showing where to go, with directions and a call button, and what was booked.
  • Letting the client cancel within a policy without a confirmation modal.
Reach for something else
  • A list of appointments: use appointments-screen.
  • Reviewing a booking before it exists: use booking-confirm-screen.
  • Order tracking for deliveries: use order-tracking-screen.

Examples

Sample appointment, no props

<BookingDetailsScreen />

With a static map image

<BookingDetailsScreen appointment={appointment} mapImage={{ uri: staticMapUrl }} />

API reference

PropTypeDefault
appointment
The appointment to show.
Appointment—
mapImage
Image for the map card, for example a static map of the address. A drawn street map shows without it.
ImageSourcePropType—
onBack
Shows a back button in the bar.
() => void—
onDirections
"Directions" on the location card, for example to open the maps app at `business.coordinates`.
(appointment: Appointment) => void—
onCall
"Call" on the location card, with the business phone number. Shown when the business has one.
(phone: string) => void—
onAddToCalendar
"Add to calendar". Return a promise to show pending, then "Added".
(appointment: Appointment) => unknown—
onReschedule
"Reschedule" on an upcoming appointment.
(appointment: Appointment) => void—
onCancel
Hold to cancel on an upcoming appointment. Return a promise: the control spins until it settles, then the status turns to Cancelled. Hidden when omitted.
(appointment: Appointment) => unknown—
onBookAgain
"Book again" on a past or cancelled appointment.
(appointment: Appointment) => void—
title
Bar title. Default "Appointment". Same as `labels.title`.
string—
locale
BCP 47 locale for dates, times and prices. Defaults to the device locale.
string—
hour12
Force a 12 or 24 hour clock. Defaults to the locale's.
boolean—
now
The current time, for "in 2 days" and the free cancellation window. Default now.
Date—
labels
Overrides for the built-in strings; a GooduiLocaleProvider pack sets them app-wide.
Partial<BookingDetailsScreenLabels>—
style
Style for the outer container.
StyleProp<ViewStyle>—
testID
Identifier for end-to-end tests.
string—

Motion and haptics

  • Hold to cancel fills like ink while held and draws a check; then the status badge turns to Cancelled with a fade and the actions give way to Book again.
  • The large title collapses into the bar on scroll; Add to calendar gathers into a spinner and opens into Added.
  • Reduced motion: the components follow their own reduced-motion rules; the status and the actions crossfade.
  • Haptic: select at each quarter of the hold, warning at its end, confirm when cancelled or error when onCancel rejects (HoldToConfirm)
  • Haptic: tap on buttons, then success or error for promises (Button)
  • Static content in one ScrollView; the drawn map is a single SVG with theme colors, no map SDK.

Accessibility

  • The day is a header; the large title collapses into the bar with a labelled back button (LargeTitleScrollView).
  • The map card is one image named after the address; the avatar is hidden because the name is written next to it.
  • Services are list rows with name, length and price; the total is its own row.
  • Hold to cancel is the kit's HoldToConfirm: a button with the policy as its hint, announced when done.

Notes for AI

  • appointment is an Appointment from booking-types; status, start, items, staff and business drive everything on screen.
  • Every action appears only when its callback is passed. onCancel and onAddToCalendar may return promises; the screen marks the appointment cancelled locally when onCancel resolves.
  • Pass mapImage for a real static map; without it a themed street drawing with a pin shows.
  • The free cancellation window comes from business.cancellationHours; the hint switches to the late fee text inside it.
  • Hide the navigator header (headerShown: false).