{
  "name": "ScrollProgress",
  "source": "src/components/ScrollProgress.tsx",
  "propsType": "ScrollProgressProps",
  "description": "The compact rail is the disclosure trigger. It is a real `<button>`, so a\nkeyboard user reaches it by Tab and opens it the same way as any button —\nEnter or Space — no `:focus-within` trick required; a mouse user can also\njust hover it. Built as a WAI-ARIA Disclosure (`aria-expanded` +\n`aria-controls`) rather than a menu: see the CSS header for why\n`role=\"menu\"` is deliberately not used here.\n\nCloses on outside pointerdown and on Escape — the minimum a disclosure\nneeds to not trap the page once opened, not a full popover/focus-trap\nimplementation.",
  "import": "import { ScrollProgress } from 'ionbase-ui';",
  "status": "stable",
  "summary": "A compact progress rail that expands into a section list. A disclosure, not a menu — and it computes nothing itself.",
  "useWhen": [
    "a long document or page needs an in-page section index with reading progress",
    "the caller already knows the scroll fraction and the active section"
  ],
  "useInstead": [
    {
      "when": "the sections are peer views rather than positions in one document",
      "use": "Tabs"
    },
    {
      "when": "it is site navigation rather than in-page position",
      "use": "NavItem"
    }
  ],
  "variants": {
    "placement": {
      "right": {
        "use": "the default — a rail on the left of the content, opening across it"
      },
      "left": {
        "use": "a rail against the right edge of the page; `right` would open the list off the screen"
      }
    }
  },
  "a11y": {
    "role": "disclosure — a real <button> with aria-expanded and aria-controls",
    "guarantees": [
      "the rail is reachable by Tab and opens with Enter or Space, not just hover",
      "closes on outside pointerdown and on Escape"
    ],
    "requires": [
      "`progress` as 0–100 and `activeId` — the component has no opinion on where they come from",
      "handling `onSelect` yourself: it reports the choice and does not scroll anywhere"
    ],
    "notes": [
      "Deliberately not `role=\"menu\"` — see the stylesheet header. It is a disclosure with a list inside it, not a menu with roving focus.",
      "Not a focus trap: it is the minimum a disclosure needs to not trap the page once opened."
    ]
  },
  "antiPatterns": [
    {
      "dont": "expecting it to scroll the page when a section is chosen",
      "why": "it reports the choice, matching Menu — what \"select this section\" means is the page's decision"
    },
    {
      "dont": "deriving `progress` from a scroll listener without throttling",
      "why": "this re-renders the rail on every scroll frame"
    },
    {
      "dont": "expecting every section to be visible at once",
      "why": "the panel caps at 60vh and scrolls — a document-length index would otherwise grow off both edges of the viewport, taking the rows nearest the active one with it"
    },
    {
      "dont": "leaving `placement` at `right` for a rail on the page's right edge",
      "why": "the open list runs past the viewport; `left` opens it back over the content"
    }
  ],
  "stylesheet": "src/styles/scroll-progress.css",
  "tokens": [
    "--border-default",
    "--border-focus",
    "--border-strong",
    "--border-stronger",
    "--border-width-default",
    "--border-width-thick",
    "--font-family-sans",
    "--font-weight-regular",
    "--ion-duration-base",
    "--ion-ease-out",
    "--ion-scroll-progress-slide",
    "--ion-shadow-shadow-md",
    "--radius-full",
    "--radius-md",
    "--radius-sm",
    "--spacing-24",
    "--spacing-32",
    "--spacing-4",
    "--spacing-6",
    "--spacing-8",
    "--surface-default",
    "--surface-muted",
    "--text-default",
    "--text-secondary",
    "--text-tertiary",
    "--type-body-sm",
    "--type-body-sm-line-height",
    "--type-caption",
    "--type-caption-line-height"
  ],
  "props": {
    "progress": {
      "type": "number",
      "required": true,
      "origin": "own",
      "description": "0-100. The caller computes this from whatever it is tracking — a\nwindow scroll fraction, a custom scroll container, a reading-time\nestimate. This component has no opinion on the source."
    },
    "sections": {
      "type": "ScrollProgressSection[]",
      "required": true,
      "origin": "own"
    },
    "activeId": {
      "type": "string | undefined",
      "required": false,
      "origin": "own",
      "description": "The section the rail's heavier tick and the panel's selected row point\nto. Also the caller's to compute — see `progress`."
    },
    "onSelect": {
      "type": "((id: string) => void) | undefined",
      "required": false,
      "origin": "own",
      "description": "Fired when a row is chosen. The component does not scroll anywhere\nitself; matching Menu, it reports the choice and lets the caller decide\nwhat \"select this section\" means for their page."
    },
    "placement": {
      "type": "ScrollProgressPlacement | undefined",
      "required": false,
      "origin": "own",
      "description": "Which way the section list opens. `right` suits a rail on the left of the\ncontent; a rail against the right edge of the page needs `left`, or the\nopen list runs off the screen.",
      "values": [
        "left",
        "right"
      ]
    },
    "triggerLabel": {
      "type": "((progress: number, section?: string) => string) | undefined",
      "required": false,
      "origin": "own",
      "description": "The rail button's name — \"Reading progress: 32%. Currently in Usage.\nActivate to jump to a section.\" in English. Receives the rounded\npercentage and the active section's label, when there is one."
    }
  },
  "propCounts": {
    "own": 6,
    "aria": 0,
    "dom": 277,
    "other": 0
  }
}
