# Night dot row (Notchset): prompt.md (v2.0.0)

- id: `night-dot-row` · version 2.0.0 · component · free
- category: Charts
- build: one set of files for every shadcn style
- install: `npx shadcn@latest add https://notchset.dev/r/night-dot-row.json`
- npm dependencies: none
- registry dependencies: utils, https://notchset.dev/r/notchset-foundation.json
- docs: https://notchset.dev/components/night-dot-row
- The install command carries everything this item needs (files, CSS, tokens, npm and registry dependencies). Prefer it to copying source by hand.

Seven nights as dots with each day and figure: hollow when below your usual, the chosen night ringed and lit. A radio group: arrow keys, Home and End move and choose, and each dot says its night in words..

## Build it
- Stack: React 18.3 or 19, TypeScript, Tailwind CSS v4 utility classes on the foundation's roles, a components.json (imports use its aliases).
- Packages: none beyond React.
- Files: `components/ui/notchset/night-dot-row.tsx`.
- Registry dependencies, installed with it automatically: shadcn `utils` (cn), `notchset-foundation`.
- Builds: one set of files, the same for every shadcn style.
- Exports to keep: `NightDotRow`, and every exported type.
- CSS: the install adds the notchset foundation (its roles, keyframes and motion switch, reading your shadcn tokens for colour) to your global stylesheet once. Nothing to import by hand.
- Re-running `add` (or `--overwrite`) re-applies those rules: put overrides in your own CSS, never in the installed rules.
- Colours: every part reads the foundation's roles (`--ns-canvas`, `--ns-ink`, `--ns-line`, `--ns-signal` …), which read your shadcn tokens (`--background`, `--foreground`, `--border`, `--destructive` …), plus the optional `--success` and `--warning`. Retheme on `:root` or any container; never add Tailwind colour classes inside the component.
- This item reads `--ns-canvas`, `--ns-font-mono`, `--ns-glow`, `--ns-ink`, `--ns-ink-2`, `--ns-ink-3`, `--ns-series`, `--ns-signal`, `--ns-text-meta`, `--ns-time-color`, `--ns-weight-medium`.
- Retune it (on any ancestor) with the foundation's roles and hooks: `--ns-bad`, `--ns-bad-ink`, `--ns-band`, `--ns-band-edge`, `--ns-band-grade`, `--ns-band-optimal`, `--ns-beat`, `--ns-bezel`, `--ns-canvas`, `--ns-cat-1`, `--ns-cat-2`, `--ns-cat-3`, `--ns-cat-4`, `--ns-cat-5`, `--ns-cat-6`, `--ns-check`, `--ns-chip`, `--ns-control`, `--ns-control-pad`, `--ns-control-sm`, `--ns-deep`, `--ns-deep-ink`, `--ns-deep-ink-2`, `--ns-deep-line`, `--ns-dialog`, `--ns-dialog-lg`, `--ns-dialog-sm`, `--ns-ease`, `--ns-ease-in`, `--ns-ease-panel`, `--ns-ease-pop`, `--ns-field`, `--ns-flood-arc`, `--ns-flood-aurora`, `--ns-flood-behind-1`, `--ns-flood-behind-2`, `--ns-flood-bloom`, `--ns-flood-deep`, `--ns-flood-dial-bloom`, `--ns-flood-glass`, `--ns-flood-grain`, `--ns-flood-image`, `--ns-flood-ink`, `--ns-flood-ink-low`, `--ns-flood-light`, `--ns-flood-mid`, `--ns-flood-pale`, `--ns-flood-pane`, `--ns-flood-pane-edge`, `--ns-flood-pill-ink`, `--ns-flood-ribbon`, `--ns-flood-ribbon-core`, `--ns-flood-shadow`, `--ns-flood-sheen`, `--ns-flood-wash`, `--ns-font-body`, `--ns-font-display`, `--ns-font-mono`, `--ns-font-script`, `--ns-glass`, `--ns-glass-blur`, `--ns-glass-shadow`, `--ns-glow`, `--ns-grain`, `--ns-gutter`, `--ns-header`, `--ns-heat`, `--ns-hover`, `--ns-ink`, `--ns-ink-2`, `--ns-ink-3`, `--ns-ink-4`, `--ns-leading`, `--ns-lift`, `--ns-light-1`, `--ns-light-2`, `--ns-light-3`, `--ns-light-spread`, `--ns-light-strength`, `--ns-line`, `--ns-line-soft`, `--ns-lit-edge`, `--ns-menu-dy`, `--ns-menu-row`, `--ns-mesh`, `--ns-mesh-frost`, `--ns-ok`, `--ns-ok-ink`, `--ns-orb-body`, `--ns-orb-cool`, `--ns-orb-core`, `--ns-orb-dawn`, `--ns-orb-deep`, `--ns-orb-edge`, `--ns-orb-grain`, `--ns-orb-ground`, `--ns-orb-halo`, `--ns-orb-inner`, `--ns-orb-key`, `--ns-orb-lift`, `--ns-orb-limb`, `--ns-orb-limb-from`, `--ns-orb-rim`, `--ns-orb-skin-hi`, `--ns-orb-skin-lo`, `--ns-orb-skin-mid`, `--ns-orb-warm`, `--ns-panel`, `--ns-panel-w`, `--ns-prism-edge`, `--ns-prism-glint`, `--ns-prism-sheen`, `--ns-prism-tint`, `--ns-pulse`, `--ns-radius`, `--ns-radius-card`, `--ns-radius-full`, `--ns-radius-lg`, `--ns-radius-sm`, `--ns-radius-xl`, `--ns-raised`, `--ns-range`, `--ns-row`, `--ns-row-compact`, `--ns-segment`, `--ns-selected`, `--ns-series`, `--ns-series-core`, `--ns-series-lilac`, `--ns-series-tint`, `--ns-shadow-dialog`, `--ns-shadow-float`, `--ns-shadow-panel`, `--ns-sheet`, `--ns-sheet-bottom`, `--ns-side`, `--ns-signal`, `--ns-sleep-awake`, `--ns-sleep-deep`, `--ns-sleep-light`, `--ns-sleep-rem`, `--ns-space-1`, `--ns-space-2`, `--ns-space-3`, `--ns-space-4`, `--ns-space-5`, `--ns-space-6`, `--ns-space-8`, `--ns-space-section`, `--ns-stack-on`, `--ns-standing-critical`, `--ns-standing-in`, `--ns-standing-out`, `--ns-state-doing`, `--ns-state-done`, `--ns-state-review`, `--ns-state-todo`, `--ns-switch-h`, `--ns-switch-w`, `--ns-text-body`, `--ns-text-control`, `--ns-text-display`, `--ns-text-figure`, `--ns-text-headline`, `--ns-text-hero`, `--ns-text-lede`, `--ns-text-meta`, `--ns-text-page`, `--ns-text-record`, `--ns-text-small`, `--ns-text-strong`, `--ns-text-sub`, `--ns-text-title`, `--ns-tile-shadow`, `--ns-time-born`, `--ns-time-color`, `--ns-time-dialog`, `--ns-time-menu`, `--ns-time-page`, `--ns-time-panel`, `--ns-time-toast`, `--ns-tint`, `--ns-tint-ink`, `--ns-toast-bottom`, `--ns-toast-left`, `--ns-touch`, `--ns-tracking-display`, `--ns-tracking-headline`, `--ns-warn`, `--ns-warn-ink`, `--ns-wash-clay`, `--ns-wash-dusk`, `--ns-wash-graphite`, `--ns-wash-ink`, `--ns-wash-sage`, `--ns-wash-sand`, `--ns-weight-light`, `--ns-weight-medium`, `--ns-weight-regular`, `--ns-weight-strong`, `--ns-well`.
- Never add Tailwind colour classes inside the component.

