{
  "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": "menu",
  "name": "Menu",
  "category": "Controls",
  "summary": "A compact row list that switches between one-off action semantics and controlled single-selection semantics.",
  "documentation": {
    "summary": "A compact row list that switches between one-off action semantics and controlled single-selection semantics.",
    "guidance": [
      {
        "heading": "Overview",
        "body": "The option-list **body** for bottom sheets, popovers, and dropdown content — the rows, not the\ntrigger. `selectionMode` switches its meaning: `single` is a picker, `none` is an action list."
      },
      {
        "heading": "When to use",
        "body": "Reach for `Menu` whenever you need a list of rows inside an opened surface. Two modes:\n\n-   `selectionMode=\"single\"` renders a **picker** (role listbox/option, a check on the current `value`) — the body of a `Select` or `Dropdown` sheet.\n-   `selectionMode=\"none\"` renders an **action list** (role menu/menuitem) of one-off commands like Rename, Share, Delete. Items can be `disabled` or `destructive`.\n\nChoosing between the neighbors:\n\n-   Need the collapsed trigger that opens this list? That's `Dropdown` (or `Select` for a form value).\n-   Picking one value from a short fixed set inside a form? Use `Select`, which composes a `Menu` internally.\n-   Filtering a long list by typing? Use `Autocomplete`.\n\nIn-app it renders the activity-filter rows opened from the `Dropdown` in `ActivityFeed.tsx`."
      }
    ],
    "usage": [],
    "examples": [],
    "source": "packages/design-system/src/components/controls/Menu/Menu.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/Menu/Menu.tsx",
    "code": "import React from 'react';\nimport { cn } from '../../../utilities';\nimport { useHaptic } from '../../../hooks/useHaptic';\nimport type { HapticFeedback } from '../../../tokens/CdsHaptic';\nimport { Icon } from '../../primitives/Icon/Icon';\nimport { Text } from '../../primitives/Text/Text';\nimport styles from './Menu.module.scss';\n\nexport type MenuSelectionMode = 'none' | 'single';\n\n/**\n * One row of a `Menu`. The same shape serves both modes — a command in an action list, an option in\n * a picker — so nothing here picks the mode; `MenuProps.selectionMode` does.\n */\nexport interface MenuItem {\n    /** Emitted by `onSelect`, matched against `MenuProps.value`, and used as the React key. */\n    value: string;\n    /** Primary row text. A node, but keep it inline: it renders inside a `Text`. */\n    label: React.ReactNode;\n    /** Optional leading content, usually an `Icon`. */\n    leading?: React.ReactNode;\n    /** Second line under the label, for spelling out an action's consequence. */\n    description?: React.ReactNode;\n    /** Blocks the press and dims the row, so `onSelect` never fires for it. */\n    disabled?: boolean;\n    /** Applies danger text treatment for destructive actions. */\n    destructive?: boolean;\n    /** Optional test selector. Falls back to `${testId}-${value}`. */\n    testId?: string;\n}\n\n/**\n * Props for `Menu`. Selection is controlled: the menu marks `value` as selected and never tracks a\n * pick of its own.\n */\nexport interface MenuProps {\n    /** Rows in render order. Keep one meaning per menu: all commands, or all option values. */\n    items: MenuItem[];\n    /** Accessible name for the container; the trigger's label sits on a separate element. */\n    ariaLabel: string;\n    /** Selection semantics. Use `single` for picker-style menus. */\n    selectionMode?: MenuSelectionMode;\n    /** Current selected value when `selectionMode` is `single`. */\n    value?: string;\n    /** Called with the pressed row's `value`. Omitting it makes every row inert. */\n    onSelect?: (value: string) => void;\n    /** Haptic feedback for item presses. Defaults to `true` (`selection` for single-select, otherwise `light`). */\n    haptic?: HapticFeedback;\n    className?: string;\n    style?: React.CSSProperties;\n    /** Container selector, and the stem each row's selector is derived from. */\n    testId?: string;\n}\n\n/**\n * The option rows of a picker or action list, never the trigger. It owns no surface, so place it in\n * one the caller owns — the sheet a `Dropdown` opens, a modal, a popover.\n */\nexport function Menu({\n    items,\n    ariaLabel,\n    selectionMode = 'none',\n    value,\n    onSelect,\n    haptic = true,\n    className,\n    style,\n    testId,\n}: MenuProps) {\n    const fireHaptic = useHaptic(selectionMode === 'single' ? 'selection' : 'light');\n    const isSingleSelect = selectionMode === 'single';\n\n    return (\n        <div\n            className={cn(styles.menu, className)}\n            style={style}\n            role={isSingleSelect ? 'listbox' : 'menu'}\n            aria-label={ariaLabel}\n            data-testid={testId}\n        >\n            {items.map(item => {\n                const isSelected = isSingleSelect && item.value === value;\n\n                return (\n                    <button\n                        key={item.value}\n                        type='button'\n                        className={cn(\n                            styles.item,\n                            isSelected && styles.selected,\n                            item.destructive && styles.destructive\n                        )}\n                        disabled={item.disabled}\n                        role={isSingleSelect ? 'option' : 'menuitem'}\n                        aria-selected={isSingleSelect ? isSelected : undefined}\n                        onClick={() => {\n                            if (!onSelect) return;\n                            fireHaptic(haptic);\n                            onSelect(item.value);\n                        }}\n                        data-testid={item.testId ?? (testId ? `${testId}-${item.value}` : undefined)}\n                    >\n                        <span className={styles.content}>\n                            {item.leading && <span className={styles.leading}>{item.leading}</span>}\n                            <span className={styles.copy}>\n                                <Text\n                                    variant='body'\n                                    color={getLabelColor(item.destructive, isSelected)}\n                                    className={styles.label}\n                                >\n                                    {item.label}\n                                </Text>\n                                {item.description && (\n                                    <Text\n                                        variant='body-small'\n                                        color={isSelected ? 'inherit' : 'secondary'}\n                                        className={styles.description}\n                                    >\n                                        {item.description}\n                                    </Text>\n                                )}\n                            </span>\n                        </span>\n                        {isSelected && <Icon name='check' size='small' strokeWidth={2} className={styles.check} />}\n                    </button>\n                );\n            })}\n        </div>\n    );\n}\n\nfunction getLabelColor(destructive: boolean | undefined, selected: boolean) {\n    if (destructive) return 'danger';\n    if (selected) return 'inherit';\n    return 'primary';\n}\n"
  },
  "spec": {
    "$schema": "../../../specs/component-spec.schema.json",
    "identity": {
      "name": "Menu",
      "status": "preview",
      "version": "1.1.0",
      "summary": "A compact row list that switches between one-off action semantics and controlled single-selection semantics."
    },
    "parts": [
      {
        "id": "container",
        "label": "Container",
        "description": "Owns the menu or listbox role, accessible name, and row spacing."
      },
      {
        "id": "item",
        "label": "Item",
        "description": "Native button exposing action or option semantics and disabled state."
      },
      {
        "id": "leading",
        "label": "Leading visual",
        "description": "Optional icon or visual before item copy."
      },
      {
        "id": "label",
        "label": "Item label",
        "description": "Primary action or option name."
      },
      {
        "id": "description",
        "label": "Item description",
        "description": "Optional supporting copy beneath an action label."
      },
      {
        "id": "selection-mark",
        "label": "Selection mark",
        "description": "Trailing check shown only on the selected single-choice item."
      }
    ],
    "allowedDesignChoices": {
      "variants": {
        "base": {
          "label": "Base",
          "description": "One row treatment configured through the independent selection-mode axis."
        }
      },
      "sizes": {},
      "selectionModes": {
        "none": {
          "label": "Actions",
          "description": "Exposes menu and menuitem semantics for one-off commands."
        },
        "single": {
          "label": "Single selection",
          "description": "Exposes listbox and option semantics with one selected value."
        }
      }
    },
    "variants": [
      {
        "id": "base",
        "label": "Base",
        "description": "Compact menu rows whose role is selected through a separate semantic mode.",
        "platforms": [
          "ios",
          "android",
          "web"
        ]
      }
    ],
    "sizes": [],
    "measurements": [
      {
        "id": "container-gap",
        "label": "Item gap",
        "kind": "exact",
        "token": "--cds-space-4",
        "value": 4,
        "unit": "px",
        "appliesTo": [
          "container",
          "item"
        ]
      },
      {
        "id": "item-min-height",
        "label": "Item minimum height",
        "kind": "exact",
        "token": "--cds-size-48",
        "value": 48,
        "unit": "px",
        "appliesTo": [
          "item"
        ]
      },
      {
        "id": "item-horizontal-padding",
        "label": "Item horizontal padding",
        "kind": "exact",
        "token": "--cds-space-16",
        "value": 16,
        "unit": "px",
        "appliesTo": [
          "item"
        ]
      },
      {
        "id": "item-corner-radius",
        "label": "Item corner radius",
        "kind": "exact",
        "token": "--cds-radius-md",
        "value": 16,
        "unit": "px",
        "appliesTo": [
          "item"
        ]
      }
    ],
    "layout": {
      "alignment": "Leading visual and copy align at the row start while the selected check remains trailing.",
      "flow": "Rows stack vertically; each row contains optional leading visual, flexible copy, then optional selection mark.",
      "notes": [
        "Use action mode for commands and single-selection mode for values; do not mix both meanings in one menu.",
        "Place Menu inside an owned surface such as Modal or popover."
      ]
    },
    "typographyAndColor": {
      "typography": [
        {
          "label": "Item label",
          "description": "Use the body ramp for primary row labels.",
          "token": "--cds-type-body-size"
        },
        {
          "label": "Description",
          "description": "Use body-small for supporting copy.",
          "token": "--cds-type-body-small-size"
        }
      ],
      "colors": [
        {
          "label": "Selected row",
          "description": "Fill the selected single-choice row with the bold surface so selection reads as a high-contrast pill, consistent with other selected controls.",
          "token": "--cds-bg-bold"
        },
        {
          "label": "Selected row content",
          "description": "Flip the selected row's label and check to on-bold so they read on the bold fill.",
          "token": "--cds-text-on-bold"
        },
        {
          "label": "Primary label",
          "description": "Use primary text emphasis for normal actions and unselected options.",
          "token": "--cds-text-primary"
        },
        {
          "label": "Description and leading visual",
          "description": "Use secondary text emphasis.",
          "token": "--cds-text-secondary"
        },
        {
          "label": "Destructive action",
          "description": "Use the solid danger role only for irreversible actions.",
          "token": "--cds-status-danger-solid"
        },
        {
          "label": "Selection mark",
          "description": "Use on-bold for the selected check, which sits on the bold selected row.",
          "token": "--cds-text-on-bold"
        }
      ]
    },
    "states": [
      {
        "id": "default",
        "label": "Default",
        "description": "Shows an enabled normal item."
      },
      {
        "id": "selected",
        "label": "Selected",
        "description": "Fills the row with the bold surface and shows an on-bold check and option state in single-selection mode."
      },
      {
        "id": "focused",
        "label": "Focused",
        "description": "Shows a visible focus ring on the row button."
      },
      {
        "id": "disabled",
        "label": "Disabled",
        "description": "Blocks the item and dims its full contents."
      },
      {
        "id": "destructive",
        "label": "Destructive",
        "description": "Uses danger emphasis for an enabled irreversible action."
      }
    ],
    "behavior": [
      {
        "label": "Action mode",
        "description": "Emit the pressed value from menu items without maintaining selected state."
      },
      {
        "label": "Single-selection mode",
        "description": "Expose the controlled value as one selected option and emit a new value on press."
      },
      {
        "label": "Disabled items",
        "description": "Never emit selection or action callbacks from disabled rows."
      }
    ],
    "accessibility": [
      {
        "label": "Container semantics",
        "description": "Require an accessible name and expose menu for actions or listbox for single selection."
      },
      {
        "label": "Row semantics",
        "description": "Expose menuitem or option roles consistently with the container mode."
      },
      {
        "label": "Selection and disabled state",
        "description": "Expose selected state only in single-selection mode and preserve native disabled behavior."
      }
    ],
    "contentAdaptation": [
      {
        "label": "Long copy",
        "description": "Wrap labels and descriptions within the flexible copy column."
      },
      {
        "label": "Destructive labels",
        "description": "Name the affected object and action clearly; danger color does not replace explicit wording."
      }
    ],
    "motionHaptics": {
      "motion": [
        {
          "label": "Selection change",
          "description": "Update selected surface and check without changing row geometry."
        }
      ],
      "haptics": [
        {
          "label": "Mode-aware feedback",
          "description": "Request selection feedback for single-choice items and light feedback for action items."
        }
      ]
    },
    "platformDifferences": [
      {
        "platforms": [
          "web"
        ],
        "description": "Web maps modes to ARIA menu/menuitem or listbox/option roles; native platforms map the same distinction to action and selectable-row accessibility traits.",
        "rationale": "Semantic intent is shared while each platform exposes its native assistive-technology vocabulary."
      }
    ]
  },
  "spec_source": "packages/design-system/src/components/controls/Menu/Menu.spec.json",
  "guidance_source": {
    "path": "packages/design-system/src/components/controls/Menu/Menu.mdx",
    "code": "import { Meta, Canvas, Controls } from '@storybook/addon-docs/blocks';\nimport { ComponentSpec } from '../../../../.storybook/blocks';\nimport * as Stories from './Menu.stories';\nimport menuSpec from './Menu.spec.json';\n\n<Meta of={Stories} />\n\n# Menu\n\nThe option-list **body** for bottom sheets, popovers, and dropdown content — the rows, not the\ntrigger. `selectionMode` switches its meaning: `single` is a picker, `none` is an action list.\n\n<Canvas of={Stories.Playground} />\n\n## When to use\n\nReach for `Menu` whenever you need a list of rows inside an opened surface. Two modes:\n\n-   `selectionMode=\"single\"` renders a **picker** (role listbox/option, a check on the current `value`) — the body of a `Select` or `Dropdown` sheet.\n-   `selectionMode=\"none\"` renders an **action list** (role menu/menuitem) of one-off commands like Rename, Share, Delete. Items can be `disabled` or `destructive`.\n\nChoosing between the neighbors:\n\n-   Need the collapsed trigger that opens this list? That's `Dropdown` (or `Select` for a form value).\n-   Picking one value from a short fixed set inside a form? Use `Select`, which composes a `Menu` internally.\n-   Filtering a long list by typing? Use `Autocomplete`.\n\nIn-app it renders the activity-filter rows opened from the `Dropdown` in `ActivityFeed.tsx`.\n\n<Canvas of={Stories.ActionMenu} />\n\n## Design Guidelines\n\n<ComponentSpec spec={menuSpec} />\n\n## Props\n\n<Controls of={Stories.Playground} />\n"
  },
  "props": [
    {
      "name": "items",
      "type": "MenuItem[]",
      "declared_type": "MenuItem[]",
      "required": true,
      "description": "Rows in render order. Keep one meaning per menu: all commands, or all option values.",
      "source": {
        "path": "packages/design-system/src/components/controls/Menu/Menu.tsx",
        "line": 38
      }
    },
    {
      "name": "ariaLabel",
      "type": "string",
      "declared_type": "string",
      "required": true,
      "description": "Accessible name for the container; the trigger's label sits on a separate element.",
      "source": {
        "path": "packages/design-system/src/components/controls/Menu/Menu.tsx",
        "line": 40
      }
    },
    {
      "name": "selectionMode",
      "type": "MenuSelectionMode | undefined",
      "declared_type": "MenuSelectionMode",
      "required": false,
      "description": "Selection semantics. Use `single` for picker-style menus.",
      "source": {
        "path": "packages/design-system/src/components/controls/Menu/Menu.tsx",
        "line": 42
      },
      "values": [
        "none",
        "single"
      ],
      "values_complete": true,
      "default": "'none'",
      "default_kind": "source-expression"
    },
    {
      "name": "value",
      "type": "string | undefined",
      "declared_type": "string",
      "required": false,
      "description": "Current selected value when `selectionMode` is `single`.",
      "source": {
        "path": "packages/design-system/src/components/controls/Menu/Menu.tsx",
        "line": 44
      }
    },
    {
      "name": "onSelect",
      "type": "((value: string) => void) | undefined",
      "declared_type": "(value: string) => void",
      "required": false,
      "description": "Called with the pressed row's `value`. Omitting it makes every row inert.",
      "source": {
        "path": "packages/design-system/src/components/controls/Menu/Menu.tsx",
        "line": 46
      }
    },
    {
      "name": "haptic",
      "type": "HapticFeedback | undefined",
      "declared_type": "HapticFeedback",
      "required": false,
      "description": "Haptic feedback for item presses. Defaults to `true` (`selection` for single-select, otherwise `light`).",
      "source": {
        "path": "packages/design-system/src/components/controls/Menu/Menu.tsx",
        "line": 48
      },
      "values": [
        "error",
        "heavy",
        "light",
        "medium",
        "selection",
        "success",
        "warning",
        false,
        true
      ],
      "values_complete": true,
      "default": "true",
      "default_kind": "source-expression"
    },
    {
      "name": "className",
      "type": "string | undefined",
      "declared_type": "string",
      "required": false,
      "description": "",
      "source": {
        "path": "packages/design-system/src/components/controls/Menu/Menu.tsx",
        "line": 49
      }
    },
    {
      "name": "style",
      "type": "React.CSSProperties | undefined",
      "declared_type": "React.CSSProperties",
      "required": false,
      "description": "",
      "source": {
        "path": "packages/design-system/src/components/controls/Menu/Menu.tsx",
        "line": 50
      }
    },
    {
      "name": "testId",
      "type": "string | undefined",
      "declared_type": "string",
      "required": false,
      "description": "Container selector, and the stem each row's selector is derived from.",
      "source": {
        "path": "packages/design-system/src/components/controls/Menu/Menu.tsx",
        "line": 52
      }
    }
  ],
  "prop_shapes": {},
  "examples": [],
  "tokens": [
    {
      "name": "--cds-space-4",
      "light": "4px",
      "dark": "4px"
    },
    {
      "name": "--cds-size-48",
      "light": "48px",
      "dark": "48px"
    },
    {
      "name": "--cds-space-16",
      "light": "16px",
      "dark": "16px"
    },
    {
      "name": "--cds-radius-md",
      "light": "16px",
      "dark": "16px"
    },
    {
      "name": "--cds-type-body-size",
      "light": "1rem",
      "dark": "1rem"
    },
    {
      "name": "--cds-type-body-small-size",
      "light": "0.875rem",
      "dark": "0.875rem"
    },
    {
      "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-text-secondary",
      "light": "#646260",
      "dark": "#ffffff99"
    },
    {
      "name": "--cds-status-danger-solid",
      "light": "#b83100",
      "dark": "#b83100"
    }
  ],
  "visual_reference": {
    "stories": [
      {
        "id": "cloaked-design-system-controls-menu--playground",
        "name": "Playground",
        "title": "Cloaked Design System/Controls/Menu",
        "exportName": "Playground",
        "source_path": "packages/design-system/src/components/controls/Menu/Menu.stories.tsx",
        "source_digest": "a03b1877898a0a6c14e604f947a4216db5c419e904efdb54a01e7285d096e2ff",
        "status": "preview",
        "local_url": "/storybook/iframe.html?id=cloaked-design-system-controls-menu--playground&viewMode=story"
      },
      {
        "id": "cloaked-design-system-controls-menu--action-menu",
        "name": "Action Menu",
        "title": "Cloaked Design System/Controls/Menu",
        "exportName": "ActionMenu",
        "source_path": "packages/design-system/src/components/controls/Menu/Menu.stories.tsx",
        "source_digest": "a03b1877898a0a6c14e604f947a4216db5c419e904efdb54a01e7285d096e2ff",
        "status": "preview",
        "local_url": "/storybook/iframe.html?id=cloaked-design-system-controls-menu--action-menu&viewMode=story"
      },
      {
        "id": "cloaked-design-system-controls-menu--with-leading-icons",
        "name": "With Leading Icons",
        "title": "Cloaked Design System/Controls/Menu",
        "exportName": "WithLeadingIcons",
        "source_path": "packages/design-system/src/components/controls/Menu/Menu.stories.tsx",
        "source_digest": "a03b1877898a0a6c14e604f947a4216db5c419e904efdb54a01e7285d096e2ff",
        "status": "preview",
        "local_url": "/storybook/iframe.html?id=cloaked-design-system-controls-menu--with-leading-icons&viewMode=story"
      },
      {
        "id": "cloaked-design-system-controls-menu--with-dropdown-sheet",
        "name": "With Dropdown Sheet",
        "title": "Cloaked Design System/Controls/Menu",
        "exportName": "WithDropdownSheet",
        "source_path": "packages/design-system/src/components/controls/Menu/Menu.stories.tsx",
        "source_digest": "a03b1877898a0a6c14e604f947a4216db5c419e904efdb54a01e7285d096e2ff",
        "status": "preview",
        "local_url": "/storybook/iframe.html?id=cloaked-design-system-controls-menu--with-dropdown-sheet&viewMode=story"
      },
      {
        "id": "cloaked-design-system-controls-menu--dark-theme",
        "name": "Dark Theme",
        "title": "Cloaked Design System/Controls/Menu",
        "exportName": "DarkTheme",
        "source_path": "packages/design-system/src/components/controls/Menu/Menu.stories.tsx",
        "source_digest": "a03b1877898a0a6c14e604f947a4216db5c419e904efdb54a01e7285d096e2ff",
        "status": "preview",
        "local_url": "/storybook/iframe.html?id=cloaked-design-system-controls-menu--dark-theme&viewMode=story"
      }
    ],
    "name": "Menu",
    "id": "menu",
    "category": "Controls"
  },
  "stories": [
    {
      "id": "cloaked-design-system-controls-menu--playground",
      "name": "Playground",
      "title": "Cloaked Design System/Controls/Menu",
      "exportName": "Playground",
      "source_path": "packages/design-system/src/components/controls/Menu/Menu.stories.tsx",
      "source_digest": "a03b1877898a0a6c14e604f947a4216db5c419e904efdb54a01e7285d096e2ff",
      "status": "preview",
      "local_url": "/storybook/iframe.html?id=cloaked-design-system-controls-menu--playground&viewMode=story"
    },
    {
      "id": "cloaked-design-system-controls-menu--action-menu",
      "name": "Action Menu",
      "title": "Cloaked Design System/Controls/Menu",
      "exportName": "ActionMenu",
      "source_path": "packages/design-system/src/components/controls/Menu/Menu.stories.tsx",
      "source_digest": "a03b1877898a0a6c14e604f947a4216db5c419e904efdb54a01e7285d096e2ff",
      "status": "preview",
      "local_url": "/storybook/iframe.html?id=cloaked-design-system-controls-menu--action-menu&viewMode=story"
    },
    {
      "id": "cloaked-design-system-controls-menu--with-leading-icons",
      "name": "With Leading Icons",
      "title": "Cloaked Design System/Controls/Menu",
      "exportName": "WithLeadingIcons",
      "source_path": "packages/design-system/src/components/controls/Menu/Menu.stories.tsx",
      "source_digest": "a03b1877898a0a6c14e604f947a4216db5c419e904efdb54a01e7285d096e2ff",
      "status": "preview",
      "local_url": "/storybook/iframe.html?id=cloaked-design-system-controls-menu--with-leading-icons&viewMode=story"
    },
    {
      "id": "cloaked-design-system-controls-menu--with-dropdown-sheet",
      "name": "With Dropdown Sheet",
      "title": "Cloaked Design System/Controls/Menu",
      "exportName": "WithDropdownSheet",
      "source_path": "packages/design-system/src/components/controls/Menu/Menu.stories.tsx",
      "source_digest": "a03b1877898a0a6c14e604f947a4216db5c419e904efdb54a01e7285d096e2ff",
      "status": "preview",
      "local_url": "/storybook/iframe.html?id=cloaked-design-system-controls-menu--with-dropdown-sheet&viewMode=story"
    },
    {
      "id": "cloaked-design-system-controls-menu--dark-theme",
      "name": "Dark Theme",
      "title": "Cloaked Design System/Controls/Menu",
      "exportName": "DarkTheme",
      "source_path": "packages/design-system/src/components/controls/Menu/Menu.stories.tsx",
      "source_digest": "a03b1877898a0a6c14e604f947a4216db5c419e904efdb54a01e7285d096e2ff",
      "status": "preview",
      "local_url": "/storybook/iframe.html?id=cloaked-design-system-controls-menu--dark-theme&viewMode=story"
    }
  ],
  "stories_source": {
    "path": "packages/design-system/src/components/controls/Menu/Menu.stories.tsx",
    "code": "import React, { useState } from 'react';\nimport type { Meta, StoryObj } from '@storybook/react-vite';\nimport { BottomSheet } from '../../overlays/BottomSheet/BottomSheet';\nimport { Icon } from '../../primitives/Icon/Icon';\nimport { Dropdown } from '../Dropdown/Dropdown';\nimport { disabledControl, hapticArgType, selectArgType } from '../../../storybook/controlOptions';\nimport { Menu, type MenuItem, type MenuSelectionMode } from './Menu';\n\nconst MENU_SELECTION_MODES = ['none', 'single'] as const satisfies readonly MenuSelectionMode[];\n\nconst FILTER_ITEMS: MenuItem[] = [\n    { value: 'all', label: 'All activity' },\n    { value: 'in-progress', label: 'In progress' },\n    { value: 'completed', label: 'Completed' },\n];\n\nconst ACTION_ITEMS: MenuItem[] = [\n    {\n        value: 'rename',\n        label: 'Rename dashboard',\n        description: 'Update the visible dashboard title',\n        leading: <Icon name='settings' size='small' />,\n    },\n    {\n        value: 'share',\n        label: 'Share dashboard',\n        description: 'Send a read-only link',\n        leading: <Icon name='upload' size='small' />,\n    },\n    {\n        value: 'archive',\n        label: 'Archive dashboard',\n        description: 'Unavailable on the free plan',\n        disabled: true,\n        leading: <Icon name='circle-outline' size='small' />,\n    },\n    {\n        value: 'delete',\n        label: 'Delete dashboard',\n        destructive: true,\n        leading: <Icon name='circle-outline' size='small' />,\n    },\n];\n\nconst meta: Meta<typeof Menu> = {\n    title: 'Cloaked Design System/Controls/Menu',\n    component: Menu,\n    parameters: {\n        a11y: { test: 'error' },\n        docs: {\n            description: {\n                component:\n                    'The option-list body for bottom sheets, popovers, and dropdown content — the rows, not the ' +\n                    'trigger (that is `Dropdown`). `selectionMode=\"single\"` renders a picker (role listbox/option ' +\n                    'with a check on the current `value`); `selectionMode=\"none\"` renders an action list (role ' +\n                    'menu/menuitem) for one-off commands. Items can be `disabled` or `destructive`.',\n            },\n        },\n    },\n    args: {\n        ariaLabel: 'Activity filter',\n        items: FILTER_ITEMS,\n        selectionMode: 'single',\n        value: 'all',\n        testId: 'controls-menu',\n    },\n    argTypes: {\n        items: { ...disabledControl, description: 'Rows rendered in order; each is a `MenuItem`.' },\n        ariaLabel: { control: 'text', description: 'Accessible name for the menu/listbox.' },\n        selectionMode: {\n            ...selectArgType(MENU_SELECTION_MODES),\n            description: '`single` = picker (checkmark on `value`); `none` = action list of commands.',\n        },\n        value: { control: 'text', description: 'Selected item value when `selectionMode` is `single`.' },\n        onSelect: { ...disabledControl, description: 'Called with the pressed item’s `value`.' },\n        haptic: {\n            ...hapticArgType,\n            description: 'Haptic per press. Defaults to `true` (`selection` for single-select, else `light`).',\n        },\n    },\n};\nexport default meta;\ntype Story = StoryObj<typeof Menu>;\n\nfunction DropdownMenuSheetDemo() {\n    const [open, setOpen] = useState(false);\n    const [selected, setSelected] = useState(FILTER_ITEMS[0].value);\n    const selectedLabel = FILTER_ITEMS.find(item => item.value === selected)?.label ?? FILTER_ITEMS[0].label;\n\n    return (\n        <>\n            <Dropdown\n                label={String(selectedLabel)}\n                size='sm'\n                expanded={open}\n                onToggle={() => setOpen(true)}\n                testId='activity-filter'\n            />\n            <BottomSheet isOpen={open} onDismiss={() => setOpen(false)} size='small' testId='activity-filter-sheet'>\n                <BottomSheet.Header>\n                    <BottomSheet.Title>Filter activity</BottomSheet.Title>\n                </BottomSheet.Header>\n                <Menu\n                    ariaLabel='Filter activity'\n                    selectionMode='single'\n                    value={selected}\n                    items={FILTER_ITEMS}\n                    onSelect={value => {\n                        setSelected(value);\n                        setOpen(false);\n                    }}\n                    testId='activity-filter-menu'\n                />\n            </BottomSheet>\n        </>\n    );\n}\n\nexport const Playground: Story = {\n    parameters: {\n        docs: {\n            description: { story: 'Single-select picker: role listbox/option, checkmark on the current `value`.' },\n        },\n    },\n};\n\nexport const ActionMenu: Story = {\n    args: {\n        ariaLabel: 'Dashboard actions',\n        selectionMode: 'none',\n        items: ACTION_ITEMS,\n    },\n    parameters: {\n        docs: {\n            description: {\n                story: '`selectionMode=\"none\"` — a command list (role menu/menuitem) with a disabled item and a destructive action.',\n            },\n        },\n    },\n};\n\nexport const WithLeadingIcons: Story = {\n    args: {\n        items: [\n            { value: 'all', label: 'All activity', leading: <Icon name='search' size='small' /> },\n            { value: 'in-progress', label: 'In progress', leading: <Icon name='clock' size='small' /> },\n            { value: 'completed', label: 'Completed', leading: <Icon name='check' size='small' /> },\n        ],\n    },\n    parameters: { docs: { description: { story: 'Picker rows with `leading` icons for scannable categories.' } } },\n};\n\nexport const WithDropdownSheet: Story = {\n    parameters: {\n        docs: {\n            description: { story: 'The full trigger→sheet flow: a `Dropdown` opens this `Menu` in a `BottomSheet`.' },\n        },\n    },\n    render: () => <DropdownMenuSheetDemo />,\n};\n\nexport const DarkTheme: Story = {\n    globals: { theme: 'dark' },\n    parameters: { docs: { description: { story: 'Single-select picker on the dark theme surface.' } } },\n};\n"
  },
  "links": {
    "human": "/component-menu",
    "json": "/data/components/menu.json",
    "markdown": "/data/components/menu.md",
    "storybook": "/storybook/?path=/story/cloaked-design-system-controls-menu--playground"
  },
  "contract_status": "preview",
  "contract_kind": "source-derived",
  "approval": "required",
  "api": {
    "method": "typescript-type-checker",
    "typescript_version": "5.9.3",
    "public_export": "Menu",
    "public_path": "Menu",
    "import_path": "@cloakedinc/design-system",
    "root_kind": "component",
    "entrypoints": [
      {
        "name": "Menu",
        "signature": 1,
        "visibility": "public",
        "source": {
          "path": "packages/design-system/src/components/controls/Menu/Menu.tsx",
          "line": 59
        },
        "props_type": "MenuProps",
        "props": [
          {
            "name": "items",
            "type": "MenuItem[]",
            "declared_type": "MenuItem[]",
            "required": true,
            "description": "Rows in render order. Keep one meaning per menu: all commands, or all option values.",
            "source": {
              "path": "packages/design-system/src/components/controls/Menu/Menu.tsx",
              "line": 38
            }
          },
          {
            "name": "ariaLabel",
            "type": "string",
            "declared_type": "string",
            "required": true,
            "description": "Accessible name for the container; the trigger's label sits on a separate element.",
            "source": {
              "path": "packages/design-system/src/components/controls/Menu/Menu.tsx",
              "line": 40
            }
          },
          {
            "name": "selectionMode",
            "type": "MenuSelectionMode | undefined",
            "declared_type": "MenuSelectionMode",
            "required": false,
            "description": "Selection semantics. Use `single` for picker-style menus.",
            "source": {
              "path": "packages/design-system/src/components/controls/Menu/Menu.tsx",
              "line": 42
            },
            "values": [
              "none",
              "single"
            ],
            "values_complete": true,
            "default": "'none'",
            "default_kind": "source-expression"
          },
          {
            "name": "value",
            "type": "string | undefined",
            "declared_type": "string",
            "required": false,
            "description": "Current selected value when `selectionMode` is `single`.",
            "source": {
              "path": "packages/design-system/src/components/controls/Menu/Menu.tsx",
              "line": 44
            }
          },
          {
            "name": "onSelect",
            "type": "((value: string) => void) | undefined",
            "declared_type": "(value: string) => void",
            "required": false,
            "description": "Called with the pressed row's `value`. Omitting it makes every row inert.",
            "source": {
              "path": "packages/design-system/src/components/controls/Menu/Menu.tsx",
              "line": 46
            }
          },
          {
            "name": "haptic",
            "type": "HapticFeedback | undefined",
            "declared_type": "HapticFeedback",
            "required": false,
            "description": "Haptic feedback for item presses. Defaults to `true` (`selection` for single-select, otherwise `light`).",
            "source": {
              "path": "packages/design-system/src/components/controls/Menu/Menu.tsx",
              "line": 48
            },
            "values": [
              "error",
              "heavy",
              "light",
              "medium",
              "selection",
              "success",
              "warning",
              false,
              true
            ],
            "values_complete": true,
            "default": "true",
            "default_kind": "source-expression"
          },
          {
            "name": "className",
            "type": "string | undefined",
            "declared_type": "string",
            "required": false,
            "description": "",
            "source": {
              "path": "packages/design-system/src/components/controls/Menu/Menu.tsx",
              "line": 49
            }
          },
          {
            "name": "style",
            "type": "React.CSSProperties | undefined",
            "declared_type": "React.CSSProperties",
            "required": false,
            "description": "",
            "source": {
              "path": "packages/design-system/src/components/controls/Menu/Menu.tsx",
              "line": 50
            }
          },
          {
            "name": "testId",
            "type": "string | undefined",
            "declared_type": "string",
            "required": false,
            "description": "Container selector, and the stem each row's selector is derived from.",
            "source": {
              "path": "packages/design-system/src/components/controls/Menu/Menu.tsx",
              "line": 52
            }
          }
        ],
        "branches": [],
        "constraints": [],
        "extraction_status": "extracted"
      }
    ],
    "type_sources": [
      {
        "path": "packages/design-system/src/components/controls/Menu/Menu.tsx",
        "line": 9,
        "name": "MenuSelectionMode",
        "code": "export type MenuSelectionMode = 'none' | 'single';"
      },
      {
        "path": "packages/design-system/src/components/controls/Menu/Menu.tsx",
        "line": 15,
        "name": "MenuItem",
        "code": "export interface MenuItem {\n    /** Emitted by `onSelect`, matched against `MenuProps.value`, and used as the React key. */\n    value: string;\n    /** Primary row text. A node, but keep it inline: it renders inside a `Text`. */\n    label: React.ReactNode;\n    /** Optional leading content, usually an `Icon`. */\n    leading?: React.ReactNode;\n    /** Second line under the label, for spelling out an action's consequence. */\n    description?: React.ReactNode;\n    /** Blocks the press and dims the row, so `onSelect` never fires for it. */\n    disabled?: boolean;\n    /** Applies danger text treatment for destructive actions. */\n    destructive?: boolean;\n    /** Optional test selector. Falls back to `${testId}-${value}`. */\n    testId?: string;\n}"
      },
      {
        "path": "packages/design-system/src/components/controls/Menu/Menu.tsx",
        "line": 36,
        "name": "MenuProps",
        "code": "export interface MenuProps {\n    /** Rows in render order. Keep one meaning per menu: all commands, or all option values. */\n    items: MenuItem[];\n    /** Accessible name for the container; the trigger's label sits on a separate element. */\n    ariaLabel: string;\n    /** Selection semantics. Use `single` for picker-style menus. */\n    selectionMode?: MenuSelectionMode;\n    /** Current selected value when `selectionMode` is `single`. */\n    value?: string;\n    /** Called with the pressed row's `value`. Omitting it makes every row inert. */\n    onSelect?: (value: string) => void;\n    /** Haptic feedback for item presses. Defaults to `true` (`selection` for single-select, otherwise `light`). */\n    haptic?: HapticFeedback;\n    className?: string;\n    style?: React.CSSProperties;\n    /** Container selector, and the stem each row's selector is derived from. */\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."
  }
}
