UNPKG

@atlaskit/navigation-system

Version:

The latest navigation system for Atlassian apps.

223 lines (222 loc) • 10.9 kB
/* chat-panel.tsx generated by @compiled/babel-plugin v3.0.2 */ import "./chat-panel.compiled.css"; import * as React from 'react'; import { ax, ix } from "@compiled/react/runtime"; import { useCallback, useEffect, useRef, useState } from 'react'; import { bind } from 'bind-event-listener'; import { fg } from '@atlaskit/platform-feature-flags/fg'; import { useSkipLinkInternal } from '../../context/skip-links/use-skip-link-internal'; import { chatPanelLiveWidthVar, chatPanelVar, defaultMainMinWidth, localSlotLayers, mainMinimumWidthVar } from './constants'; import { convertResizeBoundToPixels } from './panel-splitter/convert-resize-bound-to-pixels'; import { PanelSplitterProvider } from './panel-splitter/provider'; import { resolveLayoutWidth } from './resolve-layout-width'; import { gridRootId } from './root'; import { useLayoutAreaSizing } from './use-layout-area-sizing'; import { useLayoutId } from './use-layout-id'; import { useResizingWidthCssVarOnRootElement } from './use-resizing-width-css-var-on-root-element'; import { useSafeDefaultWidth } from './use-safe-default-width'; const chatPanelPanelSplitterId = Symbol('ChatPanel PanelSplitter'); const panelSplitterResizingVar = '--n_cPnlRsz'; const chatPanelOverlayWidthVar = '--n_cPnlOverlayW'; const fallbackDefaultWidth = 400; const fallbackCompactDefaultWidth = 320; const fallbackMinWidth = '320px'; const inlineMediaQuery = '(min-width: 40rem)'; function getPixelCustomProperty(element, propertyName, fallback) { const raw = getComputedStyle(element).getPropertyValue(propertyName).trim(); const value = Number.parseFloat(raw); return Number.isFinite(value) ? resolveLayoutWidth(raw.endsWith('vw') ? `${value}vw` : value, window.innerWidth) : fallback; } const styles = { root: "_nd5lquub _10fph9n0 _vchhusvi _p12f1osq _1pbyqfx8 _4t3i56xv _kqsw1if8 _152t1nws _bfhk1bhr _1bsb1ky5 _qhdr1ym0 _m5h51wug _1ttbvuon _1mxh8rg1", managedInline: "_12xt1ym0 _qo371wug _16hgvuon _w1ap8rg1", managedOverlay: "_12xtquub _qo37qfx8 _16hg1bhr _w1ap1ky5", scrollContainer: "_1reo1wug _18m91wug _4t3i1osq", border: "_191wglyw _16qs130s _19dmia51 _1ao7glyw", managedInlineBorder: "_i8luia51 _so1eglyw", managedOverlayBorder: "_i8luglyw _so1e130s" }; /** * The ChatPanel layout area is rendered below the banner and to the right (inline end) of the app. * * Below 40rem it overlays the app. At 40rem and above it is rendered as the final * full-height column in the page layout grid. * * You can optionally render a `PanelSplitter` as a child to make the chat panel resizable. */ export function ChatPanel({ children, defaultWidth: defaultWidthProp, minWidth = fallbackMinWidth, maxWidth, label = 'Chat panel', skipLinkLabel = label, testId, id: providedId, xcss, hasBorder = true, onClose }) { var _managedSizing$mode, _managedSizing$width; const id = useLayoutId({ providedId }); const defaultWidth = useSafeDefaultWidth({ defaultWidthProp: defaultWidthProp !== null && defaultWidthProp !== void 0 ? defaultWidthProp : fallbackDefaultWidth, fallbackDefaultWidth, slotName: 'ChatPanel' }); const compactDefaultWidth = defaultWidthProp === undefined ? fallbackCompactDefaultWidth : defaultWidth; useSkipLinkInternal({ id, label: skipLinkLabel, isHidden: defaultWidth === 0 || fg('platform_dst_nav4_skip_link_a11y_1') }); const ref = useRef(null); const [inlineWidth, setInlineWidth] = useState(defaultWidth); const [overlayWidth, setOverlayWidth] = useState(compactDefaultWidth); const [isInline, setIsInline] = useState(false); const { isManaged, prepareResize, state: managedSizing, getResizeBounds: getManagedResizeBounds, startResize, resize, completeResize } = useLayoutAreaSizing({ area: 'chat-panel', config: { defaultWidth, compactDefaultWidth, minWidth, maxWidth, isOpen: true, onRequestClose: onClose } }); useEffect(() => { const mediaQuery = window.matchMedia(inlineMediaQuery); setIsInline(mediaQuery.matches); const onChange = event => { setIsInline(event.matches); }; return bind(mediaQuery, { type: 'change', listener: onChange }); }, []); const mode = (_managedSizing$mode = managedSizing === null || managedSizing === void 0 ? void 0 : managedSizing.mode) !== null && _managedSizing$mode !== void 0 ? _managedSizing$mode : isInline ? 'inline' : 'overlay'; const renderedWidth = (_managedSizing$width = managedSizing === null || managedSizing === void 0 ? void 0 : managedSizing.width) !== null && _managedSizing$width !== void 0 ? _managedSizing$width : mode === 'inline' ? inlineWidth : overlayWidth; useResizingWidthCssVarOnRootElement({ isEnabled: !managedSizing, cssVar: panelSplitterResizingVar, panelId: chatPanelPanelSplitterId }); const getResizeBounds = useCallback(() => { const managedBounds = getManagedResizeBounds(); if (managedBounds) { return managedBounds; } const chatPanel = ref.current; const layoutRoot = chatPanel === null || chatPanel === void 0 ? void 0 : chatPanel.parentElement; const main = layoutRoot === null || layoutRoot === void 0 ? void 0 : layoutRoot.querySelector(':scope > [data-layout-slot][role="main"]'); if (!chatPanel || !main || getComputedStyle(chatPanel).gridArea !== 'chat-panel') { return { min: minWidth, max: maxWidth !== null && maxWidth !== void 0 ? maxWidth : '90vw' }; } const chatPanelWidth = chatPanel.getBoundingClientRect().width; const mainWidth = main.getBoundingClientRect().width; if (chatPanelWidth === 0 || mainWidth === 0) { return { min: minWidth, max: maxWidth !== null && maxWidth !== void 0 ? maxWidth : '90vw' }; } const localPanel = main.querySelector('[data-layout-with-panel-slot]'); const isLocalPanelInline = localPanel && getComputedStyle(localPanel).gridArea === 'panel'; const localPanelWidth = isLocalPanelInline ? localPanel.getBoundingClientRect().width : 0; const mainMinimumWidth = getPixelCustomProperty(main, mainMinimumWidthVar, defaultMainMinWidth); const mainContentWidth = mainWidth - localPanelWidth; const availableWidth = Math.max(0, mainContentWidth - mainMinimumWidth); const availableInlineWidth = Math.floor(chatPanelWidth + availableWidth); const constrainedMaxWidth = maxWidth ? Math.min(convertResizeBoundToPixels(maxWidth), availableInlineWidth) : availableInlineWidth; return { min: minWidth, max: `${Math.max(convertResizeBoundToPixels(minWidth), constrainedMaxWidth)}px` }; }, [getManagedResizeBounds, maxWidth, minWidth]); const configuredMaxWidth = maxWidth !== null && maxWidth !== void 0 ? maxWidth : '100vw'; const chatPanelVariableWidth = managedSizing ? `${renderedWidth}px` : `clamp(${minWidth}, ${inlineWidth}px, ${configuredMaxWidth})`; const chatPanelOverlayVariableWidth = managedSizing ? `${renderedWidth}px` : `clamp(${minWidth}, ${overlayWidth}px, min(90vw, ${configuredMaxWidth}))`; const onCompleteResize = useCallback(finalWidth => { if (mode === 'inline') { setInlineWidth(finalWidth); } else { setOverlayWidth(finalWidth); } completeResize(mode, finalWidth); }, [completeResize, mode]); const onResizeStartInternal = useCallback(() => { startResize(mode); }, [mode, startResize]); const onResizeInternal = useCallback(width => resize(mode, width), [mode, resize]); return /*#__PURE__*/React.createElement("section", { id: id, "data-layout-slot": true, "aria-label": label, className: ax([styles.root, hasBorder && styles.border, (managedSizing === null || managedSizing === void 0 ? void 0 : managedSizing.mode) === 'inline' && styles.managedInline, (managedSizing === null || managedSizing === void 0 ? void 0 : managedSizing.mode) === 'overlay' && styles.managedOverlay, hasBorder && (managedSizing === null || managedSizing === void 0 ? void 0 : managedSizing.mode) === 'inline' && styles.managedInlineBorder, hasBorder && (managedSizing === null || managedSizing === void 0 ? void 0 : managedSizing.mode) === 'overlay' && styles.managedOverlayBorder, xcss]), style: { // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop, @atlaskit/ui-styling-standard/no-unsafe-values, @atlaskit/ui-styling-standard/no-imported-style-values -- Shared layout CSS variable name. [chatPanelVar]: chatPanelVariableWidth, // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop, @atlaskit/ui-styling-standard/no-unsafe-values [chatPanelOverlayWidthVar]: chatPanelOverlayVariableWidth }, "data-testid": testId, "data-layout-chat-panel": "", ref: ref }, /*#__PURE__*/ // Hoist the committed or actively resizing inline width so SideNav can yield // space to ChatPanel without allowing the page layout to overflow. // eslint-disable-next-line @atlaskit/ui-styling-standard/no-global-styles React.createElement("style", null, managedSizing ? ` #${gridRootId} { --n_chatTrack: ${mode === 'inline' ? `${renderedWidth}px` : '0px'}; ${chatPanelLiveWidthVar}: var(--n_chatTrack); } ` : ` #${gridRootId} { --n_chatTrack: 0px; ${chatPanelLiveWidthVar}: 0px; } #${gridRootId} [data-layout-chat-panel] { grid-area: 2 / 1 / -1 / -1; width: min(calc(var(--n_layoutW) * 0.9), ${chatPanelOverlayVariableWidth}); z-index: ${localSlotLayers.chatPanelOverlay}; background-color: ${"var(--ds-surface-overlay, #FFFFFF)"}; ${hasBorder ? `box-shadow: ${"var(--ds-shadow-overlay, 0px 8px 12px #1E1F2126, 0px 0px 1px #1E1F214f)"}; border-inline-start: none;` : ''} } @media (min-width: 40rem) { @container navigation-chat-space (min-width: ${minWidth}) { #${gridRootId} { --n_chatTrack: min(100cqw, var(${panelSplitterResizingVar}, ${chatPanelVariableWidth})); ${chatPanelLiveWidthVar}: var(--n_chatTrack); } #${gridRootId} [data-layout-chat-panel] { grid-area: chat-panel; width: min(100cqw, var(${panelSplitterResizingVar}, ${chatPanelVariableWidth})); z-index: auto; background-color: ${"var(--ds-surface, #FFFFFF)"}; ${hasBorder ? `box-shadow: initial; border-inline-start: ${"var(--ds-border-width, 1px)"} solid ${"var(--ds-border, #0B120E24)"};` : ''} } } } `), /*#__PURE__*/React.createElement(PanelSplitterProvider, { panelId: chatPanelPanelSplitterId, panelRef: ref, panelWidth: renderedWidth, onCompleteResize: onCompleteResize, prepareResize: prepareResize, onResizeStartInternal: isManaged ? onResizeStartInternal : undefined, onResizeInternal: isManaged ? onResizeInternal : undefined, getResizeBounds: getResizeBounds, resizingCssVar: panelSplitterResizingVar, position: "start" }, /*#__PURE__*/React.createElement("div", { className: ax([styles.scrollContainer]) }, children))); }