# WaitingScreen

> Patient waiting screen with ticket, position, estimated wait, now serving, room, a live connection indicator and a called state, driven by pure queue math.

Source: https://docs.nasaqui.com/components/waiting-screen

## Install

```bash
npx shadcn@latest add https://docs.nasaqui.com/r/waiting-screen.json
```

What a patient sees on their own phone while they wait. It takes the whole queue so position, estimate and now serving always agree with what reception sees. QueueLiveIndicator is the small live, reconnecting or offline badge shared with the clinic screens. The queue-math helpers (positionInQueue, estimateWaitMinutes, callNext and friends) are exported too.

## When to use

- A patient checked in and waits for their turn.
- A QR on the ticket opens this page.

## When not to use

- A shared board in the waiting room: use LobbyDisplay.
- The doctor's list: use ClinicQueue.

## Import

```tsx
import { QueueConnection, QueueLiveIndicator, WaitingScreen } from "@fadymondy/nasaq/web";
// inside this monorepo: "@nasaq/web"
```

## Quick start

```tsx
import { WaitingScreen } from "@fadymondy/nasaq/web";

<WaitingScreen entries={queue} entryId={myEntryId} rooms={2} connection={socketState} onLeave={() => api.leave(myEntryId)} />
```

## Anatomy

```
WaitingScreen                 data-slot="waiting-screen"
├─ header                     clinic name and QueueLiveIndicator
├─ ticket card                big ticket, position, estimated wait
├─ now serving
└─ leave button (onLeave)
QueueLiveIndicator            data-slot="queue-live-indicator"
```

## API

Every `div` prop is passed through unless noted.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `entries` | `QueueEntry[]` | `required` | The whole queue. |
| `entryId` | `string` | `required` | Which entry is this patient. |
| `averageMinutes, rooms` | `number` | 10, 1 | For the estimate. |
| `clinic` | `string` |  | Name in the header. |
| `connection` | `"live" \| "reconnecting" \| "offline"` | `"live"` | Live state of your data feed. |
| `updatedAt, now` | `number` |  | Epoch milliseconds. now is for stories. |
| `onLeave` | `() => Promise<void \| { error? }>` |  | Leave the line. Omit to hide the button. |
| `labels` | `Partial<WaitingScreenLabels>` |  | Override any string. |

## Examples

**Wire it to a socket**

```tsx
const [connection, setConnection] = useState<QueueConnection>("live");
socket.onclose = () => setConnection("reconnecting");
```

## Accessibility

- Position and wait changes are announced politely; the called state is announced assertively once.
- Live state is text plus icon, not only a dot colour.

## RTL & i18n

- Ticket numbers stay left to right; the layout mirrors.
- Strings live in a `STRINGS = { en, ar }` object and any of them can be replaced with `labels`. The locale comes from `NasaqProvider`.

## Styling & tokens

- Colours, radii and type come from `--nq-*` tokens; nothing is a raw colour. Spacing and alignment use logical classes.
- Target `[data-slot="waiting-screen"]` and the inner `data-slot` parts shown in the anatomy.

## Do / Don't

- Do keep the async callbacks honest: return `{ error }` so the component can show the message.
- Do pass `now` in tests and stories so the output stays the same.
- Don't fetch inside the component: it is presentational and takes data and callbacks.
- Don't rely on colour for state: every state also has a word.

## Related

