{"slug":"context-menu","name":"ContextMenu","packageName":"@liquidify/react","category":"presentational","maturity":"experimental","summary":"An invisible wrapper around arbitrary children (the \"source\").","importPath":"@liquidify/react/context-menu","sourcePath":"packages/@liquidify/react/src/components/context-menu/context-menu.tsx","specPath":"packages/@liquidify/react/src/components/context-menu/spec.md","docsPath":"../liquidify-page/content/component-source/context-menu.md","documentationStatus":"draft","storyIds":["presentational-context-menu--default"],"additionalExports":["ContextMenuItem","ContextMenuItemRole","ContextMenuPlacement","PresentationalProps"],"anatomy":[{"name":"The source","description":"The source — children, wrapped invisibly via"},{"name":"The items","description":"The items — items: ContextMenuItem[] where each item carries a stable"},{"name":"The preview (optional)","description":"The preview (optional) — when preview is supplied, the lifted source"},{"name":"is the portalled glass panel","description":"is the portalled glass panel"},{"name":".lq-context-menu-items","description":".lq-context-menu-items — the vertical list of MenuRow items"}],"props":[{"name":"aria-label","type":"string | undefined","required":false,"description":"Accessible name of the panel (`role=\"menu\"`). Defaults to `\"Context menu\"`.","declaredIn":"packages/@liquidify/react/src/components/context-menu/context-menu.tsx"},{"name":"children","type":"ReactNode","required":true,"description":"The source (`children` role: `trigger`) — wrapped invisibly, wired with the three `useLongPress` open gestures.","declaredIn":"packages/@liquidify/react/src/components/context-menu/context-menu.tsx"},{"name":"className","type":"string | undefined","required":false,"description":"Optional additional CSS class merged into the glass surface class. The supported per-instance customisation seam: override any `--lq-*` custom property from this class's CSS — the token cascade the engine reads (ADR-0004). No raw inline style is involved, so this stays ADR-0003 clean.","declaredIn":"packages/@liquidify/react/src/liquid-component-props.ts"},{"name":"defaultOpen","type":"boolean | undefined","required":false,"description":"Uncontrolled seed for the open state (default `false`).","declaredIn":"packages/@liquidify/react/src/components/context-menu/context-menu.tsx"},{"name":"items","type":"readonly ContextMenuItem[]","required":true,"description":"The menu's action rows; each carries a stable `id` (§16.6).","declaredIn":"packages/@liquidify/react/src/components/context-menu/context-menu.tsx"},{"name":"labelProps","type":"LabelProps | undefined","required":false,"description":"Per-instance typography override spread last into every item's label (ADR-0028), applied uniformly over all rows.","declaredIn":"packages/@liquidify/react/src/components/context-menu/context-menu.tsx"},{"name":"material","type":"\"frosted\" | \"regular\" | \"clear\" | undefined","required":false,"description":"Material of the glass surface — `frosted · regular · clear` (§06). Forwarded to the engine, which resolves the chain `prop ?? <Material default> ?? \"regular\"`, so a bare surface inherits the enclosing provider default.","defaultValue":"enclosing `<Material default>`, else `\"regular\"`","declaredIn":"packages/@liquidify/react/src/liquid-component-props.ts"},{"name":"onOpenChange","type":"((open: boolean) => void) | undefined","required":false,"description":"Fires with the next open state on every open/close path.","declaredIn":"packages/@liquidify/react/src/components/context-menu/context-menu.tsx"},{"name":"open","type":"boolean | undefined","required":false,"description":"Controlled open state. Passing it switches ContextMenu to controlled mode.","declaredIn":"packages/@liquidify/react/src/components/context-menu/context-menu.tsx"},{"name":"perf","type":"\"low\" | \"high\" | undefined","required":false,"description":"Perf budget of the glass surface (ADR-0006/0007). Forwarded to the engine as `data-glass-perf`; `\"high\"` opts this surface into the edge-lensing filter. When omitted, derives from the enclosing `<Backdrop type>` (§04).","defaultValue":"derived from `<Backdrop type>`, else `\"low\"`","declaredIn":"packages/@liquidify/react/src/liquid-component-props.ts"},{"name":"placement","type":"LogicalSide | undefined","required":false,"description":"Which side of the source the panel sits on. Forwarded to Popup. Defaults to `\"bottom\"`.","declaredIn":"packages/@liquidify/react/src/components/context-menu/context-menu.tsx"},{"name":"preview","type":"(() => ReactNode) | undefined","required":false,"description":"Render-prop; when supplied, replaces the lifted source while the menu is open.","declaredIn":"packages/@liquidify/react/src/components/context-menu/context-menu.tsx"},{"name":"ref","type":"Ref<HTMLDivElement> | undefined","required":false,"description":"Forwarded ref to the rendered element — the component's native root (or the merged child element on a component that opts into {@link SlottableProps}). Generic over `E` (charter `docs/api-conventions.md` §4 rule 1, default `HTMLElement`) so a component with a polymorphic root (Link's navigation / action mode union, Text's `as`) can instantiate the concrete element per variant instead of leaving every consumer with a widened `Ref<HTMLElement>`. A component that does not narrow simply inherits the `HTMLElement` default — source-compatible with every pre-existing non-generic `extends ControlProps`.","declaredIn":"packages/@liquidify/react/src/liquid-component-props.ts"}],"swiftUIMap":[{"swiftUI":"(the modified view)","react":"`children: ReactNode`","notes":"the source, wrapped invisibly via `Popup.Trigger asChild`","relationship":"direct"},{"swiftUI":"`menuItems: () -> View` (Buttons)","react":"`items: ContextMenuItem[]`","notes":"`{ id, label, icon?, role?, onPress?, disabled? }`; each row carries a required stable `id` (§16.6 — rows key on `id`, duplicate `id` is a dev error); activation runs `onPress` then closes","relationship":"direct"},{"swiftUI":"`.tag(_:)` / `id` (collection identity)","react":"`id: string` (required, per item)","notes":"consumer-supplied stable identity; the roving cursor + reconciliation key on it","relationship":"direct"},{"swiftUI":"`Button`'s `Label(_:systemImage:)`","react":"`icon?: ReactNode`","notes":"leading icon slot per item","relationship":"direct"},{"swiftUI":"`Button(role: .destructive)`","react":"`role: \"destructive\"`","notes":"red label + icon (`--lq-color-action-destructive`)","relationship":"direct"},{"swiftUI":"`preview: () -> Preview`","react":"`preview?: () => ReactNode`","notes":"render-prop; when present, replaces the lifted source while open","relationship":"direct"},{"swiftUI":"(long-press gesture, implicit)","react":"(built in — long-press / right-click / keyboard)","notes":"no gesture prop; `useLongPress` wires all three trigger paths","relationship":"direct"},{"swiftUI":"`isPresented`-equivalent (implicit)","react":"`open` / `defaultOpen` / `onOpenChange`","notes":"presentation triad (ADR-0015), forwarded to `Popup`","relationship":"direct"},{"swiftUI":"(menu anchor side)","react":"`placement?: PopupPlacement`","notes":"default `\"bottom\"`; forwarded to `Popup` — the panel sits on that source edge","relationship":"direct"},{"swiftUI":"(presentation chrome)","react":"`material?` / `perf?` / `className?`","notes":"`PresentationalProps` (ADR-0023) — the panel's glass surface (default `frosted`); the base is tint-free (P0.B6 — **no `tint` prop**; `tint=` type-rejects; items take the role palette)","relationship":"direct"},{"swiftUI":"(menu accessible name)","react":"`aria-label?: string`","notes":"the panel's `role=\"menu\"` accessible name; default `\"Context menu\"`, forwarded to `Popup.Content`","relationship":"direct"},{"swiftUI":"(per-instance typography)","react":"`labelProps?: LabelProps`","notes":"charter §2 seam — `Pick<TextProps, \"variant\"|\"weight\"|\"color\"|\"tint\"|\"tracking\"|\"transform\"|\"italic\"|\"lineHeight\"|\"wrap\">` spread last into every item's `TextLabel` (ADR-0028), applied uniformly over all rows (rule 4)","relationship":"direct"},{"swiftUI":"(no SwiftUI analogue)","react":"`ref?`","notes":"forwarded to the **source host** `.lq-context-menu-source` (the invisible wrapper), typed `Ref<HTMLDivElement>`, always attached (open or closed) — charter §4 (concrete element, semantic-root policy)","relationship":"web-only"}],"tokens":["--lq-color-action-destructive","--lq-color-label-primary-","--lq-context-menu-lift-scale","--lq-context-menu-min-width","--lq-context-menu-press-duration","--lq-duration-","--lq-duration-quick","--lq-easing-","--lq-easing-standard","--lq-font-size-body","--lq-glass-","--lq-popup-","--lq-popup-bloom-scale","--lq-radius-14","--lq-radius-8","--lq-shadow-presentational","--lq-space-12","--lq-space-2","--lq-space-20","--lq-space-8"],"keyboard":[{"key":"Keyboard","action":"Keyboard — Enter, Space, or the ContextMenu key while the source is focused opens immediately."}],"variants":[],"variantPages":[]}