{
  "version": "0.2.0-preview.1",
  "content_digest": "70ac710f4d22dce040e7ac593379ba8aca9dcb59a0b9c0f839819cae0e2bb721",
  "status": "preview",
  "authority": {
    "repository": "cloakedinc/design",
    "path": "packages/design-system",
    "catalog": "packages/design-system/catalog.json",
    "status": "preview"
  },
  "provenance": {
    "repo": "cloakedinc/automation",
    "commit": "f96f416e1c3bfbfaffcd155a88b87b18b8bb25f1",
    "path": "packages/design-system",
    "imported_spec_status": "approved"
  },
  "adaptations": [
    {
      "id": "button-content-gap",
      "status": "preview",
      "description": "Use the specification's 4px content gap instead of the inherited 8px implementation gap."
    },
    {
      "id": "keyboard-focus",
      "status": "preview",
      "description": "Add visible Button and Input keyboard focus and an overridable inset focus default for shared button resets, including FAQ cards and ListItem rows."
    },
    {
      "id": "supporting-text-contrast",
      "status": "preview",
      "description": "Map light secondary text to the existing grey-04-strong primitive for readable helper text on white and cream."
    },
    {
      "id": "danger-text-contrast",
      "status": "preview",
      "description": "Separate readable danger text/borders from destructive fills; preserve the primary danger-button fill and label pair."
    },
    {
      "id": "preview-status",
      "status": "preview",
      "description": "Local components, specs and token adaptations await human sign-off; upstream approved status is provenance only."
    },
    {
      "id": "motion-comparison-accessibility",
      "status": "preview",
      "description": "Preserve the original MotionShowcase comparisons while requiring explicit replay, responding to live reduced-motion changes, and keeping duration tracks usable on small screens."
    },
    {
      "id": "list-item-ownership",
      "status": "preview",
      "description": "List owns listitem wrappers for its content rows, while section labels stay outside the row count and standalone rows retain their existing semantics."
    },
    {
      "id": "card-delta-text-contrast",
      "status": "preview",
      "description": "Mix Card.Delta foregrounds from 60% existing tone solid and 40% primary text for readable contrast in both themes; preserve the four original tone fills."
    },
    {
      "id": "carousel-keyboard-access",
      "status": "preview",
      "description": "Keep all native-scrolling carousel slide controls exposed to assistive technology and keyboard navigation; pause autoplay while focus is inside the carousel."
    }
  ],
  "id": "segmentedcontrol",
  "name": "SegmentedControl",
  "category": "Controls",
  "summary": "A list of buttons for switching one view mode, with the selected option marked by aria-current.",
  "documentation": {
    "summary": "A list of buttons for switching one view mode, with the selected option marked by aria-current.",
    "guidance": [
      {
        "heading": "Overview",
        "body": "An iOS-style single-select switch for up to 5 mutually exclusive modes that re-filter one view in\nplace, such as a time range or a chart granularity."
      },
      {
        "heading": "It is a list of buttons",
        "body": "The markup is a `<ul>` of buttons, and the selected one carries `aria-current`. It is deliberately\nnone of the roles it resembles:\n\n-   Not a **radiogroup**. That role needs radio semantics on every option — `role=\"radio\"` with\n    `aria-checked` — plus radio-group arrow-key behavior. These are ordinary buttons.\n-   Not a **tablist**. That role promises content panels and arrow-key traversal.\n-   Not a **toolbar**. That role is a collection of function buttons.\n\nEach of those roles carries its own keyboard and announcement rules, so borrowing the wrong one\nmisleads anyone using assistive technology."
      },
      {
        "heading": "When to use",
        "body": "Reach for `SegmentedControl` when a small, fixed set of options changes how the same content is\nsliced and all options fit inline. Choosing between the neighbours:\n\n-   The options switch between **distinct content panels** rather than re-filtering one view? Use\n    `Tabs`.\n-   The set **grows from data**, wraps, or scrolls? Use a `ChipGroup`. The boundary is the shape of\n    the set: a segmented control is fixed, connected, and equal-width.\n-   A **binary input**, such as on and off? Use `Toggle`.\n-   You want a **labelled form field** with descriptions, or more than 5 options? Use `RadioGroup`.\n-   Too many options to sit inline? Use `Dropdown` + `Menu`."
      },
      {
        "heading": "Counts and dots",
        "body": "An option may carry a trailing `count`, a decorative `dot`, or its own `disabled` state. A count is\ntext, so it is announced with the label. A dot is not, so put the reason it is there into the label\nwhen it carries meaning."
      }
    ],
    "usage": [],
    "examples": [],
    "source": "packages/design-system/src/components/controls/SegmentedControl/SegmentedControl.mdx",
    "status": "local-preview",
    "note": "Guidance is read from local documentation, originally imported from the frozen source. Platform assertions in guidance are not parity verification."
  },
  "source": {
    "path": "packages/design-system/src/components/controls/SegmentedControl/SegmentedControl.tsx",
    "code": "import React from 'react';\nimport { cn } from '../../../utilities';\nimport { useHaptic } from '../../../hooks/useHaptic';\nimport type { HapticFeedback } from '../../../tokens/CdsHaptic';\nimport styles from './SegmentedControl.module.scss';\n\n/**\n * One option in the strip. Options are data, not elements: none of them carries a handler, and the\n * strip reports which `id` was pressed.\n */\nexport interface SegmentedControlItem {\n    /** Stable option id. Also suffixes the derived test selector. */\n    id: string;\n    /** Visible option label. Keep it short: segments share the width equally. */\n    label: string;\n    /** Trailing count, rendered as text so it is announced with the label. */\n    count?: number;\n    /** Attention indicator. Decorative, so say why it is there in the label when it carries meaning. */\n    dot?: boolean;\n    /** Blocks this one option through the native attribute, without disabling the set. */\n    disabled?: boolean;\n    /** Overrides the selector derived from `testId` and `id`. */\n    testId?: string;\n}\n\n/**\n * How a strip's options claim horizontal space. `fill` divides the strip\n * equally; `hug` lets each option keep its own label's width.\n */\nexport type SegmentedControlSizing = 'fill' | 'hug';\n\n/**\n * Props for `SegmentedControl`. Selection is controlled: the strip renders `activeId` as active and\n * emits the next id rather than tracking one.\n */\nexport interface SegmentedControlProps {\n    /** Options rendered in order. Keep the set to 5 or fewer, per the iOS guideline. */\n    items: SegmentedControlItem[];\n    /** Controlled `id` of the active option. An id matching no item leaves none active. */\n    activeId: string;\n    /** Called with the selected option id. Skipped when the pressed option is already active. */\n    onSelect?: (id: string) => void;\n    /** Accessible name for the strip, announced before the options — such as `Time range`. */\n    ariaLabel?: string;\n    className?: string;\n    /** Haptic feedback for selection changes. Defaults to `true` (`selection`). */\n    haptic?: HapticFeedback;\n    /**\n     * How options claim width. `fill` (default) shares the strip equally and\n     * ellipsises labels that do not fit; `hug` sizes each option to its own\n     * label, for a strip that should be no wider than its content.\n     */\n    sizing?: SegmentedControlSizing;\n    /** Optional test selector. Each option derives `${testId}-${id}`. */\n    testId?: string;\n}\n\n/**\n * A list of buttons, deliberately not a `radiogroup` (which needs `role='radio'`, `aria-checked`,\n * and radio-group arrow-key behavior) and not a `tablist` (which promises content panels). These\n * are ordinary buttons, so the selected one is marked with `aria-current`.\n */\nexport function SegmentedControl({\n    items,\n    activeId,\n    onSelect,\n    ariaLabel,\n    className,\n    haptic = true,\n    sizing = 'fill',\n    testId,\n}: SegmentedControlProps) {\n    const fireHaptic = useHaptic('selection');\n\n    return (\n        <ul\n            className={cn(styles.strip, styles[`sizing-${sizing}`], className)}\n            role='list'\n            aria-label={ariaLabel}\n            data-testid={testId}\n        >\n            {items.map(item => {\n                const isActive = item.id === activeId;\n                const derivedTestId = item.testId ?? (testId ? `${testId}-${item.id}` : undefined);\n\n                return (\n                    <li key={item.id} className={styles.slot}>\n                        <button\n                            type='button'\n                            aria-current={isActive}\n                            disabled={item.disabled}\n                            className={cn(styles.item, isActive ? styles.active : styles.inactive)}\n                            onClick={() => {\n                                if (isActive) return;\n                                if (onSelect) fireHaptic(haptic);\n                                onSelect?.(item.id);\n                            }}\n                            data-testid={derivedTestId}\n                        >\n                            <span className={styles.label}>{item.label}</span>\n                            {item.count !== undefined && <span className={styles.count}>{item.count}</span>}\n                            {item.dot && <span className={styles.dot} aria-hidden='true' />}\n                        </button>\n                    </li>\n                );\n            })}\n        </ul>\n    );\n}\n"
  },
  "spec": {
    "$schema": "../../../specs/component-spec.schema.json",
    "identity": {
      "name": "SegmentedControl",
      "status": "preview",
      "version": "2.1.0",
      "summary": "A list of buttons for switching one view mode, with the selected option marked by aria-current."
    },
    "parts": [
      {
        "id": "strip",
        "label": "Strip",
        "description": "The list that owns the accessible name and shares width equally between options.",
        "required": true
      },
      {
        "id": "option",
        "label": "Option",
        "description": "One button per option. Carries aria-current and its own disabled state.",
        "required": true
      },
      {
        "id": "count",
        "label": "Count",
        "description": "Optional trailing number, rendered as text so it is announced with the label."
      },
      {
        "id": "dot",
        "label": "Dot",
        "description": "Optional decorative attention indicator."
      }
    ],
    "allowedDesignChoices": {
      "variants": {
        "pill": {
          "label": "Pill",
          "description": "Equal-width pill segments with filled active and outlined inactive treatments."
        }
      },
      "sizes": {}
    },
    "variants": [
      {
        "id": "pill",
        "label": "Pill",
        "description": "Compact mode switch with one active option.",
        "platforms": [
          "ios",
          "android",
          "web"
        ]
      }
    ],
    "sizes": [],
    "measurements": [
      {
        "id": "option-height",
        "label": "Option height",
        "kind": "exact",
        "token": "--cds-size-32",
        "value": 32,
        "unit": "px",
        "appliesTo": [
          "option"
        ]
      },
      {
        "id": "option-horizontal-padding",
        "label": "Option horizontal padding",
        "kind": "exact",
        "token": "--cds-space-16",
        "value": 16,
        "unit": "px",
        "appliesTo": [
          "option"
        ]
      },
      {
        "id": "option-content-gap",
        "label": "Option content gap",
        "kind": "exact",
        "token": "--cds-space-4",
        "value": 4,
        "unit": "px",
        "appliesTo": [
          "option",
          "count",
          "dot"
        ]
      },
      {
        "id": "option-gap",
        "label": "Gap between options",
        "kind": "exact",
        "token": "--cds-space-8",
        "value": 8,
        "unit": "px",
        "appliesTo": [
          "strip"
        ]
      },
      {
        "id": "dot-size",
        "label": "Dot size",
        "kind": "exact",
        "token": "--cds-size-8",
        "value": 8,
        "unit": "px",
        "appliesTo": [
          "dot"
        ]
      }
    ],
    "layout": {
      "alignment": "Options share equal width and center their content in both axes.",
      "flow": "Two to four options form one horizontal row.",
      "notes": [
        "Use Tabs when options switch distinct content panels rather than one view mode.",
        "Use a ChipGroup when the set grows from data, wraps, or scrolls.",
        "Use Toggle for a binary input rather than a two-option strip."
      ]
    },
    "typographyAndColor": {
      "typography": [
        {
          "label": "Segment label",
          "description": "Use the compact link ramp for concise mode labels.",
          "token": "--cds-type-link-size"
        }
      ],
      "colors": [
        {
          "label": "Active segment",
          "description": "Use the bold semantic surface.",
          "token": "--cds-bg-bold"
        },
        {
          "label": "Active label",
          "description": "Use text intended for bold surfaces.",
          "token": "--cds-text-on-bold"
        },
        {
          "label": "Inactive label",
          "description": "Use primary text emphasis.",
          "token": "--cds-text-primary"
        },
        {
          "label": "Inactive border",
          "description": "Use the subtle surface stroke.",
          "token": "--cds-surface-subtle"
        }
      ]
    },
    "states": [
      {
        "id": "active",
        "label": "Active",
        "description": "Bold fill with on-bold text, marked by aria-current."
      },
      {
        "id": "inactive",
        "label": "Inactive",
        "description": "Transparent fill with a subtle border."
      },
      {
        "id": "disabled",
        "label": "Disabled",
        "description": "One option dimmed and blocked through the native disabled attribute."
      },
      {
        "id": "with-count",
        "label": "With count",
        "description": "Shows a trailing number after the label."
      },
      {
        "id": "with-dot",
        "label": "With dot",
        "description": "Shows a trailing attention indicator."
      }
    ],
    "behavior": [
      {
        "label": "Select on press",
        "description": "Invoke onSelect with the pressed option id. Skip it when that option is already active."
      },
      {
        "label": "Per-option disable",
        "description": "An option may be disabled on its own through the native attribute, without disabling the set."
      },
      {
        "label": "Bounded set",
        "description": "Keep the set to 5 or fewer options, per the iOS guideline. Past that, reach for a Chip group or a Select."
      }
    ],
    "accessibility": [
      {
        "label": "Not a radiogroup",
        "description": "A radiogroup needs radio semantics on every option, role=radio with aria-checked, plus radio-group arrow-key behavior. These are ordinary buttons, so the selected one carries aria-current."
      },
      {
        "label": "Not a tablist",
        "description": "A tablist promises content panels and arrow-key traversal. Reach for Tabs when the options switch panels."
      },
      {
        "label": "Explicit list role",
        "description": "The list keeps role=list because removing the bullets strips list semantics in some engines."
      },
      {
        "label": "Group name",
        "description": "Pass ariaLabel so the set is announced before its options."
      },
      {
        "label": "Counts over dots",
        "description": "A count is text and announces. A dot is decorative, so say why it is there in the label when it carries meaning."
      }
    ],
    "contentAdaptation": [
      {
        "label": "Option count",
        "description": "Keep the set to two through four mutually exclusive modes."
      },
      {
        "label": "Concise labels",
        "description": "Use parallel, short labels that remain legible at equal widths."
      },
      {
        "label": "Width sizing",
        "description": "sizing='fill' (default) shares the strip equally and ellipsises a label that does not fit. sizing='hug' sizes each option to its own label, so an auto-width strip is never narrower than its content; use it when the strip sits beside other content rather than filling a row."
      }
    ],
    "motionHaptics": {
      "motion": [
        {
          "label": "Selection treatment",
          "description": "Swap active and inactive color treatments without moving or resizing segments."
        }
      ],
      "haptics": [
        {
          "label": "Selection feedback",
          "description": "Request the selection haptic after the active segment changes."
        }
      ]
    },
    "platformDifferences": [
      {
        "platforms": [
          "web"
        ],
        "description": "Web renders no focus ring and no arrow-key traversal between options.",
        "rationale": "The consumers are a touch iOS WKWebView and a harness previewing it, and per Primer arrow keys must not move selection in a segmented control anyway."
      }
    ]
  },
  "spec_source": "packages/design-system/src/components/controls/SegmentedControl/SegmentedControl.spec.json",
  "guidance_source": {
    "path": "packages/design-system/src/components/controls/SegmentedControl/SegmentedControl.mdx",
    "code": "import { Meta, Canvas, Controls } from '@storybook/addon-docs/blocks';\nimport { ComponentSpec } from '../../../../.storybook/blocks';\nimport * as Stories from './SegmentedControl.stories';\nimport segmentedControlSpec from './SegmentedControl.spec.json';\n\n<Meta of={Stories} />\n\n# SegmentedControl\n\nAn iOS-style single-select switch for up to 5 mutually exclusive modes that re-filter one view in\nplace, such as a time range or a chart granularity.\n\n<Canvas of={Stories.Playground} />\n\n## It is a list of buttons\n\nThe markup is a `<ul>` of buttons, and the selected one carries `aria-current`. It is deliberately\nnone of the roles it resembles:\n\n-   Not a **radiogroup**. That role needs radio semantics on every option — `role=\"radio\"` with\n    `aria-checked` — plus radio-group arrow-key behavior. These are ordinary buttons.\n-   Not a **tablist**. That role promises content panels and arrow-key traversal.\n-   Not a **toolbar**. That role is a collection of function buttons.\n\nEach of those roles carries its own keyboard and announcement rules, so borrowing the wrong one\nmisleads anyone using assistive technology.\n\n## When to use\n\nReach for `SegmentedControl` when a small, fixed set of options changes how the same content is\nsliced and all options fit inline. Choosing between the neighbours:\n\n-   The options switch between **distinct content panels** rather than re-filtering one view? Use\n    `Tabs`.\n-   The set **grows from data**, wraps, or scrolls? Use a `ChipGroup`. The boundary is the shape of\n    the set: a segmented control is fixed, connected, and equal-width.\n-   A **binary input**, such as on and off? Use `Toggle`.\n-   You want a **labelled form field** with descriptions, or more than 5 options? Use `RadioGroup`.\n-   Too many options to sit inline? Use `Dropdown` + `Menu`.\n\n<Canvas of={Stories.MiddleActive} />\n\n## Counts and dots\n\nAn option may carry a trailing `count`, a decorative `dot`, or its own `disabled` state. A count is\ntext, so it is announced with the label. A dot is not, so put the reason it is there into the label\nwhen it carries meaning.\n\n<Canvas of={Stories.WithCountsAndDots} />\n\n## Design Guidelines\n\n<ComponentSpec spec={segmentedControlSpec} />\n\n## Props\n\n<Controls of={Stories.Playground} />\n"
  },
  "props": [
    {
      "name": "items",
      "type": "SegmentedControlItem[]",
      "declared_type": "SegmentedControlItem[]",
      "required": true,
      "description": "Options rendered in order. Keep the set to 5 or fewer, per the iOS guideline.",
      "source": {
        "path": "packages/design-system/src/components/controls/SegmentedControl/SegmentedControl.tsx",
        "line": 38
      }
    },
    {
      "name": "activeId",
      "type": "string",
      "declared_type": "string",
      "required": true,
      "description": "Controlled `id` of the active option. An id matching no item leaves none active.",
      "source": {
        "path": "packages/design-system/src/components/controls/SegmentedControl/SegmentedControl.tsx",
        "line": 40
      }
    },
    {
      "name": "onSelect",
      "type": "((id: string) => void) | undefined",
      "declared_type": "(id: string) => void",
      "required": false,
      "description": "Called with the selected option id. Skipped when the pressed option is already active.",
      "source": {
        "path": "packages/design-system/src/components/controls/SegmentedControl/SegmentedControl.tsx",
        "line": 42
      }
    },
    {
      "name": "ariaLabel",
      "type": "string | undefined",
      "declared_type": "string",
      "required": false,
      "description": "Accessible name for the strip, announced before the options — such as `Time range`.",
      "source": {
        "path": "packages/design-system/src/components/controls/SegmentedControl/SegmentedControl.tsx",
        "line": 44
      }
    },
    {
      "name": "className",
      "type": "string | undefined",
      "declared_type": "string",
      "required": false,
      "description": "",
      "source": {
        "path": "packages/design-system/src/components/controls/SegmentedControl/SegmentedControl.tsx",
        "line": 45
      }
    },
    {
      "name": "haptic",
      "type": "HapticFeedback | undefined",
      "declared_type": "HapticFeedback",
      "required": false,
      "description": "Haptic feedback for selection changes. Defaults to `true` (`selection`).",
      "source": {
        "path": "packages/design-system/src/components/controls/SegmentedControl/SegmentedControl.tsx",
        "line": 47
      },
      "values": [
        "error",
        "heavy",
        "light",
        "medium",
        "selection",
        "success",
        "warning",
        false,
        true
      ],
      "values_complete": true,
      "default": "true",
      "default_kind": "source-expression"
    },
    {
      "name": "sizing",
      "type": "SegmentedControlSizing | undefined",
      "declared_type": "SegmentedControlSizing",
      "required": false,
      "description": "How options claim width. `fill` (default) shares the strip equally and\nellipsises labels that do not fit; `hug` sizes each option to its own\nlabel, for a strip that should be no wider than its content.",
      "source": {
        "path": "packages/design-system/src/components/controls/SegmentedControl/SegmentedControl.tsx",
        "line": 53
      },
      "values": [
        "fill",
        "hug"
      ],
      "values_complete": true,
      "default": "'fill'",
      "default_kind": "source-expression"
    },
    {
      "name": "testId",
      "type": "string | undefined",
      "declared_type": "string",
      "required": false,
      "description": "Optional test selector. Each option derives `${testId}-${id}`.",
      "source": {
        "path": "packages/design-system/src/components/controls/SegmentedControl/SegmentedControl.tsx",
        "line": 55
      }
    }
  ],
  "prop_shapes": {},
  "examples": [],
  "tokens": [
    {
      "name": "--cds-size-32",
      "light": "32px",
      "dark": "32px"
    },
    {
      "name": "--cds-space-16",
      "light": "16px",
      "dark": "16px"
    },
    {
      "name": "--cds-space-4",
      "light": "4px",
      "dark": "4px"
    },
    {
      "name": "--cds-space-8",
      "light": "8px",
      "dark": "8px"
    },
    {
      "name": "--cds-size-8",
      "light": "8px",
      "dark": "8px"
    },
    {
      "name": "--cds-type-link-size",
      "light": "0.75rem",
      "dark": "0.75rem"
    },
    {
      "name": "--cds-bg-bold",
      "light": "#0b0b0a",
      "dark": "#ffffff"
    },
    {
      "name": "--cds-text-on-bold",
      "light": "#ffffff",
      "dark": "#141410"
    },
    {
      "name": "--cds-text-primary",
      "light": "#141410",
      "dark": "#f3f1ed"
    },
    {
      "name": "--cds-surface-subtle",
      "light": "#e0ddd6",
      "dark": "#ffffff26"
    }
  ],
  "visual_reference": {
    "stories": [
      {
        "id": "cloaked-design-system-controls-segmentedcontrol--playground",
        "name": "Playground",
        "title": "Cloaked Design System/Controls/SegmentedControl",
        "exportName": "Playground",
        "source_path": "packages/design-system/src/components/controls/SegmentedControl/SegmentedControl.stories.tsx",
        "source_digest": "121530dc43e7a965963a481371b64c84b32f04b9f8aacf842582749471006a28",
        "status": "preview",
        "local_url": "/storybook/iframe.html?id=cloaked-design-system-controls-segmentedcontrol--playground&viewMode=story"
      },
      {
        "id": "cloaked-design-system-controls-segmentedcontrol--middle-active",
        "name": "Middle Active",
        "title": "Cloaked Design System/Controls/SegmentedControl",
        "exportName": "MiddleActive",
        "source_path": "packages/design-system/src/components/controls/SegmentedControl/SegmentedControl.stories.tsx",
        "source_digest": "121530dc43e7a965963a481371b64c84b32f04b9f8aacf842582749471006a28",
        "status": "preview",
        "local_url": "/storybook/iframe.html?id=cloaked-design-system-controls-segmentedcontrol--middle-active&viewMode=story"
      },
      {
        "id": "cloaked-design-system-controls-segmentedcontrol--with-counts-and-dots",
        "name": "With Counts And Dots",
        "title": "Cloaked Design System/Controls/SegmentedControl",
        "exportName": "WithCountsAndDots",
        "source_path": "packages/design-system/src/components/controls/SegmentedControl/SegmentedControl.stories.tsx",
        "source_digest": "121530dc43e7a965963a481371b64c84b32f04b9f8aacf842582749471006a28",
        "status": "preview",
        "local_url": "/storybook/iframe.html?id=cloaked-design-system-controls-segmentedcontrol--with-counts-and-dots&viewMode=story"
      },
      {
        "id": "cloaked-design-system-controls-segmentedcontrol--dark-theme",
        "name": "Dark Theme",
        "title": "Cloaked Design System/Controls/SegmentedControl",
        "exportName": "DarkTheme",
        "source_path": "packages/design-system/src/components/controls/SegmentedControl/SegmentedControl.stories.tsx",
        "source_digest": "121530dc43e7a965963a481371b64c84b32f04b9f8aacf842582749471006a28",
        "status": "preview",
        "local_url": "/storybook/iframe.html?id=cloaked-design-system-controls-segmentedcontrol--dark-theme&viewMode=story"
      }
    ],
    "name": "SegmentedControl",
    "id": "segmentedcontrol",
    "category": "Controls"
  },
  "stories": [
    {
      "id": "cloaked-design-system-controls-segmentedcontrol--playground",
      "name": "Playground",
      "title": "Cloaked Design System/Controls/SegmentedControl",
      "exportName": "Playground",
      "source_path": "packages/design-system/src/components/controls/SegmentedControl/SegmentedControl.stories.tsx",
      "source_digest": "121530dc43e7a965963a481371b64c84b32f04b9f8aacf842582749471006a28",
      "status": "preview",
      "local_url": "/storybook/iframe.html?id=cloaked-design-system-controls-segmentedcontrol--playground&viewMode=story"
    },
    {
      "id": "cloaked-design-system-controls-segmentedcontrol--middle-active",
      "name": "Middle Active",
      "title": "Cloaked Design System/Controls/SegmentedControl",
      "exportName": "MiddleActive",
      "source_path": "packages/design-system/src/components/controls/SegmentedControl/SegmentedControl.stories.tsx",
      "source_digest": "121530dc43e7a965963a481371b64c84b32f04b9f8aacf842582749471006a28",
      "status": "preview",
      "local_url": "/storybook/iframe.html?id=cloaked-design-system-controls-segmentedcontrol--middle-active&viewMode=story"
    },
    {
      "id": "cloaked-design-system-controls-segmentedcontrol--with-counts-and-dots",
      "name": "With Counts And Dots",
      "title": "Cloaked Design System/Controls/SegmentedControl",
      "exportName": "WithCountsAndDots",
      "source_path": "packages/design-system/src/components/controls/SegmentedControl/SegmentedControl.stories.tsx",
      "source_digest": "121530dc43e7a965963a481371b64c84b32f04b9f8aacf842582749471006a28",
      "status": "preview",
      "local_url": "/storybook/iframe.html?id=cloaked-design-system-controls-segmentedcontrol--with-counts-and-dots&viewMode=story"
    },
    {
      "id": "cloaked-design-system-controls-segmentedcontrol--dark-theme",
      "name": "Dark Theme",
      "title": "Cloaked Design System/Controls/SegmentedControl",
      "exportName": "DarkTheme",
      "source_path": "packages/design-system/src/components/controls/SegmentedControl/SegmentedControl.stories.tsx",
      "source_digest": "121530dc43e7a965963a481371b64c84b32f04b9f8aacf842582749471006a28",
      "status": "preview",
      "local_url": "/storybook/iframe.html?id=cloaked-design-system-controls-segmentedcontrol--dark-theme&viewMode=story"
    }
  ],
  "stories_source": {
    "path": "packages/design-system/src/components/controls/SegmentedControl/SegmentedControl.stories.tsx",
    "code": "import React, { useState } from 'react';\nimport type { Meta, StoryObj } from '@storybook/react-vite';\nimport { disabledControl, hapticArgType } from '../../../storybook/controlOptions';\nimport { SegmentedControl, type SegmentedControlItem } from './SegmentedControl';\n\nconst SEGMENT_ITEMS: SegmentedControlItem[] = [\n    { id: 'week', label: 'Week' },\n    { id: 'month', label: 'Month' },\n    { id: 'year', label: 'Year' },\n];\n\nconst meta: Meta<typeof SegmentedControl> = {\n    title: 'Cloaked Design System/Controls/SegmentedControl',\n    component: SegmentedControl,\n    parameters: {\n        a11y: { test: 'error' },\n        docs: {\n            description: {\n                component:\n                    'An iOS-style single-select switch for up to 5 mutually exclusive view modes that re-filter ' +\n                    'the same view, such as a time range or a chart granularity. The markup is a list of ' +\n                    'buttons carrying `aria-current`, deliberately not a radiogroup (which needs radio ' +\n                    'semantics and radio-group arrow keys) and not a tablist (which promises content panels). ' +\n                    'Use `Tabs` when the options switch ' +\n                    'panels, a `ChipGroup` when the set grows from data, and `Toggle` for a binary input.',\n            },\n        },\n    },\n    args: {\n        items: SEGMENT_ITEMS,\n        activeId: 'week',\n        ariaLabel: 'Time range',\n        testId: 'controls-segmented',\n    },\n    argTypes: {\n        items: { ...disabledControl, description: 'Options rendered in order; each is a `SegmentedControlItem`.' },\n        activeId: { control: 'text', description: 'Controlled: `id` of the selected option.' },\n        onSelect: { ...disabledControl, description: 'Called with the selected option `id`.' },\n        ariaLabel: { control: 'text', description: 'Accessible name announced before the options.' },\n        haptic: {\n            ...hapticArgType,\n            description: 'Haptic on selection change. Defaults to `true` (`selection`).',\n        },\n    },\n};\nexport default meta;\ntype Story = StoryObj<typeof SegmentedControl>;\n\nfunction SegmentedControlPlayground() {\n    const [activeId, setActiveId] = useState(SEGMENT_ITEMS[0].id);\n\n    return (\n        <SegmentedControl\n            items={SEGMENT_ITEMS}\n            activeId={activeId}\n            onSelect={setActiveId}\n            ariaLabel='Time range'\n            testId='controls-segmented'\n        />\n    );\n}\n\nexport const Playground: Story = {\n    parameters: { docs: { description: { story: 'Interactive — `onSelect` drives `activeId` through React state.' } } },\n    render: () => <SegmentedControlPlayground />,\n};\n\nexport const MiddleActive: Story = {\n    args: { activeId: 'month' },\n    parameters: { docs: { description: { story: 'A non-first option selected.' } } },\n};\n\nexport const WithCountsAndDots: Story = {\n    parameters: {\n        docs: {\n            description: {\n                story:\n                    'A count is text, so it is announced with the label. A dot is decorative, so put the ' +\n                    'reason it is there into the label when it carries meaning.',\n            },\n        },\n    },\n    args: {\n        activeId: 'approved',\n        ariaLabel: 'Request state',\n        items: [\n            { id: 'approved', label: 'Approved', count: 12 },\n            { id: 'requests', label: 'Requests', dot: true },\n            { id: 'blocked', label: 'Blocked', count: 0, disabled: true },\n        ],\n    },\n};\n\nexport const DarkTheme: Story = {\n    globals: { theme: 'dark' },\n    parameters: { docs: { description: { story: 'Segmented control on the dark theme surface.' } } },\n};\n"
  },
  "links": {
    "human": "/component-segmentedcontrol",
    "json": "/data/components/segmentedcontrol.json",
    "markdown": "/data/components/segmentedcontrol.md",
    "storybook": "/storybook/?path=/story/cloaked-design-system-controls-segmentedcontrol--playground"
  },
  "contract_status": "preview",
  "contract_kind": "source-derived",
  "approval": "required",
  "api": {
    "method": "typescript-type-checker",
    "typescript_version": "5.9.3",
    "public_export": "SegmentedControl",
    "public_path": "SegmentedControl",
    "import_path": "@cloakedinc/design-system",
    "root_kind": "component",
    "entrypoints": [
      {
        "name": "SegmentedControl",
        "signature": 1,
        "visibility": "public",
        "source": {
          "path": "packages/design-system/src/components/controls/SegmentedControl/SegmentedControl.tsx",
          "line": 63
        },
        "props_type": "SegmentedControlProps",
        "props": [
          {
            "name": "items",
            "type": "SegmentedControlItem[]",
            "declared_type": "SegmentedControlItem[]",
            "required": true,
            "description": "Options rendered in order. Keep the set to 5 or fewer, per the iOS guideline.",
            "source": {
              "path": "packages/design-system/src/components/controls/SegmentedControl/SegmentedControl.tsx",
              "line": 38
            }
          },
          {
            "name": "activeId",
            "type": "string",
            "declared_type": "string",
            "required": true,
            "description": "Controlled `id` of the active option. An id matching no item leaves none active.",
            "source": {
              "path": "packages/design-system/src/components/controls/SegmentedControl/SegmentedControl.tsx",
              "line": 40
            }
          },
          {
            "name": "onSelect",
            "type": "((id: string) => void) | undefined",
            "declared_type": "(id: string) => void",
            "required": false,
            "description": "Called with the selected option id. Skipped when the pressed option is already active.",
            "source": {
              "path": "packages/design-system/src/components/controls/SegmentedControl/SegmentedControl.tsx",
              "line": 42
            }
          },
          {
            "name": "ariaLabel",
            "type": "string | undefined",
            "declared_type": "string",
            "required": false,
            "description": "Accessible name for the strip, announced before the options — such as `Time range`.",
            "source": {
              "path": "packages/design-system/src/components/controls/SegmentedControl/SegmentedControl.tsx",
              "line": 44
            }
          },
          {
            "name": "className",
            "type": "string | undefined",
            "declared_type": "string",
            "required": false,
            "description": "",
            "source": {
              "path": "packages/design-system/src/components/controls/SegmentedControl/SegmentedControl.tsx",
              "line": 45
            }
          },
          {
            "name": "haptic",
            "type": "HapticFeedback | undefined",
            "declared_type": "HapticFeedback",
            "required": false,
            "description": "Haptic feedback for selection changes. Defaults to `true` (`selection`).",
            "source": {
              "path": "packages/design-system/src/components/controls/SegmentedControl/SegmentedControl.tsx",
              "line": 47
            },
            "values": [
              "error",
              "heavy",
              "light",
              "medium",
              "selection",
              "success",
              "warning",
              false,
              true
            ],
            "values_complete": true,
            "default": "true",
            "default_kind": "source-expression"
          },
          {
            "name": "sizing",
            "type": "SegmentedControlSizing | undefined",
            "declared_type": "SegmentedControlSizing",
            "required": false,
            "description": "How options claim width. `fill` (default) shares the strip equally and\nellipsises labels that do not fit; `hug` sizes each option to its own\nlabel, for a strip that should be no wider than its content.",
            "source": {
              "path": "packages/design-system/src/components/controls/SegmentedControl/SegmentedControl.tsx",
              "line": 53
            },
            "values": [
              "fill",
              "hug"
            ],
            "values_complete": true,
            "default": "'fill'",
            "default_kind": "source-expression"
          },
          {
            "name": "testId",
            "type": "string | undefined",
            "declared_type": "string",
            "required": false,
            "description": "Optional test selector. Each option derives `${testId}-${id}`.",
            "source": {
              "path": "packages/design-system/src/components/controls/SegmentedControl/SegmentedControl.tsx",
              "line": 55
            }
          }
        ],
        "branches": [],
        "constraints": [],
        "extraction_status": "extracted"
      }
    ],
    "type_sources": [
      {
        "path": "packages/design-system/src/components/controls/SegmentedControl/SegmentedControl.tsx",
        "line": 11,
        "name": "SegmentedControlItem",
        "code": "export interface SegmentedControlItem {\n    /** Stable option id. Also suffixes the derived test selector. */\n    id: string;\n    /** Visible option label. Keep it short: segments share the width equally. */\n    label: string;\n    /** Trailing count, rendered as text so it is announced with the label. */\n    count?: number;\n    /** Attention indicator. Decorative, so say why it is there in the label when it carries meaning. */\n    dot?: boolean;\n    /** Blocks this one option through the native attribute, without disabling the set. */\n    disabled?: boolean;\n    /** Overrides the selector derived from `testId` and `id`. */\n    testId?: string;\n}"
      },
      {
        "path": "packages/design-system/src/components/controls/SegmentedControl/SegmentedControl.tsx",
        "line": 30,
        "name": "SegmentedControlSizing",
        "code": "export type SegmentedControlSizing = 'fill' | 'hug';"
      },
      {
        "path": "packages/design-system/src/components/controls/SegmentedControl/SegmentedControl.tsx",
        "line": 36,
        "name": "SegmentedControlProps",
        "code": "export interface SegmentedControlProps {\n    /** Options rendered in order. Keep the set to 5 or fewer, per the iOS guideline. */\n    items: SegmentedControlItem[];\n    /** Controlled `id` of the active option. An id matching no item leaves none active. */\n    activeId: string;\n    /** Called with the selected option id. Skipped when the pressed option is already active. */\n    onSelect?: (id: string) => void;\n    /** Accessible name for the strip, announced before the options — such as `Time range`. */\n    ariaLabel?: string;\n    className?: string;\n    /** Haptic feedback for selection changes. Defaults to `true` (`selection`). */\n    haptic?: HapticFeedback;\n    /**\n     * How options claim width. `fill` (default) shares the strip equally and\n     * ellipsises labels that do not fit; `hug` sizes each option to its own\n     * label, for a strip that should be no wider than its content.\n     */\n    sizing?: SegmentedControlSizing;\n    /** Optional test selector. Each option derives `${testId}-${id}`. */\n    testId?: string;\n}"
      },
      {
        "path": "packages/design-system/src/tokens/CdsHaptic.ts",
        "line": 6,
        "name": "HapticFeedback",
        "code": "export type HapticFeedback = boolean | HapticType;"
      },
      {
        "path": "packages/design-system/src/tokens/HapticType.ts",
        "line": 2,
        "name": "HapticType",
        "code": "export type HapticType = 'light' | 'medium' | 'heavy' | 'success' | 'warning' | 'error' | 'selection';"
      }
    ],
    "defaults_policy": "Only explicit destructured parameter initializers are extracted. Other runtime defaults remain in implementation source."
  },
  "platforms": {
    "implemented": [
      "web"
    ],
    "native": "Imported design guidance only; no native implementation or parity verification in this preview.",
    "react": {
      "implementation": "local",
      "status": "preview",
      "parity": "web-reference"
    },
    "vue": {
      "implementation": "absent",
      "parity": "not-verified",
      "status": "reference-only"
    },
    "swift": {
      "implementation": "absent",
      "parity": "not-verified",
      "status": "reference-only"
    },
    "kotlin": {
      "implementation": "absent",
      "parity": "not-verified",
      "status": "reference-only"
    }
  },
  "missing_facts": [
    {
      "code": "human-review-pending",
      "detail": "This local preview has no recorded human design or engineering sign-off."
    },
    {
      "code": "platform-parity-unverified",
      "detail": "Vue, Swift and Kotlin implementations are absent from this package; platform parity has not been verified."
    },
    {
      "code": "figma-mapping-unverified",
      "detail": "No current component-level Figma node and immutable revision have been verified for this contract."
    }
  ],
  "review": {
    "local_status": "not-reviewed",
    "source_approval": null,
    "imported_spec_status": "approved",
    "spec_origin": "imported-adapted",
    "local_spec_source_status": "approved",
    "imported_source_revision": "f96f416e1c3bfbfaffcd155a88b87b18b8bb25f1",
    "note": "Inherited specification status is provenance, not approval of the current local implementation or extracted contract."
  }
}
