# GlanceSurfaces

> Glanceable surfaces, a tray or menu-bar popover, watch rows, home and lock-screen widget tiles and a widget gallery with a size picker.

Source: https://docs.nasaqui.com/components/glance-surfaces

## Install

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

Information you read in a second. `GlanceRow` is one icon, label, value line shared by every surface.
`TrayPopover` is a menu-bar popover with a caret and actions. `WatchGlance` is a round or square watch face of
rows. `WidgetTile` is a home or lock-screen tile in five sizes, and `WidgetGallery` lets people pick a size and add
or remove a widget.

## When to use

- A tray app, a status bar popover or a companion watch view.
- A widget picker for a customisable home screen.

## When not to use

- A browser extension popup: use [ExtensionPopup](https://docs.nasaqui.com/components/extension-popup).
- A full dashboard: use the dashboard board.

## Import

```tsx
import { GlanceRow, TrayPopover, WidgetGallery, WidgetTile } from "@fadymondy/nasaq/web";
// inside this monorepo: "@nasaq/web"
```

## Quick start

```tsx
import { ClockIcon } from "lucide-react";
import { GlanceRow, TrayPopover } from "@fadymondy/nasaq/web";

export function Tray() {
  return (
    <TrayPopover title="Today">
      <GlanceRow icon={ClockIcon} label="Timer running" value="00:42" tone="info" />
    </TrayPopover>
  );
}
```

## Anatomy

```
TrayPopover        data-slot="tray-popover"   caret, header, rows, actions with shortcuts
WatchGlance        data-slot="watch-glance"   round or square frame, rows
WidgetTile         data-slot="widget-tile"    size, surface, ring or bar progress
WidgetGallery      data-slot="widget-gallery" preview, size picker, add or remove
GlanceRow          icon, label, value, detail
```

## API

### `WidgetTile`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `size?` | `"circular" \| "inline" \| "small" \| "medium" \| "large"` | `"small"` | Tile size. |
| `surface?` | `"home" \| "lock"` | `"home"` | Lock tiles are translucent. |
| `title`, `icon`, `value`, `caption` | mixed | none | Content. |
| `progress?` | `number` | none | 0 to 100, a ring for circular, else a bar. |
| `tone?` | `GlanceTone` | `"neutral"` | Accent. |
| `onOpen?` | `() => void` | none | Makes the tile a button. |

### `TrayPopover`

`title`, `subtitle`, `headerEnd`, `caret` (`"start" | "end"`), `actions` (label, icon, shortcut, onSelect).

### `WatchGlance`

`title`, `headerEnd`, `shape` (`"round" | "square"`), children.

### `WidgetGallery`

`widgets` (`WidgetDefinition`: id, title, sizes, render), `added`, `onAdd`, `onRemove`, `labels`.

## Examples

The lab has Default, Arabic, Mobile plus Gallery and GalleryArabic stories.

## Accessibility

Tiles with `onOpen` are buttons. The size picker is a radio-style group. Progress carries a text value.

## RTL & i18n

The tray caret and progress ring mirror in RTL. Numbers keep their direction. Strings via `useOptionalNasaq()`.

## Styling & tokens

Only `--nq-*` tokens; tones map to the status colours.

## Do / Don't

- Do keep a tile to one number and one caption.
- Don't nest interactive controls inside a tile that has `onOpen`.

## Related

[ExtensionPopup](https://docs.nasaqui.com/components/extension-popup), [`Progress`](https://docs.nasaqui.com/components/progress).

## Lab

`Components / Layout / Glance Surfaces` in the lab.

## Code

### React

```tsx
import { ClockIcon } from "lucide-react";
import { GlanceRow, TrayPopover } from "@fadymondy/nasaq/web";

export function Tray() {
  return (
    <TrayPopover title="Today">
      <GlanceRow icon={ClockIcon} label="Timer running" value="00:42" tone="info" />
    </TrayPopover>
  );
}
```

### shadcn

```tsx
import { ClockIcon } from "lucide-react";
import { GlanceRow, TrayPopover } from "@/components/ui/glance-surfaces";

export function Tray() {
  return (
    <TrayPopover title="Today">
      <GlanceRow icon={ClockIcon} label="Timer running" value="00:42" tone="info" />
    </TrayPopover>
  );
}
```

### Vue

```vue
<script setup lang="ts">
import { NqGlanceRow, NqTrayPopover } from "@fadymondy/nasaq/vue";
import { Clock } from "lucide-vue-next";
</script>

<template>
  <NqTrayPopover title="Today">
    <NqGlanceRow :icon="Clock" label="Timer running" value="00:42" tone="info" />
  </NqTrayPopover>
</template>
```

### Blade

```blade
<x-nq::glance-surfaces.tray-popover title="Today">
    <x-nq::glance-surfaces.glance-row icon="clock" label="Timer running" value="00:42" tone="info" />
</x-nq::glance-surfaces.tray-popover>
<x-nq::glance-surfaces.widget-gallery :added="['timer']" class="mt-6">
    <x-nq::glance-surfaces.widget-gallery-item id="steps" title="Steps" description="Daily goal" :sizes="['small', 'medium']">
        <x-nq::glance-surfaces.widget-tile size="small" title="Steps" icon="footprints" value="8,200" caption="of 10,000" :progress="82" tone="success" x-show="size === 'small'" />
        <x-nq::glance-surfaces.widget-tile size="medium" title="Steps" icon="footprints" value="8,200" caption="of 10,000" :progress="82" tone="success" x-show="size === 'medium'" style="display: none" />
    </x-nq::glance-surfaces.widget-gallery-item>
    <x-nq::glance-surfaces.widget-gallery-item id="timer" title="Timer" :sizes="['circular']">
        <x-nq::glance-surfaces.widget-tile size="circular" title="Timer" icon="clock" value="42" :progress="70" tone="info" />
    </x-nq::glance-surfaces.widget-gallery-item>
</x-nq::glance-surfaces.widget-gallery>
```

### HTML + Alpine

```html
<div data-slot="tray-popover" role="group" aria-label="Today" class="relative w-80 max-w-full rounded-xl border border-border bg-card text-foreground shadow-lg">
            <span aria-hidden="true" class="absolute -top-1.5 size-3 rotate-45 border-t border-s border-border bg-card end-5"></span>
        <header class="relative flex items-center gap-3 px-4 pt-3.5 pb-2">
        <div class="min-w-0 flex-1">
            <h2 class="truncate text-label font-semibold">Today</h2>
                    </div>
    </header>
            <div class="flex flex-col px-1 pb-1"><div data-slot="glance-row" class="flex w-full items-center gap-2.5 rounded-control px-3 py-2">
    <svg aria-hidden="true" class="shrink-0 size-[18px] text-nq-info-text" 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">
  <circle cx="12" cy="12" r="10"/>
  <path d="M12 6v6l4 2"/>
</svg>    <span class="min-w-0 flex-1 text-start">
        <span class="block truncate text-foreground text-label">Timer running</span>
            </span>
    <span class="shrink-0 tabular-nums text-label font-medium text-nq-info-text">00:42</span></div></div>
        </div>
<div data-slot="widget-gallery" role="list" aria-label="Widget gallery" x-data="nqWidgetGallery(JSON.parse('[\u0022timer\u0022]'))" x-modelable="added"
    class="grid gap-6 [grid-template-columns:repeat(auto-fill,minmax(min(100%,20rem),1fr))] mt-6">
    <article role="listitem" data-widget-id="steps" x-data="{ size: 'small', id: 'steps' }" class="flex flex-col items-start gap-3 rounded-xl border border-border bg-secondary/40 p-4">
    <div class="flex w-full min-h-40 items-center justify-center overflow-hidden"><div data-slot="widget-tile" data-size="small" data-surface="home" role="group" aria-label="Steps, 8,200, of 10,000" x-show="size === &#039;small&#039;" class="relative flex shrink-0 overflow-hidden text-foreground flex-col border border-border bg-card shadow-sm size-38 rounded-3xl p-4">
            <div class="flex items-center gap-1.5 text-caption font-medium text-muted-foreground">
            <svg aria-hidden="true" class="size-4 shrink-0" 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="M4 16v-2.38C4 11.5 2.97 10.5 3 8c.03-2.72 1.49-6 4.5-6C9.37 2 10 3.8 10 5.5c0 3.11-2 5.66-2 8.68V16a2 2 0 1 1-4 0Z"/>
  <path d="M20 20v-2.38c0-2.12 1.03-3.12 1-5.62-.03-2.72-1.49-6-4.5-6C14.63 6 14 7.8 14 9.5c0 3.11 2 5.66 2 8.68V20a2 2 0 1 0 4 0Z"/>
  <path d="M16 17h4"/>
  <path d="M4 13h4"/>
</svg>            <span class="truncate">Steps</span>
        </div>
        <div class="mt-auto">
            <p class="tabular-nums leading-none font-semibold text-h1 text-nq-success-text">8,200</p>            <p class="mt-1 truncate text-caption text-muted-foreground">of 10,000</p>                            <div role="presentation" class="mt-2 h-1.5 overflow-hidden rounded-full bg-nq-line-strong/50">
                    <div class="h-full rounded-full bg-nq-success" style="width: 82%"></div>
                </div>
                    </div>
            </div>
        <div data-slot="widget-tile" data-size="medium" data-surface="home" role="group" aria-label="Steps, 8,200, of 10,000" x-show="size === &#039;medium&#039;" style="display: none" class="relative flex shrink-0 overflow-hidden text-foreground flex-col border border-border bg-card shadow-sm h-38 w-80 rounded-3xl p-4">
            <div class="flex items-center gap-1.5 text-caption font-medium text-muted-foreground">
            <svg aria-hidden="true" class="size-4 shrink-0" 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="M4 16v-2.38C4 11.5 2.97 10.5 3 8c.03-2.72 1.49-6 4.5-6C9.37 2 10 3.8 10 5.5c0 3.11-2 5.66-2 8.68V16a2 2 0 1 1-4 0Z"/>
  <path d="M20 20v-2.38c0-2.12 1.03-3.12 1-5.62-.03-2.72-1.49-6-4.5-6C14.63 6 14 7.8 14 9.5c0 3.11 2 5.66 2 8.68V20a2 2 0 1 0 4 0Z"/>
  <path d="M16 17h4"/>
  <path d="M4 13h4"/>
</svg>            <span class="truncate">Steps</span>
        </div>
        <div class="mt-auto">
            <p class="tabular-nums leading-none font-semibold text-display text-nq-success-text">8,200</p>            <p class="mt-1 truncate text-caption text-muted-foreground">of 10,000</p>                            <div role="presentation" class="mt-2 h-1.5 overflow-hidden rounded-full bg-nq-line-strong/50">
                    <div class="h-full rounded-full bg-nq-success" style="width: 82%"></div>
                </div>
                    </div>
            </div></div>
    <div class="min-w-0">
        <h3 class="text-label font-semibold text-foreground">Steps</h3>
        <p class="text-caption text-muted-foreground">Daily goal</p>    </div>
    <div class="flex w-full flex-wrap items-center justify-between gap-2">
                    <div role="radiogroup" aria-label="Widget size: Steps" class="flex gap-0.5 rounded-control bg-secondary p-0.5">
                                    <button type="button" role="radio" aria-checked="true"
                        x-on:click="size = 'small'" x-effect="$el.setAttribute('aria-checked', size === 'small')"
                        class="h-7 rounded-[calc(var(--radius-control)-2px)] px-2.5 text-caption text-muted-foreground outline-none hover:text-foreground aria-checked:bg-card aria-checked:text-foreground aria-checked:shadow-xs focus-visible:outline-2 focus-visible:outline-nq-focus">Small</button>
                                    <button type="button" role="radio" aria-checked="false"
                        x-on:click="size = 'medium'" x-effect="$el.setAttribute('aria-checked', size === 'medium')"
                        class="h-7 rounded-[calc(var(--radius-control)-2px)] px-2.5 text-caption text-muted-foreground outline-none hover:text-foreground aria-checked:bg-card aria-checked:text-foreground aria-checked:shadow-xs focus-visible:outline-2 focus-visible:outline-nq-focus">Medium</button>
                            </div>
                <button data-slot="button"
     type="button"                         x-show="isAdded(id)" style="display: none" x-on:click="remove(id)" 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 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="M20 6 9 17l-5-5"/>
</svg>            Remove</button>
        <button data-slot="button"
     type="button"                         x-show="! isAdded(id)" x-on:click="add(id, size)" 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 bg-primary text-primary-foreground hover:bg-[color-mix(in_oklab,var(--nq-action)_88%,var(--nq-fg))] h-control-sm px-2.5">
        <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="M5 12h14"/>
  <path d="M12 5v14"/>
</svg>            Add widget</button>
    </div>
</article>
    <article role="listitem" data-widget-id="timer" x-data="{ size: 'circular', id: 'timer' }" class="flex flex-col items-start gap-3 rounded-xl border border-border bg-secondary/40 p-4">
    <div class="flex w-full min-h-40 items-center justify-center overflow-hidden"><div data-slot="widget-tile" data-size="circular" data-surface="home" role="group" aria-label="Timer, 42" class="relative flex shrink-0 overflow-hidden text-foreground items-center justify-center border border-border bg-card shadow-sm size-16 rounded-full p-0">
                        <svg aria-hidden="true" viewBox="0 0 64 64" class="absolute inset-0 size-full -rotate-90 rtl:scale-y-[-1]">
                <circle cx="32" cy="32" r="26" fill="none" stroke-width="5" class="stroke-nq-line-strong/60" />
                <circle cx="32" cy="32" r="26" fill="none" stroke-width="5" stroke-linecap="round" stroke-dasharray="163.36281798667" stroke-dashoffset="49.008845396001" class="stroke-nq-info" />
            </svg>
                <span class="relative flex flex-col items-center leading-none">
            <svg aria-hidden="true" class="mb-0.5 size-3.5 text-muted-foreground" 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">
  <circle cx="12" cy="12" r="10"/>
  <path d="M12 6v6l4 2"/>
</svg>            <span class="text-label font-semibold tabular-nums text-nq-info-text">42</span>
        </span>
    </div></div>
    <div class="min-w-0">
        <h3 class="text-label font-semibold text-foreground">Timer</h3>
            </div>
    <div class="flex w-full flex-wrap items-center justify-between gap-2">
                    <span></span>
                <button data-slot="button"
     type="button"                         x-show="isAdded(id)" x-on:click="remove(id)" 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 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="M20 6 9 17l-5-5"/>
</svg>            Remove</button>
        <button data-slot="button"
     type="button"                         x-show="! isAdded(id)" style="display: none" x-on:click="add(id, size)" 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 bg-primary text-primary-foreground hover:bg-[color-mix(in_oklab,var(--nq-action)_88%,var(--nq-fg))] h-control-sm px-2.5">
        <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="M5 12h14"/>
  <path d="M12 5v14"/>
</svg>            Add widget</button>
    </div>
</article>
</div>
```
