# LobbyDisplay

> Large-screen now-serving board with one card per room, up next and recent calls, a clock, and a chime plus flash for each new call, sized from its own width.

Source: https://docs.nasaqui.com/components/lobby-display

## Install

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

A board for a television in the waiting room. Sizes use container queries, so it reads from across the room at 1920 pixels and still fits a phone. The board shows ticket numbers only, never names.

## When to use

- A screen in the waiting room shows who is called to which room.
- Reception previews the board on a laptop.

## When not to use

- A patient's own phone: use WaitingScreen.

## Import

```tsx
import { LobbyDisplay } from "@fadymondy/nasaq/web";
// inside this monorepo: "@nasaq/web"
```

## Quick start

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

<LobbyDisplay entries={queue} rooms={["1", "2", "3"]} clinic="Nasaq Family Clinic" />
```

## Anatomy

```
LobbyDisplay                  data-slot="lobby-display"  (@container)
├─ header                     clinic, clock, sound switch
├─ room cards                 ticket and room; a new call flashes
├─ up next                    the next tickets in line
├─ recent calls
└─ sr-only live region        announces each new call
```

## API

Every `div` prop is passed through unless noted.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `entries` | `QueueEntry[]` | `required` | The whole queue. |
| `rooms` | `string[]` | `required` | Room or desk names, in board order. |
| `clinic` | `string` |  | Name in the header. |
| `connection` | `"live" \| "reconnecting" \| "offline"` | `"live"` | Small indicator. |
| `sound, defaultSound, onSoundChange` | `boolean` |  | The chime. Browsers need one click before sound can play. |
| `highlightMs` | `number` | `10000` | How long a fresh call stays highlighted. |
| `upNext` | `number` | `5` | Tickets listed under Up next. |
| `now` | `number` |  | Epoch ms for stories. |
| `labels` | `Partial<LobbyDisplayLabels>` |  | Override any string. |

## Examples

**Sound needs a click**

```tsx
// The operator turns sound on once. Until then the board flashes and announces silently.
```

## Accessibility

- A new call is announced through an assertive live region. The first render is silent.
- The flash respects reduced motion: the highlight stays, the pulse stops.
- Contrast is high for reading at a distance.

## RTL & i18n

- The board mirrors. Tickets and the clock stay left to right.
- 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="lobby-display"]` 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

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

## Lab

https://docs.nasaqui.com/?path=/docs/components-bookings-lobby-display--docs

## Code

### React

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

<LobbyDisplay entries={queue} rooms={["1", "2", "3"]} clinic="Nasaq Family Clinic" />
```

### shadcn

```tsx
import { LobbyDisplay } from "@/components/ui/lobby-display";

<LobbyDisplay entries={queue} rooms={["1", "2", "3"]} clinic="Nasaq Family Clinic" />
```

### Vue

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

const t = Date.now();
const base = { checkedInAt: t - 3_600_000 };
const entries = ref<QueueEntry[]>([
  { ...base, id: "a", ticket: "A-014", number: 14, status: "serving", room: "2", queuedAt: t - 1_500_000, calledAt: t - 600_000 },
  { ...base, id: "b", ticket: "A-015", number: 15, status: "called", room: "3", queuedAt: t - 1_200_000, calledAt: t - 90_000 },
  { ...base, id: "c", ticket: "A-016", number: 16, status: "waiting", queuedAt: t - 600_000 },
  { ...base, id: "d", ticket: "A-017", number: 17, status: "waiting", priority: "urgent", queuedAt: t - 500_000 },
  { ...base, id: "e", ticket: "A-018", number: 18, status: "waiting", queuedAt: t - 300_000 },
]);
</script>

<template>
  <NqLobbyDisplay :entries="entries" :rooms="['1', '2', '3']" clinic="Nasaq Clinic" />
</template>
```

### Blade

```blade
@php
    $t = now()->getTimestamp() * 1000;
    $base = ['checkedInAt' => $t - 3600000];
    $queue = [
        $base + ['id' => 'a', 'ticket' => 'A-014', 'number' => 14, 'status' => 'serving', 'room' => '2', 'queuedAt' => $t - 1500000, 'calledAt' => $t - 600000],
        $base + ['id' => 'b', 'ticket' => 'A-015', 'number' => 15, 'status' => 'called', 'room' => '3', 'queuedAt' => $t - 1200000, 'calledAt' => $t - 90000],
        $base + ['id' => 'c', 'ticket' => 'A-016', 'number' => 16, 'status' => 'waiting', 'queuedAt' => $t - 600000],
        $base + ['id' => 'd', 'ticket' => 'A-017', 'number' => 17, 'status' => 'waiting', 'priority' => 'urgent', 'queuedAt' => $t - 500000],
        $base + ['id' => 'e', 'ticket' => 'A-018', 'number' => 18, 'status' => 'waiting', 'queuedAt' => $t - 300000],
    ];
@endphp
<x-nq::lobby-display :entries="$queue" :rooms="['1', '2', '3']" clinic="Nasaq Clinic" :now="$t" />
```