- [`lobby-display`](https://docs.nasaqui.com/components/lobby-display)
- [`check-in-kiosk`](https://docs.nasaqui.com/components/check-in-kiosk)
- [`clinic-queue`](https://docs.nasaqui.com/components/clinic-queue)

## Lab

https://docs.nasaqui.com/?path=/docs/components-bookings-waiting-screen--docs

## Code

### React

```tsx
import { WaitingScreen } from "@fadymondy/nasaq/web";

<WaitingScreen entries={queue} entryId={myEntryId} rooms={2} connection={socketState} onLeave={() => api.leave(myEntryId)} />
```

### shadcn

```tsx
import { WaitingScreen } from "@/components/ui/waiting-screen";

<WaitingScreen entries={queue} entryId={myEntryId} rooms={2} connection={socketState} onLeave={() => api.leave(myEntryId)} />
```

### Vue

```vue
<script setup lang="ts">
import { NqWaitingScreen, type QueueEntry } from "@fadymondy/nasaq/vue";

const t = Date.now();
const base = { priority: "normal" as const, checkedInAt: t - 600_000 };
const queue: QueueEntry[] = [
  { ...base, id: "q1", ticket: "A-014", number: 14, status: "serving", room: "2", queuedAt: t - 900_000, calledAt: t - 300_000 },
  { ...base, id: "q2", ticket: "A-015", number: 15, status: "waiting", queuedAt: t - 500_000 },
  { ...base, id: "q3", ticket: "A-016", number: 16, status: "waiting", queuedAt: t - 400_000 },
  { ...base, id: "me", ticket: "A-017", number: 17, status: "waiting", queuedAt: t - 300_000 },
];
const socketState = "live" as const;
const leave = async () => {};
</script>

<template>
  <NqWaitingScreen :entries="queue" entry-id="me" :rooms="2" :connection="socketState" :on-leave="leave" />
</template>
```

### Blade

```blade
@php
    $t = now()->getTimestamp() * 1000;
    $base = ['priority' => 'normal', 'checkedInAt' => $t - 600000];
    $queue = [
        $base + ['id' => 'q1', 'ticket' => 'A-014', 'number' => 14, 'status' => 'serving', 'room' => '2', 'queuedAt' => $t - 900000, 'calledAt' => $t - 300000],
        $base + ['id' => 'q2', 'ticket' => 'A-015', 'number' => 15, 'status' => 'waiting', 'queuedAt' => $t - 500000],
        $base + ['id' => 'q3', 'ticket' => 'A-016', 'number' => 16, 'status' => 'waiting', 'queuedAt' => $t - 400000],
        $base + ['id' => 'me', 'ticket' => 'A-017', 'number' => 17, 'status' => 'waiting', 'queuedAt' => $t - 300000],
    ];
@endphp
<x-nq::waiting-screen :entries="$queue" entry-id="me" :rooms="2" connection="live" :updated-at="$t - 12000" leaveable />
```

### HTML + Alpine

```html
<div data-slot="waiting-screen" data-status="waiting" x-data="nqWaitingScreen" class="mx-auto flex w-full max-w-md flex-col gap-4">
        <div class="flex items-center justify-between gap-2">
                            <span></span>
                        <span data-slot="queue-live" data-connection="live" role="status" aria-label="Connection: Live"  x-data="nqQueueLive(JSON.parse('{\u0022updatedAt\u0022:1790672388000,\u0022frozen\u0022:false,\u0022serverNow\u0022:1790672400000,\u0022justNow\u0022:\u0022Updated just now\u0022,\u0022ago\u0022:\u0022Updated :n s ago\u0022}'))"     class="inline-flex items-center gap-1.5 text-caption text-muted-foreground">
            <span aria-hidden="true" class="relative flex size-2">
            <span class="absolute inline-flex size-full rounded-full opacity-60 motion-safe:animate-ping bg-nq-success-text"></span>
            <span class="relative inline-flex size-2 rounded-full bg-nq-success-text"></span>
        </span>
        <span class="font-medium text-foreground">Live</span>
            <span>· <span data-slot="queue-updated"  x-text="text" >Updated 12 s ago</span></span>
    </span>
        </div>
        <div data-slot="card" class="flex flex-col gap-4 rounded-card border border-border bg-card py-4 text-card-foreground"><div data-slot="card-header" class="grid auto-rows-min items-start gap-1 px-4 has-data-[slot=card-action]:grid-cols-[1fr_auto]"><h3 data-slot="card-title" class="text-label text-foreground">Your ticket</h3>
                                    <span data-slot="badge"     class="inline-flex h-5 shrink-0 items-center gap-1 whitespace-nowrap rounded-[4px] border px-1.5 text-caption font-medium [&_svg]:size-3 border-nq-warning/40 bg-nq-warning-soft text-nq-warning-text justify-self-end"><svg aria-hidden="true" class="size-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M5 22h14"/>
  <path d="M5 2h14"/>
  <path d="M17 22v-4.172a2 2 0 0 0-.586-1.414L12 12l-4.414 4.414A2 2 0 0 0 7 17.828V22"/>
  <path d="M7 2v4.172a2 2 0 0 0 .586 1.414L12 12l4.414-4.414A2 2 0 0 0 17 6.172V2"/>
</svg>                        Waiting</span></div>
            <div data-slot="card-content" class="px-4 flex flex-col gap-4"><bdi dir="ltr" data-slot="waiting-ticket" class="text-center font-mono text-[3.5rem] font-semibold leading-none tracking-wider tabular-nums">A-017</bdi>
                                    <div class="grid grid-cols-2 gap-3 border-t border-nq-line pt-4">
                        <div class="flex flex-col gap-0.5">
                            <span class="text-caption text-muted-foreground">Your place in line</span>
                            <span class="text-h2 tabular-nums"><bdi data-slot="num" data-numeric="" class="tabular-nums">3</bdi>
</span>
                            <span class="text-caption text-muted-foreground">2 people ahead of you</span>
                        </div>
                        <div class="flex flex-col gap-0.5">
                            <span class="text-caption text-muted-foreground">Estimated wait</span>
                            <span class="text-h2">About 10 min</span>
                        </div>
                    </div></div></div>
        <section aria-label="Now serving" aria-live="polite" class="flex flex-col gap-2 rounded-card border border-border bg-card p-4">
            <h3 class="text-label font-semibold">Now serving</h3>
                            <ul class="m-0 grid list-none gap-2 p-0">
                                            <li class="flex items-center justify-between gap-3 rounded-control px-3 py-2 bg-secondary">
                            <bdi dir="ltr" class="font-mono text-h3 tabular-nums">A-014</bdi>
                                                            <span class="inline-flex items-center gap-1.5 text-body-sm text-muted-foreground">
                                    <svg aria-hidden="true" class="size-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M11 20H2"/>
  <path d="M11 4.562v16.157a1 1 0 0 0 1.242.97L19 20V5.562a2 2 0 0 0-1.515-1.94l-4-1A2 2 0 0 0 11 4.561z"/>
  <path d="M11 4H8a2 2 0 0 0-2 2v14"/>
  <path d="M14 12h.01"/>
  <path d="M22 20h-3"/>
</svg>                                    Room 2
                                </span>
                                                    </li>
                                    </ul>
                    </section>
                    <div data-slot="alert-dialog" x-data="nqAlertDialog(false)" x-modelable="open" x-id="['nq-alert-dialog']" class="contents">
    <button data-slot="alert-dialog-trigger"
     type="button"                         aria-haspopup="dialog" x-on:click="show()" :aria-expanded="open" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 text-foreground hover:bg-nq-hover h-control px-[var(--nq-control-pad)]">
        <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="m16 17 5-5-5-5"/>
  <path d="M21 12H9"/>
  <path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/>
</svg>                Leave the line</button>
    <template x-teleport="body">
    <div data-slot="alert-dialog-portal">
        <div data-slot="alert-dialog-backdrop" x-nq-presence="open" class="fixed inset-0 z-50 bg-nq-fg/15 dark:bg-nq-bg/60 transition-opacity duration-150 ease-nq data-starting-style:opacity-0 data-ending-style:opacity-0"></div>
        <div data-slot="alert-dialog-content" x-bind="popup" x-nq-presence="open" x-trap.noscroll="open"
            class="fixed inset-0 z-50 m-auto grid h-fit w-[calc(100%-2rem)] max-w-md gap-4 rounded-floating border border-border bg-popover p-6 text-popover-foreground outline-none max-h-[calc(100dvh-2rem)] overflow-y-auto transition-opacity duration-150 ease-nq data-starting-style:opacity-0 data-ending-style:opacity-0">
            <div data-slot="alert-dialog-header" class="flex flex-col gap-1.5 text-start"><h2 data-slot="alert-dialog-title" :id="$id('nq-alert-dialog', 'title')" class="text-h3 text-foreground">Leave the line?</h2>
                            <p data-slot="alert-dialog-description" :id="$id('nq-alert-dialog', 'description')" class="text-body-sm text-muted-foreground">You lose your place. You can check in again, but you go to the back.</p></div>
        <div data-slot="alert-dialog-footer" class="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end"><button data-slot="alert-dialog-cancel"
     type="button"                         autofocus="autofocus" x-on:click="close()" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 text-foreground hover:bg-nq-hover h-control px-[var(--nq-control-pad)]">
        Cancel</button>
            <button data-slot="alert-dialog-action"
     type="button"                         x-on:click.capture="close()" x-on:click="$dispatch(&#039;nq-leave&#039;)" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 text-foreground hover:bg-nq-hover h-control px-[var(--nq-control-pad)]">
        Yes, leave</button></div>
        </div>
    </div>
</template>
</div>
                    </div>
```
