Booking details screen
ProReact Native · ExpoOne 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.
Installation
$ npx shadcn@latest add @goodui-native-pro/booking-details-screenThen 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
- 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.
- 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
| Prop | Type | Default |
|---|---|---|
| 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).
Related
A client's bookings in Upcoming and Past tabs: date-tile cards with status, time, professional and place. Swipe a card for Reschedule or Cancel; cancelling asks in an alert that spins while the request runs, then the card slides out of Upcoming and the tab count drops.
Confirms an irreversible action by holding: ink fills from the leading edge, the label inverts where the ink passes, and haptics tick at every quarter.
An iOS large title that collapses into a compact bar as the content scrolls: the titles cross-fade, the bar fades in, the title stretches when pulled past the top and a half-finished collapse settles on either state.
Settings and navigation cells with a token-colored icon tile, title, subtitle and a trailing value, switch, badge, check or chevron, grouped in inset sections with headers and footers. On touch down the highlight floods in, the tile sinks and the chevron leans forward; on release the highlight lingers and fades.
A count, dot or status badge that springs in when it appears, pops when its value changes, caps at 99+ and shrinks away at zero; BadgeAnchor pins it to an icon or avatar.