# FocusStatusChip

> Focus presence, a header status chip with the time left, an avatar with an in-focus dot, and a do not disturb switch. focusStateOf derives the state from a pomodoro.

Source: https://docs.nasaqui.com/components/focus-status

## Install

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

What a person is doing right now, in three small pieces. `FocusStatusChip` is the compact header chip ("In focus 12:34").
`FocusAvatar` is an [`Avatar`](https://docs.nasaqui.com/components/avatar) with a presence dot at its inline end. `DoNotDisturbToggle` is the
switch row that silences notifications. `focusStateOf` turns a pomodoro and the do not disturb switch into one of four
states: `available`, `focus`, `break`, `dnd`.

## When to use

- Showing your own status in an app header, and your teammates' on their avatars.
- Pairing with [`usePomodoro`](https://docs.nasaqui.com/components/pomodoro) so focusing updates presence by itself.

## When not to use

- A generic state in a table or list: use [`Status`](https://docs.nasaqui.com/components/status).
- Online or offline connection state: use [`WsStatus`](https://docs.nasaqui.com/components/ws-status).

## Import

```tsx
import { DoNotDisturbToggle, FocusAvatar, FocusStatusChip, focusStateOf } from "@fadymondy/nasaq/web";
// inside this monorepo: "@nasaq/web"
```

## Quick start

```tsx
import { FocusAvatar, FocusStatusChip, focusStateOf, usePomodoro } from "@fadymondy/nasaq/web";

export function HeaderStatus({ name }: { name: string }) {
  const pomodoro = usePomodoro();
  const state = focusStateOf(pomodoro);
  return (
    <div className="flex items-center gap-3">
      <FocusStatusChip state={state} seconds={pomodoro.seconds} />
      <FocusAvatar name={name} state={state} />
    </div>
  );
}
```

## Anatomy

```
FocusStatusChip                 data-slot="focus-status", data-state
├─ icon                         a different glyph per state
├─ label                        or `text`
└─ mm:ss                        while focusing or on a break
FocusAvatar                     data-slot="focus-avatar", data-state
├─ Avatar
└─ presence dot                 role="img" with the state as its name
DoNotDisturbToggle              data-slot="do-not-disturb", data-state
├─ label, description, "Until" time
└─ Switch
```

## API

### `FocusStatusChip`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `state` | `"available" \| "focus" \| "break" \| "dnd"` | required | Which presence to show. |
| `seconds?` | `number` | none | Time left. Shown as mm:ss for `focus` and `break` only. |
| `text?` | `string` | state word | Replaces the word, for example a task name. |
| `onClick?` | `() => void` | none | Makes the chip a button. |
| `labels?` | `FocusStatusLabels` | built-in en/ar | String overrides. |

### `FocusAvatar`

Takes every `Avatar` prop (`name`, `src`, `size`, `shape`) plus `state`, `hideAvailable?` (no dot when available) and `labels?`.
Larger sizes show the state icon in the dot; small sizes show a plain dot with the same accessible name.

### `DoNotDisturbToggle`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `checked` | `boolean` | required | On or off. |
| `onCheckedChange` | `(checked: boolean) => void` | required | Change handler. |
| `until?` | `string` | none | Text after "Until", shown while on. |
| `disabled?` | `boolean` | `false` | Disable the switch. |
| `labels?` | `FocusStatusLabels` | built-in | String overrides. |

### `focusStateOf(input, dnd?)`

`(input: { phase, status } | null | undefined, dnd = false) => FocusState`. Do not disturb wins. A running or paused focus is
`focus`, a running or paused break is `break`, anything not started is `available`. A `PomodoroController` fits `input`.

## Examples

### Task name in the chip

```tsx
<FocusStatusChip state="focus" seconds={754} text="Design review" onClick={openPomodoro} />
```

### Team list

```tsx
{people.map((p) => (
  <li key={p.id}><FocusAvatar name={p.name} src={p.avatar} state={p.state} /> {p.name}</li>
))}
```

### Do not disturb wins

```tsx
const [dnd, setDnd] = useState(false);
const state = focusStateOf(pomodoro, dnd);
<DoNotDisturbToggle checked={dnd} onCheckedChange={setDnd} until="6:00 PM" />
```

## Accessibility

| Key | Action |
| --- | --- |
| Tab | Focus the chip (when it has `onClick`) or the switch. |
| Enter / Space | Activate the chip; toggle the switch. |

- Each state has its own icon and word, so it is not colour only. The presence dot is `role="img"` with the state as its name.
- The time has an `aria-label` such as "12:34 left".
- Localise `labels` for other languages.

## RTL & i18n

- The dot sits at the inline end of the avatar, so it flips in RTL. The mm:ss stays left-to-right.
- Built-in English and Arabic strings follow the Nasaq locale.

## Styling & tokens

Tokens only (`bg-nq-success`, `bg-primary`, `bg-nq-warning-soft`, `border-background`). Target `data-state` on each part. Pass
`className` for layout.

## Do / Don't

- Do show do not disturb even during focus; it is the stronger signal.
- Don't rely on the dot colour alone in your own layouts; keep the label or `title`.
- Don't use it for connection state.

## Related

- [`Pomodoro`](https://docs.nasaqui.com/components/pomodoro)
- [`Avatar`](https://docs.nasaqui.com/components/avatar)
- [`Status`](https://docs.nasaqui.com/components/status)
- [`Countdown`](https://docs.nasaqui.com/components/countdown)

## Lab

`https://docs.nasaqui.com/?path=/docs/components-productivity-focus-status--docs`

## Code

### React

```tsx
import { FocusAvatar, FocusStatusChip, focusStateOf, usePomodoro } from "@fadymondy/nasaq/web";

export function HeaderStatus({ name }: { name: string }) {
  const pomodoro = usePomodoro();
  const state = focusStateOf(pomodoro);
  return (
    <div className="flex items-center gap-3">
      <FocusStatusChip state={state} seconds={pomodoro.seconds} />
      <FocusAvatar name={name} state={state} />
    </div>
  );
}
```

### shadcn

```tsx
import { FocusAvatar, FocusStatusChip, focusStateOf } from "@/components/ui/focus-status";
import { usePomodoro } from "@/components/ui/pomodoro";

export function HeaderStatus({ name }: { name: string }) {
  const pomodoro = usePomodoro();
  const state = focusStateOf(pomodoro);
  return (
    <div className="flex items-center gap-3">
      <FocusStatusChip state={state} seconds={pomodoro.seconds} />
      <FocusAvatar name={name} state={state} />
    </div>
  );
}
```

### Vue

```vue
<script setup lang="ts">
import { ref } from "vue";
import { NqDoNotDisturbToggle, NqFocusAvatar, NqFocusStatusChip, focusStateOf } from "@fadymondy/nasaq/vue";

const name = "Fady Mondy";
const dnd = ref(true);
// In an app this is the pomodoro controller; any { phase, status } works.
const state = focusStateOf({ phase: "focus", status: "running" });
</script>

<template>
  <div class="flex flex-col gap-4">
    <div class="flex items-center gap-3">
      <NqFocusStatusChip :state="state" :seconds="754" />
      <NqFocusAvatar :name="name" :state="state" />
    </div>
    <NqDoNotDisturbToggle v-model="dnd" until="6:00 PM" class="max-w-md" />
  </div>
</template>
```

### Blade

```blade
<div class="flex flex-col gap-4">
    <div class="flex items-center gap-3">
        <x-nq::focus-status state="focus" :seconds="754" />
        <x-nq::focus-status.avatar name="Fady Mondy" state="focus" />
    </div>
    <x-nq::focus-status.do-not-disturb checked until="6:00 PM" class="max-w-md" />
</div>
```

### HTML + Alpine

```html
<div class="flex flex-col gap-4">
    <div class="flex items-center gap-3">
        <span data-slot="focus-status" data-state="focus"     class="inline-flex h-7 max-w-full items-center gap-1.5 rounded-full border px-2.5 text-caption font-medium border-primary/40 bg-[color-mix(in_oklab,var(--nq-action)_12%,transparent)] text-foreground">
    <svg aria-hidden="true" class="size-3.5 shrink-0 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="M12 18V5"/>
  <path d="M15 13a4.17 4.17 0 0 1-3-4 4.17 4.17 0 0 1-3 4"/>
  <path d="M17.598 6.5A3 3 0 1 0 12 5a3 3 0 1 0-5.598 1.5"/>
  <path d="M17.997 5.125a4 4 0 0 1 2.526 5.77"/>
  <path d="M18 18a4 4 0 0 0 2-7.464"/>
  <path d="M19.967 17.483A4 4 0 1 1 12 18a4 4 0 1 1-7.967-.517"/>
  <path d="M6 18a4 4 0 0 1-2-7.464"/>
  <path d="M6.003 5.125a4 4 0 0 0-2.526 5.77"/>
</svg>    <span class="truncate">In focus</span>
    <span dir="ltr" class="tabular-nums text-muted-foreground" aria-label="12:34 left">12:34</span></span>
        <span data-slot="focus-avatar" data-state="focus" class="relative inline-flex shrink-0">
    <span data-slot="avatar" x-data="nqAvatar(0)"
    class="inline-flex shrink-0 select-none items-center justify-center overflow-hidden bg-secondary align-middle font-medium text-secondary-foreground size-8 text-caption rounded-full">
                    <span data-slot="avatar-fallback" class="flex size-full items-center justify-center" x-bind="fallback"
             role="img" aria-label="Fady Mondy" >FM</span>
    </span>
            <span role="img" aria-label="In focus" title="In focus"
            class="absolute -bottom-0.5 -end-0.5 grid place-items-center rounded-full border-2 border-background size-4 bg-primary text-primary-foreground">
            <svg aria-hidden="true" class="size-2.5" 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 18V5"/>
  <path d="M15 13a4.17 4.17 0 0 1-3-4 4.17 4.17 0 0 1-3 4"/>
  <path d="M17.598 6.5A3 3 0 1 0 12 5a3 3 0 1 0-5.598 1.5"/>
  <path d="M17.997 5.125a4 4 0 0 1 2.526 5.77"/>
  <path d="M18 18a4 4 0 0 0 2-7.464"/>
  <path d="M19.967 17.483A4 4 0 1 1 12 18a4 4 0 1 1-7.967-.517"/>
  <path d="M6 18a4 4 0 0 1-2-7.464"/>
  <path d="M6.003 5.125a4 4 0 0 0-2.526 5.77"/>
</svg>        </span>
    </span>
    </div>
    <div data-slot="do-not-disturb"
    x-data="{ dnd: true }" x-modelable="dnd" x-bind:data-state="dnd ? 'on' : 'off'"
    class="flex items-center justify-between gap-4 max-w-md">
    <div class="flex min-w-0 flex-col gap-0.5">
        <label for="nq-dnd-000001" class="inline-flex items-center gap-2 text-label text-foreground">
            <svg aria-hidden="true" class="size-4 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">
  <path d="M10.268 21a2 2 0 0 0 3.464 0"/>
  <path d="M17 17H4a1 1 0 0 1-.74-1.673C4.59 13.956 6 12.499 6 8a6 6 0 0 1 .258-1.742"/>
  <path d="m2 2 20 20"/>
  <path d="M8.668 3.01A6 6 0 0 1 18 8c0 2.687.77 4.653 1.707 6.05"/>
</svg>            Do not disturb
        </label>
        <p class="text-body-sm text-muted-foreground">
            Silences notifications and shows you as busy to your team.
            <span class="ms-1 text-nq-warning-text" x-show="dnd" >Until ⁦6:00 PM⁩</span>        </p>
    </div>
    <button type="button" role="switch" data-slot="switch" x-data="nqSwitch(true)" x-modelable="checked" x-bind="root"
    aria-checked="true"
     data-checked                 id="nq-dnd-000001" x-model="dnd" class="relative inline-flex h-5 w-9 shrink-0 items-center rounded-full border border-transparent bg-nq-line-strong p-0.5 outline-none transition-colors duration-150 ease-nq data-checked:bg-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:cursor-not-allowed data-disabled:opacity-50">
    <span data-slot="switch-thumb"  data-checked         x-bind:data-checked="checked ? '' : undefined" x-bind:data-unchecked="checked ? undefined : ''"
        class="block size-4 rounded-full bg-background shadow-xs data-checked:bg-primary-foreground transition-[translate] duration-150 ease-nq data-checked:translate-x-3.5 rtl:data-checked:-translate-x-3.5"></span>
    </button>
</div>
</div>
```
