# StoreOrderTimeline

> The order status timeline shared by the customer account and the store admin. The tracking variant draws placed, paid, shipped, out for delivery and delivered with times and a carrier link. The activity variant lists every event newest first and takes internal notes.

Source: https://docs.nasaqui.com/components/store-order-timeline

## Install

```bash
npx shadcn@latest add https://docs.nasaqui.com/r/store-order-timeline.json
```

One component for both sides of an order. The customer sees five steps (placed, paid, shipped, out for delivery, delivered) with the time each happened and a link to the carrier. The store team sees the full event log, newest first, and can add internal notes. The steps are worked out from `CommerceOrder.status`, `payment` and `events`, so the two sides can never disagree about where an order is.

The status and payment words and chip colours (`ORDER_STATUS_LABEL`, `PAYMENT_LABEL`, `FULFILMENT_LABEL` and the matching `*_VARIANT` maps) are exported too, so a chip in the admin list reads the same as the one in the customer's account.

## When to use

- The order page in a customer account (`variant="tracking"`).
- The order page in the store admin (`variant="activity"`).

## When not to use

- A generic activity feed that has nothing to do with orders: use `timeline`.
- A returns (RMA) progress bar: `StoreReturnStatus` in `store-account` draws that.

## Import

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

## Quick start

```tsx
import { StoreOrderTimeline, type CommerceOrder } from "@fadymondy/nasaq/web";

export function Tracking({ order }: { order: CommerceOrder }) {
  return (
    <StoreOrderTimeline
      status={order.status}
      payment={order.payment}
      placedAt={order.placedAt}
      events={order.events}
      tracking={order.tracking}
      trackingTemplate="https://track.example.com/?n={number}"
    />
  );
}
```

## Anatomy

```
StoreOrderTimeline [data-slot="store-order-timeline"] [data-variant="tracking" | "activity"]
├─ terminal banner          cancelled, refunded or returned (tracking)
├─ partial badge            "Part of this order has shipped" (tracking)
├─ ol > li[data-state]      five steps: done, current, upcoming or skipped (tracking)
├─ carrier row              carrier, tracking number, "Track shipment" link (tracking)
├─ note composer            Textarea and Add note button, when onAddNote is set (activity)
└─ Timeline                 events, newest first (activity)
```

## API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` | `"tracking" \| "activity"` | `"tracking"` | Customer steps or admin event log. |
| `status` | `CommerceOrderStatus` | `required` | The order status. |
| `payment` | `CommercePaymentStatus` |  | Cash on delivery changes the "paid" step to "confirmed" and adds a note. |
| `placedAt` | `string` |  | ISO time of the first step. |
| `events` | `CommerceOrderEvent[]` |  | Times for the steps come from event kinds; the activity variant lists them all. |
| `tracking` | `{ carrier; number; url? }` |  | Shows the carrier row. |
| `trackingTemplate` | `string` |  | Carrier link with `{number}`, used when `tracking.url` is empty. |
| `onAddNote` | `(note: string) => void` |  | Activity: shows the composer. |
| `labels` | `StoreOrderTimelineLabels` |  | Replace any string. |
| `className` | `string` |  | On the root. |

Helpers: `trackingModel({ status, payment, placedAt, events, hasTracking })` returns `{ steps, reached, percent, partial, terminal }`. `trackingUrl(tracking, template)` builds the carrier link. `sortEventsNewestFirst(events)` and `activityKind(kind)` group event kinds for the icons. `useStoreTimelineStrings(labels)` returns `{ t, ar, locale }`.

## Examples

**Admin log with notes**

```tsx
<StoreOrderTimeline variant="activity" status={order.status} events={order.events} onAddNote={(note) => save(note)} />
```

**A cancelled order**

```tsx
<StoreOrderTimeline status="cancelled" placedAt="2026-09-20T10:00:00Z" events={events} />
```

## Accessibility

- The steps are an ordered list. The current step has `aria-current="step"`. Every state is also written in words ("Done", "In progress", "Next"), not only shown by colour.
- The carrier link opens in a new tab with `rel="noreferrer"`.
- The note field has a label; the Add note button stays disabled until there is text.

| Key | Action |
| --- | --- |
| Tab | Moves to the note field, the Add note button and the carrier link. |
| Enter | Submits the note from the button. |

## RTL & i18n

- The step bar runs vertically on narrow screens and across on wide ones, with logical borders, so it mirrors in Arabic.
- Times use `DateTime` and the active locale. Tracking numbers are isolated and left to right.
- Strings live in `STRINGS = { en, ar }`; the locale comes from `NasaqProvider`. Any string can be replaced with `labels`.