### HTML + Alpine

```html
<div data-slot="lobby-display" aria-label="Queue board" x-data="nqLobbyDisplay(JSON.parse('{\u0022sound\u0022:false,\u0022highlightMs\u0022:10000,\u0022locale\u0022:\u0022en\u0022,\u0022frozen\u0022:true,\u0022serverNow\u0022:1790672400000,\u0022on\u0022:\u0022Sound on\u0022,\u0022off\u0022:\u0022Turn sound on\u0022}'))"
    class="@container flex w-full flex-col gap-[2cqi] bg-background p-[2cqi] text-foreground [container-type:inline-size]">
    <div aria-live="assertive" aria-atomic="true" class="sr-only" x-text="announcement"></div>
    <header class="flex flex-wrap items-center justify-between gap-3">
        <div class="min-w-0">
                            <h1 class="truncate text-[clamp(1.25rem,3cqi,2.5rem)] font-semibold leading-tight">Nasaq Clinic</h1>
                        <p class="text-[clamp(0.8rem,1.6cqi,1.25rem)] text-muted-foreground"><bdi data-date>Tuesday 29 September</bdi></p>
        </div>
        <div class="flex items-center gap-4">
            <span data-slot="queue-live" data-connection="live" role="status" aria-label="Connection: Live"     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>
            <button data-slot="button"
     type="button"                         data-sound="data-sound" x-on:click="toggleSound()" x-bind:aria-pressed="sound ? &#039;true&#039; : &#039;false&#039;" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border 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 border-border bg-card text-foreground hover:bg-nq-hover h-control-sm px-2.5">
        <svg x-show="sound" aria-hidden="true" style="display: none;" 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 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z"/>
  <path d="M16 9a5 5 0 0 1 0 6"/>
  <path d="M19.364 18.364a9 9 0 0 0 0-12.728"/>
</svg>                <svg x-show="! sound" aria-hidden="true" style="" 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 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z"/>
  <line x1="22" x2="16" y1="9" y2="15"/>
  <line x1="16" x2="22" y1="9" y2="15"/>
</svg>                <span x-text="soundLabel">Turn sound on</span></button>
            <bdi dir="ltr" data-clock class="text-[clamp(1.5rem,4.5cqi,4rem)] font-semibold leading-none tabular-nums">9:00 AM</bdi>
        </div>
    </header>
    <div class="grid gap-[2cqi] @3xl:grid-cols-[2fr_1fr]">
        <section aria-label="Now serving" class="flex flex-col gap-[1.2cqi]">
            <h2 class="text-[clamp(1rem,2.2cqi,1.75rem)] font-medium text-muted-foreground">Now serving</h2>
            <ul class="m-0 grid list-none grid-cols-[repeat(auto-fit,minmax(min(100%,15rem),1fr))] gap-[1.5cqi] p-0">
                                                        <li data-room="1" data-state="free"
                                                class="flex flex-col gap-2 rounded-card border p-[1.6cqi] transition-colors duration-300 border-dashed border-nq-line bg-secondary/60 text-muted-foreground">
                        <div class="flex items-center gap-2 text-[clamp(0.9rem,1.8cqi,1.5rem)] font-medium">
                            <svg aria-hidden="true" class="size-[1.2em]" 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 1
                        </div>
                                                    <p class="py-[2cqi] text-center text-[clamp(1rem,2.4cqi,2rem)]">Available</p>
                                            </li>
                                                        <li data-room="2" data-state="serving"
                         data-call="a:1790671800000" data-announce="Ticket A-014, please go to Room 2."                         class="flex flex-col gap-2 rounded-card border p-[1.6cqi] transition-colors duration-300 border-nq-line bg-card">
                        <div class="flex items-center gap-2 text-[clamp(0.9rem,1.8cqi,1.5rem)] font-medium">
                            <svg aria-hidden="true" class="size-[1.2em]" 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
                        </div>
                                                    <bdi dir="ltr" class="whitespace-nowrap text-center font-mono text-[clamp(2rem,5cqi,5.5rem)] font-semibold leading-none tracking-wider tabular-nums">A-014</bdi>
                            <div class="flex items-center justify-center gap-2 text-[clamp(0.8rem,1.6cqi,1.4rem)]">
                                                                    <svg aria-hidden="true" class="size-[1.2em]" 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 2v2"/>
  <path d="M5 2v2"/>
  <path d="M5 3H4a2 2 0 0 0-2 2v4a6 6 0 0 0 12 0V5a2 2 0 0 0-2-2h-1"/>
  <path d="M8 15a6 6 0 0 0 12 0v-3"/>
  <circle cx="20" cy="10" r="2"/>
</svg>                                    In visit
                                                            </div>
                                            </li>
                                                        <li data-room="3" data-state="called"
                         data-call="b:1790672310000" data-announce="Ticket A-015, please go to Room 3."                         class="flex flex-col gap-2 rounded-card border p-[1.6cqi] transition-colors duration-300 border-primary bg-nq-selected">
                        <div class="flex items-center gap-2 text-[clamp(0.9rem,1.8cqi,1.5rem)] font-medium">
                            <svg aria-hidden="true" class="size-[1.2em]" 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 3
                        </div>
                                                    <bdi dir="ltr" class="whitespace-nowrap text-center font-mono text-[clamp(2rem,5cqi,5.5rem)] font-semibold leading-none tracking-wider tabular-nums">A-015</bdi>
                            <div class="flex items-center justify-center gap-2 text-[clamp(0.8rem,1.6cqi,1.4rem)]">
                                                                    <svg aria-hidden="true" class="size-[1.2em]" 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="M10.268 21a2 2 0 0 0 3.464 0"/>
  <path d="M22 8c0-2.3-.8-4.3-2-6"/>
  <path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326"/>
  <path d="M4 2C2.8 3.7 2 5.7 2 8"/>
</svg>                                    Please come in
                                                            </div>
                                            </li>
                                            </ul>
        </section>
        <div class="flex flex-col gap-[2cqi]">
            <section aria-label="Up next" class="flex flex-col gap-2 rounded-card border border-nq-line p-[1.6cqi]">
                <h2 class="flex items-baseline justify-between text-[clamp(0.9rem,1.8cqi,1.5rem)] font-medium text-muted-foreground">
                    Up next
                    <span class="text-[0.8em]">Waiting: 3</span>
                </h2>
                                    <ul class="m-0 flex list-none flex-wrap gap-2 p-0">
                                                    <li class="rounded-control bg-secondary px-3 py-1.5">
                                <bdi dir="ltr" class="font-mono text-[clamp(1.1rem,2.6cqi,2.25rem)] font-semibold tabular-nums">A-017</bdi>
                            </li>
                                                    <li class="rounded-control bg-secondary px-3 py-1.5">
                                <bdi dir="ltr" class="font-mono text-[clamp(1.1rem,2.6cqi,2.25rem)] font-semibold tabular-nums">A-016</bdi>
                            </li>
                                                    <li class="rounded-control bg-secondary px-3 py-1.5">
                                <bdi dir="ltr" class="font-mono text-[clamp(1.1rem,2.6cqi,2.25rem)] font-semibold tabular-nums">A-018</bdi>
                            </li>
                                                                    </ul>
                            </section>
            <section aria-label="Recently called" class="flex flex-col gap-2 rounded-card border border-nq-line p-[1.6cqi]">
                <h2 class="text-[clamp(0.9rem,1.8cqi,1.5rem)] font-medium text-muted-foreground">Recently called</h2>
                                    <ul class="m-0 grid list-none gap-1.5 p-0">
                                                    <li class="flex items-center justify-between gap-3 text-[clamp(1rem,2cqi,1.75rem)]">
                                <bdi dir="ltr" class="font-mono font-semibold tabular-nums">A-015</bdi>
                                <span class="text-muted-foreground">Room 3</span>
                            </li>
                                                    <li class="flex items-center justify-between gap-3 text-[clamp(1rem,2cqi,1.75rem)]">
                                <bdi dir="ltr" class="font-mono font-semibold tabular-nums">A-014</bdi>
                                <span class="text-muted-foreground">Room 2</span>
                            </li>
                                            </ul>
                            </section>
        </div>
    </div>
</div>
```
