@grafana/ui
Version:
Grafana Components Library
1 lines • 9.44 kB
Source Map (JSON)
{"version":3,"file":"DataLinksContextMenu.mjs","sources":["../../../../src/components/DataLinks/DataLinksContextMenu.tsx"],"sourcesContent":["import { css } from '@emotion/css';\nimport { type CSSProperties, type JSX } from 'react';\nimport * as React from 'react';\n\nimport { type ActionModel, type GrafanaTheme2, type LinkModel } from '@grafana/data';\nimport { selectors } from '@grafana/e2e-selectors';\n\nimport { useStyles2 } from '../../themes/ThemeContext';\nimport { linkModelToContextMenuItems } from '../../utils/dataLinks';\nimport { type OpenMenuTrigger, WithContextMenu } from '../ContextMenu/WithContextMenu';\nimport { MenuGroup, type MenuItemsGroup } from '../Menu/MenuGroup';\nimport { MenuItem } from '../Menu/MenuItem';\n\nexport interface DataLinksContextMenuProps {\n children: (props: DataLinksContextMenuApi) => JSX.Element;\n links: () => LinkModel[];\n style?: CSSProperties;\n /**\n * @deprecated Will be removed in a future version\n */\n actions?: ActionModel[];\n}\n\n/**\n * Props consumers spread on the focusable element that opens the data-links menu.\n * Intentionally `Partial<...>` of the underlying handler types so the props can\n * be applied to either an HTML element (`<button>`) or an SVG element (`<g>`,\n * `<a>`) without TS gymnastics on the consumer side.\n *\n * Use these for the multi-link case to get keyboard accessibility (Enter/Space\n * to open the menu) and the correct ARIA semantics for free.\n */\nexport interface DataLinksMenuTriggerProps {\n role: 'button';\n tabIndex: 0;\n 'aria-haspopup': 'menu';\n onClick: (event: React.MouseEvent<Element>) => void;\n onKeyDown: (event: React.KeyboardEvent<Element>) => void;\n}\n\nexport interface DataLinksContextMenuApi {\n /**\n * Opens the menu, anchored to either a pointer event, an `Element`, or an\n * explicit `{x, y}`. Most consumers should prefer spreading `triggerProps`\n * onto a focusable element instead of wiring `openMenu` manually.\n */\n openMenu?: (trigger: OpenMenuTrigger) => void;\n targetClassName?: string;\n /**\n * Spread onto the focusable trigger element to make the menu keyboard- and\n * screen-reader-accessible (Enter/Space to open, `role=\"button\"`,\n * `aria-haspopup=\"menu\"`). Only present when the menu has more than one\n * link — for a single link the component renders a real `<a>` itself.\n */\n triggerProps?: DataLinksMenuTriggerProps;\n}\n\nexport const DataLinksContextMenu = ({ children, links, style }: DataLinksContextMenuProps) => {\n const styles = useStyles2(getStyles);\n\n const itemsGroup: MenuItemsGroup[] = [\n { items: linkModelToContextMenuItems(links), label: Boolean(links().length) ? 'Data links' : '' },\n ];\n\n const linksCounter = itemsGroup[0].items.length;\n const renderMenuGroupItems = () => {\n return itemsGroup.map((group, groupIdx) => (\n <MenuGroup key={`${group.label}${groupIdx}`} label={group.label}>\n {(group.items || []).map((item, itemIdx) => (\n <MenuItem\n key={`${group.label}-${groupIdx}-${itemIdx}}`}\n url={item.url}\n label={item.label}\n target={item.target}\n icon={item.icon}\n active={item.active}\n onClick={item.onClick}\n className={styles.itemWrapper}\n />\n ))}\n </MenuGroup>\n ));\n };\n\n // Class consumers can apply to their click target purely as a visual \"this\n // opens a context menu\" affordance (cursor only). Behaviour is exposed\n // separately via `openMenu` / `triggerProps`.\n const targetClassName = styles.menuTarget;\n\n if (linksCounter > 1) {\n return (\n <WithContextMenu renderMenuItems={renderMenuGroupItems}>\n {({ openMenu }) => {\n // Keyboard activation — anchor the menu to the trigger element\n // itself instead of fabricating a synthetic MouseEvent. This works\n // identically for HTML and SVG focusable elements.\n const triggerProps: DataLinksMenuTriggerProps = {\n role: 'button',\n tabIndex: 0,\n 'aria-haspopup': 'menu',\n onClick: (e) => openMenu(e),\n onKeyDown: (e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n openMenu(e.currentTarget);\n }\n },\n };\n return children({ openMenu, targetClassName, triggerProps });\n }}\n </WithContextMenu>\n );\n } else if (linksCounter === 1) {\n const linkModel = links()[0];\n return (\n <a\n href={linkModel.href}\n onClick={linkModel.onClick}\n target={linkModel.target}\n title={linkModel.title}\n // Layout shorthands stay inline for back-compat — BarGauge/Gauge pass\n // `style={{ flexGrow: 1 }}`/`{ height: '100%' }` and rely on the\n // wrapper being a flex container. The `:focus-visible` ring lives in\n // the themed class so every consumer gains a focus indicator without\n // opting in (this is what fixes the keyboard-accessibility gap).\n style={{ ...style, overflow: 'hidden', display: 'flex' }}\n className={styles.singleLink}\n data-testid={selectors.components.DataLinksContextMenu.singleLink}\n >\n {children({})}\n </a>\n );\n }\n\n // No links — render children plainly. This guards a long-standing latent\n // crash: `hasLinks` and a `getLinks` supplier come from independent code\n // paths (`hasLinks(field)` checks `field.config.links.length` while the\n // supplier is created by `applyFieldOverrides`), so consumers can land here\n // with `getLinks() === []`. Returning `children({})` mirrors what the panel\n // would have rendered without the wrapper.\n return children({});\n};\n\nconst getStyles = (theme: GrafanaTheme2) => ({\n itemWrapper: css({\n fontSize: 12,\n }),\n // The single-link path used to ship only inline styles; the `:focus-visible`\n // ring is the one piece of polish that was missing and the reason panels\n // were re-implementing this branch themselves. Layout shorthands stay\n // inline (see component) so this class is purely visual.\n singleLink: css({\n color: 'inherit',\n textDecoration: 'none',\n borderRadius: theme.shape.radius.default,\n '&:focus-visible': {\n outline: `2px solid ${theme.colors.primary.main}`,\n outlineOffset: '2px',\n },\n }),\n menuTarget: css({\n cursor: 'context-menu',\n borderRadius: theme.shape.radius.default,\n '&:focus-visible': {\n outline: `2px solid ${theme.colors.primary.main}`,\n outlineOffset: '2px',\n },\n }),\n});\n"],"names":[],"mappings":";;;;;;;;;;AAyDO,MAAM,uBAAuB,CAAC,EAAE,QAAA,EAAU,KAAA,EAAO,OAAM,KAAiC;AAC7F,EAAA,MAAM,MAAA,GAAS,WAAW,SAAS,CAAA;AAEnC,EAAA,MAAM,UAAA,GAA+B;AAAA,IACnC,EAAE,KAAA,EAAO,2BAAA,CAA4B,KAAK,CAAA,EAAG,KAAA,EAAO,OAAA,CAAQ,KAAA,EAAM,CAAE,MAAM,CAAA,GAAI,YAAA,GAAe,EAAA;AAAG,GAClG;AAEA,EAAA,MAAM,YAAA,GAAe,UAAA,CAAW,CAAC,CAAA,CAAE,KAAA,CAAM,MAAA;AACzC,EAAA,MAAM,uBAAuB,MAAM;AACjC,IAAA,OAAO,WAAW,GAAA,CAAI,CAAC,KAAA,EAAO,QAAA,yBAC3B,SAAA,EAAA,EAA4C,KAAA,EAAO,KAAA,CAAM,KAAA,EACtD,iBAAM,KAAA,IAAS,IAAI,GAAA,CAAI,CAAC,MAAM,OAAA,qBAC9B,GAAA;AAAA,MAAC,QAAA;AAAA,MAAA;AAAA,QAEC,KAAK,IAAA,CAAK,GAAA;AAAA,QACV,OAAO,IAAA,CAAK,KAAA;AAAA,QACZ,QAAQ,IAAA,CAAK,MAAA;AAAA,QACb,MAAM,IAAA,CAAK,IAAA;AAAA,QACX,QAAQ,IAAA,CAAK,MAAA;AAAA,QACb,SAAS,IAAA,CAAK,OAAA;AAAA,QACd,WAAW,MAAA,CAAO;AAAA,OAAA;AAAA,MAPb,GAAG,KAAA,CAAM,KAAK,CAAA,CAAA,EAAI,QAAQ,IAAI,OAAO,CAAA,CAAA;AAAA,KAS7C,KAZa,CAAA,EAAG,KAAA,CAAM,KAAK,CAAA,EAAG,QAAQ,EAazC,CACD,CAAA;AAAA,EACH,CAAA;AAKA,EAAA,MAAM,kBAAkB,MAAA,CAAO,UAAA;AAE/B,EAAA,IAAI,eAAe,CAAA,EAAG;AACpB,IAAA,2BACG,eAAA,EAAA,EAAgB,eAAA,EAAiB,sBAC/B,QAAA,EAAA,CAAC,EAAE,UAAS,KAAM;AAIjB,MAAA,MAAM,YAAA,GAA0C;AAAA,QAC9C,IAAA,EAAM,QAAA;AAAA,QACN,QAAA,EAAU,CAAA;AAAA,QACV,eAAA,EAAiB,MAAA;AAAA,QACjB,OAAA,EAAS,CAAC,CAAA,KAAM,QAAA,CAAS,CAAC,CAAA;AAAA,QAC1B,SAAA,EAAW,CAAC,CAAA,KAAM;AAChB,UAAA,IAAI,CAAA,CAAE,GAAA,KAAQ,OAAA,IAAW,CAAA,CAAE,QAAQ,GAAA,EAAK;AACtC,YAAA,CAAA,CAAE,cAAA,EAAe;AACjB,YAAA,QAAA,CAAS,EAAE,aAAa,CAAA;AAAA,UAC1B;AAAA,QACF;AAAA,OACF;AACA,MAAA,OAAO,QAAA,CAAS,EAAE,QAAA,EAAU,eAAA,EAAiB,cAAc,CAAA;AAAA,IAC7D,CAAA,EACF,CAAA;AAAA,EAEJ,CAAA,MAAA,IAAW,iBAAiB,CAAA,EAAG;AAC7B,IAAA,MAAM,SAAA,GAAY,KAAA,EAAM,CAAE,CAAC,CAAA;AAC3B,IAAA,uBACE,GAAA;AAAA,MAAC,GAAA;AAAA,MAAA;AAAA,QACC,MAAM,SAAA,CAAU,IAAA;AAAA,QAChB,SAAS,SAAA,CAAU,OAAA;AAAA,QACnB,QAAQ,SAAA,CAAU,MAAA;AAAA,QAClB,OAAO,SAAA,CAAU,KAAA;AAAA,QAMjB,OAAO,EAAE,GAAG,OAAO,QAAA,EAAU,QAAA,EAAU,SAAS,MAAA,EAAO;AAAA,QACvD,WAAW,MAAA,CAAO,UAAA;AAAA,QAClB,aAAA,EAAa,SAAA,CAAU,UAAA,CAAW,oBAAA,CAAqB,UAAA;AAAA,QAEtD,QAAA,EAAA,QAAA,CAAS,EAAE;AAAA;AAAA,KACd;AAAA,EAEJ;AAQA,EAAA,OAAO,QAAA,CAAS,EAAE,CAAA;AACpB;AAEA,MAAM,SAAA,GAAY,CAAC,KAAA,MAA0B;AAAA,EAC3C,aAAa,GAAA,CAAI;AAAA,IACf,QAAA,EAAU;AAAA,GACX,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAKD,YAAY,GAAA,CAAI;AAAA,IACd,KAAA,EAAO,SAAA;AAAA,IACP,cAAA,EAAgB,MAAA;AAAA,IAChB,YAAA,EAAc,KAAA,CAAM,KAAA,CAAM,MAAA,CAAO,OAAA;AAAA,IACjC,iBAAA,EAAmB;AAAA,MACjB,OAAA,EAAS,CAAA,UAAA,EAAa,KAAA,CAAM,MAAA,CAAO,QAAQ,IAAI,CAAA,CAAA;AAAA,MAC/C,aAAA,EAAe;AAAA;AACjB,GACD,CAAA;AAAA,EACD,YAAY,GAAA,CAAI;AAAA,IACd,MAAA,EAAQ,cAAA;AAAA,IACR,YAAA,EAAc,KAAA,CAAM,KAAA,CAAM,MAAA,CAAO,OAAA;AAAA,IACjC,iBAAA,EAAmB;AAAA,MACjB,OAAA,EAAS,CAAA,UAAA,EAAa,KAAA,CAAM,MAAA,CAAO,QAAQ,IAAI,CAAA,CAAA;AAAA,MAC/C,aAAA,EAAe;AAAA;AACjB,GACD;AACH,CAAA,CAAA;;;;"}