## Styling & tokens

- Bars use `border-primary`, `border-border`; the terminal banner uses `bg-secondary`.
- Target `[data-slot="store-order-timeline"]` and `li[data-state="done" | "current" | "upcoming" | "skipped"]`.

## Do / Don't

- Do pass the same `status`, `payment` and `events` to both variants.
- Do keep the customer variant free of internal notes: notes only render in `activity`.
- Don't compute progress yourself; call `trackingModel`.

## Related

- [`store-account`](https://docs.nasaqui.com/components/store-account)
- [`store-orders-admin`](https://docs.nasaqui.com/components/store-orders-admin)
- [`timeline`](https://docs.nasaqui.com/components/timeline)

## Lab

https://docs.nasaqui.com/?path=/docs/components-storefront-store-order-timeline--docs

## Code

### React

```tsx
import { StoreOrderTimeline, type CommerceOrder } from "@fadymondy/nasaq/web";

export function Tracking({ order }: { order: CommerceOrder }) {
  return (
    <StoreOrderTimeline
      status={order.status}
      payment={order.payment}
      placedAt={order.placedAt}
      events={order.events}
      tracking={order.tracking}
      trackingTemplate="https://track.example.com/?n={number}"
    />
  );
}
```

### shadcn

```tsx
import { StoreOrderTimeline, type CommerceOrder } from "@/components/ui/store-order-timeline";

export function Tracking({ order }: { order: CommerceOrder }) {
  return (
    <StoreOrderTimeline
      status={order.status}
      payment={order.payment}
      placedAt={order.placedAt}
      events={order.events}
      tracking={order.tracking}
      trackingTemplate="https://track.example.com/?n={number}"
    />
  );
}
```

### Vue

```vue
<script setup lang="ts">
import { NqStoreOrderTimeline } from "@fadymondy/nasaq/vue";

const order = {
  status: "shipped",
  payment: "paid",
  placedAt: "2026-09-20T10:00:00Z",
  events: [
    { at: "2026-09-20T10:00:00Z", kind: "placed", label: "Order placed" },
    { at: "2026-09-20T10:05:00Z", kind: "paid", label: "Payment confirmed" },
    { at: "2026-09-21T09:00:00Z", kind: "shipped", label: "Shipped" },
  ],
  tracking: { carrier: "Aramex", number: "AB123456789" },
} as const;
</script>

<template>
  <NqStoreOrderTimeline
    :status="order.status"
    :payment="order.payment"
    :placed-at="order.placedAt"
    :events="order.events"
    :tracking="order.tracking"
    tracking-template="https://track.example.com/?n={number}"
  />
</template>
```

### Blade

```blade
<x-nq::store-order-timeline
    status="shipped"
    payment="paid"
    placed-at="2026-09-20T10:00:00Z"
    :events="[
        ['at' => '2026-09-20T10:00:00Z', 'kind' => 'placed', 'label' => 'Order placed'],
        ['at' => '2026-09-20T10:05:00Z', 'kind' => 'paid', 'label' => 'Payment confirmed'],
        ['at' => '2026-09-21T09:00:00Z', 'kind' => 'shipped', 'label' => 'Shipped'],
    ]"
    :tracking="['carrier' => 'Aramex', 'number' => 'AB123456789']"
    tracking-template="https://track.example.com/?n={number}"
/>
```

### HTML + Alpine

```html
<section data-slot="store-order-timeline" data-variant="tracking" aria-label="Order progress" class="flex flex-col gap-4">
                        <ol class="m-0 grid list-none gap-3 p-0 sm:grid-cols-5" aria-label="Order progress" data-percent="50">
                                            <li data-state="done"                     class="flex min-w-0 flex-col gap-1 border-s-4 ps-3 sm:border-s-0 sm:border-t-4 sm:ps-0 sm:pt-3 border-primary">
                    <span class="flex items-center gap-1.5 text-body-sm font-medium text-foreground">
                                                    <svg aria-hidden="true" class="size-4 text-primary" 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="M20 6 9 17l-5-5"/>
</svg>                                                <span class="min-w-0">Order placed</span>
                    </span>
                    <span class="text-caption text-muted-foreground">
                                                    <time data-slot="date-time" datetime="2026-09-20T10:00:00+00:00" dir="auto"     class="tabular-nums [unicode-bidi:isolate]">Sep 20, 2026, 10:00 AM</time>
                                            </span>
                </li>
                                            <li data-state="done"                     class="flex min-w-0 flex-col gap-1 border-s-4 ps-3 sm:border-s-0 sm:border-t-4 sm:ps-0 sm:pt-3 border-primary">
                    <span class="flex items-center gap-1.5 text-body-sm font-medium text-foreground">
                                                    <svg aria-hidden="true" class="size-4 text-primary" 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="M20 6 9 17l-5-5"/>
</svg>                                                <span class="min-w-0">Payment confirmed</span>
                    </span>
                    <span class="text-caption text-muted-foreground">
                                                    <time data-slot="date-time" datetime="2026-09-20T10:05:00+00:00" dir="auto"     class="tabular-nums [unicode-bidi:isolate]">Sep 20, 2026, 10:05 AM</time>
                                            </span>
                </li>
                                            <li data-state="current"  aria-current="step"                     class="flex min-w-0 flex-col gap-1 border-s-4 ps-3 sm:border-s-0 sm:border-t-4 sm:ps-0 sm:pt-3 border-primary/50">
                    <span class="flex items-center gap-1.5 text-body-sm font-medium text-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="M12 22v-9"/>
  <path d="M15.17 2.21a1.67 1.67 0 0 1 1.63 0L21 4.57a1.93 1.93 0 0 1 0 3.36L8.82 14.79a1.655 1.655 0 0 1-1.64 0L3 12.43a1.93 1.93 0 0 1 0-3.36z"/>
  <path d="M20 13v3.87a2.06 2.06 0 0 1-1.11 1.83l-6 3.08a1.93 1.93 0 0 1-1.78 0l-6-3.08A2.06 2.06 0 0 1 4 16.87V13"/>
  <path d="M21 12.43a1.93 1.93 0 0 0 0-3.36L8.83 2.2a1.64 1.64 0 0 0-1.63 0L3 4.57a1.93 1.93 0 0 0 0 3.36l12.18 6.86a1.636 1.636 0 0 0 1.63 0z"/>
</svg>                                                <span class="min-w-0">Shipped</span>
                    </span>
                    <span class="text-caption text-muted-foreground">
                                                    <time data-slot="date-time" datetime="2026-09-21T09:00:00+00:00" dir="auto"     class="tabular-nums [unicode-bidi:isolate]">Sep 21, 2026, 9:00 AM</time>
                                            </span>
                </li>
                                            <li data-state="upcoming"                     class="flex min-w-0 flex-col gap-1 border-s-4 ps-3 sm:border-s-0 sm:border-t-4 sm:ps-0 sm:pt-3 border-border">
                    <span class="flex items-center gap-1.5 text-body-sm font-medium 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="M14 18V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v11a1 1 0 0 0 1 1h2"/>
  <path d="M15 18H9"/>
  <path d="M19 18h2a1 1 0 0 0 1-1v-3.65a1 1 0 0 0-.22-.624l-3.48-4.35A1 1 0 0 0 17.52 8H14"/>
  <circle cx="17" cy="18" r="2"/>
  <circle cx="7" cy="18" r="2"/>
</svg>                                                <span class="min-w-0">Out for delivery</span>
                    </span>
                    <span class="text-caption text-muted-foreground">
                                                    Next
                                            </span>
                </li>
                                            <li data-state="upcoming"                     class="flex min-w-0 flex-col gap-1 border-s-4 ps-3 sm:border-s-0 sm:border-t-4 sm:ps-0 sm:pt-3 border-border">
                    <span class="flex items-center gap-1.5 text-body-sm font-medium 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="M12 22V12"/>
  <path d="m16 17 2 2 4-4"/>
  <path d="M21 11.127V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.729l7 4a2 2 0 0 0 2 .001l1.32-.753"/>
  <path d="M3.29 7 12 12l8.71-5"/>
  <path d="m7.5 4.27 8.997 5.148"/>
</svg>                                                <span class="min-w-0">Delivered</span>
                    </span>
                    <span class="text-caption text-muted-foreground">
                                                    Next
                                            </span>
                </li>
                    </ol>
                            <div class="flex flex-wrap items-center gap-x-4 gap-y-2 text-body-sm">
                <span class="text-muted-foreground">Carrier: <span class="text-foreground">Aramex</span></span>
                <span class="text-muted-foreground">Tracking number: <bdi dir="ltr" class="font-mono text-foreground">AB123456789</bdi></span>
                                    <a data-slot="button"
     href="https://track.example.com/?n=AB123456789"                         target="_blank" rel="noreferrer" 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">
        Track shipment
                        <svg aria-hidden="true" class="rtl:-scale-x-100" 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="M15 3h6v6"/>
  <path d="M10 14 21 3"/>
  <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
</svg></a>
                            </div>
            </section>
```