```tsx
import { NightDotRow } from "@/components/ui/notchset/night-dot-row";
```

## Parts
- A single export that takes props.

## Match the original
- Read `components/ui/notchset/night-dot-row.tsx` as the reference implementation before changing or recreating anything, and match it: sizes, colours per theme, motion timings, copy and behaviour.
- If you deviate (a prop you can't honour, a style you changed, a dependency you swapped), say so in your reply, part by part.
- Keep the accessibility contract, the keyboard map and the motion levels listed below.

## Use it when

### Not when

## Mistakes
- Recreating the component instead of installing it

## Usage

```tsx
"use client";
import * as React from "react";
import { NightDotRow } from "@/components/ui/notchset/night-dot-row";

const NIGHTS = [
  { id: "19", day: "Mon 19", value: "7:04" },
  { id: "20", day: "Tue 20", value: "6:22", below: true },
  { id: "21", day: "Wed 21", value: "7:18" },
  { id: "22", day: "Thu 22", value: "6:41" },
  { id: "23", day: "Fri 23", value: "6:12", below: true },
  { id: "24", day: "Sat 24", value: "7:25" },
  { id: "25", day: "Sun 25", value: "6:57" },
];

export default function NightDotRowDemo() {
  const [night, setNight] = React.useState("25");
  return (
    <div className="max-w-[720px]">
      <NightDotRow nights={NIGHTS} value={night} onValueChange={setNight} />
    </div>
  );
}
```

## Props

| Prop | Type | Default | What it does |
|---|---|---|---|
| `nights` | `Night[]` |  | Required. |
| `value` | `string` |  | Required. |
| `onValueChange` | `(id: string) => void` |  | Required. |
| `label` | `string` | `"Choose a night"` |  |

Full docs: https://notchset.dev/components/night-dot-row

## Customising
- Colours: every part reads the foundation's roles (`--ns-canvas`, `--ns-ink`, `--ns-line`, `--ns-signal` …), which read your shadcn tokens (`--background`, `--foreground`, `--border`, `--destructive` …), plus the optional `--success` and `--warning`. Retheme on `:root` or any container; never add Tailwind colour classes inside the component.
- Dark mode follows the `.dark` class or `data-theme="dark"` on an ancestor (the shadcn and next-themes conventions).
- Update later: Commit or stash local changes first (or copy the files), so the update can be undone. Read https://notchset.dev/r/changelog.json for the new versions: a major version, an entry marked breaking or a merge says what to change. Then re-run the install with `--overwrite`, on a branch. It replaces the component's files, the shared helper files they import, and the item's CSS rules in the global stylesheet, so review the whole diff (components, shared files, CSS), re-apply your own edits, and run the app's typecheck, tests and build.

## Accessibility contract (preserve when editing)

## Install

```bash
npx shadcn@latest add https://notchset.dev/r/night-dot-row.json
```

Or add the `@notchset` registry to components.json and run `npx shadcn@latest add @notchset/night-dot-row`. Every components.json style gets the same files.

## Continue the workflow

- [Day timeline](https://notchset.dev/components/day-timeline)
- [Ghost trend](https://notchset.dev/components/ghost-trend)
- [Glucose day](https://notchset.dev/components/glucose-day)

## Source

### components/ui/notchset/night-dot-row.tsx

```tsx
// Notchset night-dot-row 2.0.0 · https://notchset.dev/changelog
"use client";
import * as React from "react";
import { cn } from "@/lib/utils";

/*
 * Night dot row: seven nights as dots, each with its day and figure. A night below your usual is hollow; the chosen
 * night is ringed and lit, and choosing one changes the whole page (the host listens). It is a radio group: arrow
 * keys move and choose, Home and End jump, and each dot says its night in words, so the row is its own table.
 *
 *   <NightDotRow nights={[{ id: "19", day: "Mon 19", value: "7:04", below: false }, …]} value="25" onValueChange={setNight} />
 */
type Night = {
  id: string;
  /** "Mon 19" */
  day: string;
  /** "7:04", or null when nothing was recorded */
  value: string | null;
  /** below your usual: drawn hollow */
  below?: boolean | undefined;
};
type NightDotRowProps = {
  nights: Night[];
  value: string;
  onValueChange: (id: string) => void;
  label?: string | undefined;
  className?: string | undefined;
};

function NightDotRow({ nights, value, onValueChange, label = "Choose a night", className }: NightDotRowProps) {
  const refs = React.useRef<(HTMLButtonElement | null)[]>([]);
  const at = Math.max(0, nights.findIndex((n) => n.id === value));
  const go = (i: number) => {
    const n = nights[(i + nights.length) % nights.length];
    if (!n) return;
    onValueChange(n.id);
    refs.current[(i + nights.length) % nights.length]?.focus();
  };
  const onKey = (e: React.KeyboardEvent) => {
    const k = e.key;
    if (k === "ArrowRight" || k === "ArrowDown") go(at + 1);
    else if (k === "ArrowLeft" || k === "ArrowUp") go(at - 1);
    else if (k === "Home") go(0);
    else if (k === "End") go(nights.length - 1);
    else return;
    e.preventDefault();
  };
  return (
    <div data-slot="night-dot-row" role="radiogroup" aria-label={label} onKeyDown={onKey} className={cn("grid min-w-0 gap-1", className)} style={{ gridTemplateColumns: `repeat(${nights.length}, minmax(0, 1fr))` }}>
      {nights.map((n, i) => {
        const on = i === at;
        const none = n.value == null;
        return (
          <button
            key={n.id}
            ref={(el) => {
              refs.current[i] = el;
            }}
            type="button"
            role="radio"
            aria-checked={on}
            tabIndex={on ? 0 : -1}
            aria-label={`${n.day}: ${none ? "nothing recorded" : `${n.value}${n.below ? ", below your usual" : ""}`}`}
            onClick={() => onValueChange(n.id)}
            data-state={on ? "on" : "off"}
            className={cn(
              "flex min-w-0 cursor-pointer flex-col items-center gap-1.5 group/night relative rounded-[10px] py-2 outline-hidden focus-visible:outline-2 focus-visible:outline-[var(--ns-signal)]",
            )}
          >
            <span className={cn("truncate text-[length:var(--ns-text-meta)] [transition:color_var(--ns-time-color)]", on ? "font-[number:var(--ns-weight-medium)] text-[var(--ns-ink)]" : "text-[var(--ns-ink-3)] group-hover/night:text-[var(--ns-ink-2)]")}>{n.day}</span>
            <i
              aria-hidden
              data-slot="night-dot"
              data-below={n.below ? "" : undefined}
              className={cn(
                "block size-3 rounded-full",
                none ? "border border-dashed border-[var(--ns-ink-3)]" : n.below ? "border-[1.5px] border-[var(--ns-series)] bg-transparent" : "bg-[var(--ns-series)] opacity-80",
                on && !none && "opacity-100 shadow-[0_0_0_2.5px_var(--ns-canvas),0_0_0_4px_var(--ns-series),0_0_14px_4px_color-mix(in_oklab,var(--ns-series)_var(--ns-glow),transparent)]",
              )}
            />
            <span className={cn("font-[family-name:var(--ns-font-mono)] text-[length:var(--ns-text-meta)] tabular-nums", on ? "text-[var(--ns-ink)]" : "text-[var(--ns-ink-3)]")}>{n.value ?? "–"}</span>
            {/* the chosen night: a short notch under its figure, no box */}
            <i aria-hidden className={cn("block h-0.5 w-4 rounded-full bg-[var(--ns-series)] [transition:opacity_var(--ns-time-color),transform_var(--ns-time-color)]", on ? "opacity-100" : "scale-x-50 opacity-0")} />
          </button>
        );
      })}
    </div>
  );
}

export { NightDotRow, type Night, type NightDotRowProps };
```

The foundation (the tokens listed above, keyframes and motion levels) installs once with the first component; its CSS is public at https://notchset.dev/r/notchset-foundation.json.
