@grafana/ui
Version:
Grafana Components Library
1 lines • 3.96 kB
Source Map (JSON)
{"version":3,"file":"Portal.cjs","sources":["../../../../src/components/Portal/Portal.tsx"],"sourcesContent":["import { css } from '@emotion/css';\nimport { createContext, type PropsWithChildren, useContext, useRef } from 'react';\nimport ReactDOM from 'react-dom';\n\nimport { type GrafanaTheme2 } from '@grafana/data';\nimport { selectors } from '@grafana/e2e-selectors';\n\nimport { useStyles2, useTheme2 } from '../../themes/ThemeContext';\n\n// Tracks whether we're in a nested Portal.\n// A nested portal will render inside it's parent portal instead of the default portal container.\n// This prevents nested portals from being rendered in reverse order due to React's bottom-up commit phase.\nconst PortalContext = createContext<HTMLDivElement | null>(null);\n\ninterface Props {\n className?: string;\n root?: HTMLElement;\n // the zIndex of the node; defaults to theme.zIndex.portal\n zIndex?: number;\n}\n\nexport function Portal(props: PropsWithChildren<Props>) {\n const { children, className, root, zIndex } = props;\n const theme = useTheme2();\n const parentPortal = useContext(PortalContext);\n const portalRef = useRef<HTMLDivElement | null>(null);\n\n const portalRoot = root ?? parentPortal ?? getPortalContainer();\n\n return ReactDOM.createPortal(\n <PortalContext.Provider value={portalRef.current}>\n <div\n className={className}\n ref={portalRef}\n style={{ position: 'relative', zIndex: zIndex ?? theme.zIndex.portal }}\n >\n {children}\n </div>\n </PortalContext.Provider>,\n portalRoot\n );\n}\n\n/**\n * Returns the container element Grafana renders overlays into (tooltips, menus, modals).\n * Content rendered into this container coexists with an open Modal: it is treated as\n * inside the modal for focus management and pressing it does not dismiss the modal.\n */\nexport function getPortalContainer() {\n return window.document.getElementById('grafana-portal-container') ?? document.body;\n}\n\n/** @internal */\nexport function PortalContainer() {\n const styles = useStyles2(getStyles);\n return (\n <div\n id=\"grafana-portal-container\"\n data-testid={selectors.components.Portal.container}\n className={styles.grafanaPortalContainer}\n />\n );\n}\n\nconst getStyles = (theme: GrafanaTheme2) => {\n return {\n grafanaPortalContainer: css({\n position: 'fixed',\n top: 0,\n width: '100%',\n zIndex: theme.zIndex.portal,\n }),\n };\n};\n"],"names":["createContext","useTheme2","useContext","useRef","ReactDOM","jsx","useStyles2","selectors","css"],"mappings":";;;;;;;;;;;;;;;;AAYA,MAAM,aAAA,GAAgBA,oBAAqC,IAAI,CAAA;AASxD,SAAS,OAAO,KAAA,EAAiC;AArBxD,EAAA,IAAA,EAAA;AAsBE,EAAA,MAAM,EAAE,QAAA,EAAU,SAAA,EAAW,IAAA,EAAM,QAAO,GAAI,KAAA;AAC9C,EAAA,MAAM,QAAQC,sBAAA,EAAU;AACxB,EAAA,MAAM,YAAA,GAAeC,iBAAW,aAAa,CAAA;AAC7C,EAAA,MAAM,SAAA,GAAYC,aAA8B,IAAI,CAAA;AAEpD,EAAA,MAAM,UAAA,GAAA,CAAa,EAAA,GAAA,IAAA,IAAA,IAAA,GAAA,IAAA,GAAQ,YAAA,KAAR,IAAA,GAAA,EAAA,GAAwB,kBAAA,EAAmB;AAE9D,EAAA,OAAOC,yBAAA,CAAS,YAAA;AAAA,mCACb,aAAA,CAAc,QAAA,EAAd,EAAuB,KAAA,EAAO,UAAU,OAAA,EACvC,QAAA,kBAAAC,cAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,SAAA;AAAA,QACA,GAAA,EAAK,SAAA;AAAA,QACL,KAAA,EAAO,EAAE,QAAA,EAAU,UAAA,EAAY,QAAQ,MAAA,IAAA,IAAA,GAAA,MAAA,GAAU,KAAA,CAAM,OAAO,MAAA,EAAO;AAAA,QAEpE;AAAA;AAAA,KACH,EACF,CAAA;AAAA,IACA;AAAA,GACF;AACF;AAOO,SAAS,kBAAA,GAAqB;AAhDrC,EAAA,IAAA,EAAA;AAiDE,EAAA,OAAA,CAAO,YAAO,QAAA,CAAS,cAAA,CAAe,0BAA0B,CAAA,KAAzD,YAA8D,QAAA,CAAS,IAAA;AAChF;AAGO,SAAS,eAAA,GAAkB;AAChC,EAAA,MAAM,MAAA,GAASC,wBAAW,SAAS,CAAA;AACnC,EAAA,uBACED,cAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,EAAA,EAAG,0BAAA;AAAA,MACH,aAAA,EAAaE,sBAAA,CAAU,UAAA,CAAW,MAAA,CAAO,SAAA;AAAA,MACzC,WAAW,MAAA,CAAO;AAAA;AAAA,GACpB;AAEJ;AAEA,MAAM,SAAA,GAAY,CAAC,KAAA,KAAyB;AAC1C,EAAA,OAAO;AAAA,IACL,wBAAwBC,OAAA,CAAI;AAAA,MAC1B,QAAA,EAAU,OAAA;AAAA,MACV,GAAA,EAAK,CAAA;AAAA,MACL,KAAA,EAAO,MAAA;AAAA,MACP,MAAA,EAAQ,MAAM,MAAA,CAAO;AAAA,KACtB;AAAA,GACH;AACF,CAAA;;;;;;"}