UNPKG

@atlaskit/navigation-system

Version:

The latest navigation system for Atlassian apps.

147 lines (145 loc) • 7.01 kB
import React, { useCallback, useContext, useEffect, useRef, useState } from 'react'; import invariant from 'tiny-invariant'; import useStableRef from '@atlaskit/ds-lib/use-stable-ref'; import ExitingPersistence from '@atlaskit/motion/exiting-persistence'; import { fg } from '@atlaskit/platform-feature-flags/fg'; import { getPanelLayoutDefaults } from './get-panel-layout-defaults'; import { LayoutAreaSizingContext } from './layout-area-sizing-context'; import { LayoutAreaSizingProvider } from './layout-area-sizing-provider'; import { LocalPanelSurface } from './local-panel-surface'; import { PanelLayoutShell } from './panel-layout-shell'; import { useLayoutAreaSizing } from './use-layout-area-sizing'; // Development-only ownership checks are scoped to each coordinator, not the whole page. const mountedPanelLayouts = new WeakSet(); function CoordinatedPanelLayout({ children, panel, isPanelOpen, panelKey, panelWidth, defaultPanelWidth, compactDefaultPanelWidth, panelMinWidth: suppliedMinWidth, panelMaxWidth, onPanelWidthChange, onResizeEnd, panelLabel = 'Panel', resizeLabel = 'Resize panel', testId, layoutRef }) { var _ref, _ref2, _ref3, _state$width; const [defaults] = useState(getPanelLayoutDefaults); const panelMinWidth = suppliedMinWidth !== null && suppliedMinWidth !== void 0 ? suppliedMinWidth : defaults.panelMinWidth; const coordinator = useContext(LayoutAreaSizingContext); useEffect(() => { if (process.env.NODE_ENV !== 'production' && coordinator) { invariant(!mountedPanelLayouts.has(coordinator), 'Only one PanelLayout may be mounted per navigation Root/sizing coordinator, including closed hosts and LayoutWithPanel. Reuse the host to switch panel content.'); mountedPanelLayouts.add(coordinator); return () => { mountedPanelLayouts.delete(coordinator); }; } }, [coordinator]); const [defaultWidth] = useState((_ref = defaultPanelWidth !== null && defaultPanelWidth !== void 0 ? defaultPanelWidth : panelWidth) !== null && _ref !== void 0 ? _ref : defaults.panelWidth); const [compactWidth] = useState((_ref2 = (_ref3 = compactDefaultPanelWidth !== null && compactDefaultPanelWidth !== void 0 ? compactDefaultPanelWidth : defaultPanelWidth) !== null && _ref3 !== void 0 ? _ref3 : panelWidth) !== null && _ref2 !== void 0 ? _ref2 : defaults.compactPanelWidth); // Use the controlled request before measurement too, without changing the mount-time default. const fallbackWidth = Math.max(panelMinWidth, Math.min(panelWidth !== null && panelWidth !== void 0 ? panelWidth : defaultWidth, panelMaxWidth !== null && panelMaxWidth !== void 0 ? panelMaxWidth : Infinity)); const { state, prepareResize, getResizeBounds, startResize, resize, completeResize } = useLayoutAreaSizing({ area: 'panel', config: { isOpen: isPanelOpen, requestedWidth: panelWidth, defaultWidth, compactDefaultWidth: compactWidth, minWidth: panelMinWidth, maxWidth: panelMaxWidth } }); // Host callback identity is unrelated to sizing. Keep drag bindings stable while // still delivering completions to the latest product-owned state and analytics. const onWidthChangeRef = useStableRef(onPanelWidthChange); const onResizeEndRef = useStableRef(onResizeEnd); const handleWidthChange = useCallback(width => { var _onWidthChangeRef$cur; return (_onWidthChangeRef$cur = onWidthChangeRef.current) === null || _onWidthChangeRef$cur === void 0 ? void 0 : _onWidthChangeRef$cur.call(onWidthChangeRef, width); }, [onWidthChangeRef]); const handleResizeEnd = useCallback(event => { var _onResizeEndRef$curre; return (_onResizeEndRef$curre = onResizeEndRef.current) === null || _onResizeEndRef$curre === void 0 ? void 0 : _onResizeEndRef$curre.call(onResizeEndRef, event); }, [onResizeEndRef]); const isMotionEnabled = fg('platform-dst-motion-uplift-panel'); const surface = isPanelOpen && /*#__PURE__*/React.createElement(LocalPanelSurface, { key: "local-panel", contentKey: panelKey, defaultWidth: fallbackWidth, minWidth: panelMinWidth, maxWidth: panelMaxWidth, layoutRef: layoutRef, managedSizing: state, getManagedResizeBounds: getResizeBounds, prepareResize: prepareResize, onManagedResizeStart: startResize, onManagedResize: resize, onManagedResizeEnd: completeResize, onWidthChange: handleWidthChange, onResizeEnd: handleResizeEnd, label: panelLabel, resizeLabel: resizeLabel, testId: testId && `${testId}--panel-slot` }, panel); return /*#__PURE__*/React.createElement(PanelLayoutShell, { isPanelOpen: isPanelOpen, panelWidth: (_state$width = state === null || state === void 0 ? void 0 : state.width) !== null && _state$width !== void 0 ? _state$width : fallbackWidth, compactPanelWidth: Math.max(panelMinWidth, Math.min(panelWidth !== null && panelWidth !== void 0 ? panelWidth : compactWidth, panelMaxWidth !== null && panelMaxWidth !== void 0 ? panelMaxWidth : Infinity)), panelMinWidth: panelMinWidth, mode: state === null || state === void 0 ? void 0 : state.mode, layoutRef: layoutRef, shouldClipOverflow: isMotionEnabled, testId: testId, panel: isMotionEnabled ? /*#__PURE__*/React.createElement(ExitingPersistence, null, surface) : surface }, children); } /** * A coordinated local panel with caller-owned content and state. Keep this host mounted * while closed to retain resized widths. Unmount it to reset its width preferences. * * Mount only one local-panel host per navigation Root/sizing coordinator, including * closed hosts and panel-system's LayoutWithPanel. Switch content through the same host; * separate Roots may each own a local panel. * * With chat layout enabled it shares Root's sizing coordinator. Otherwise it measures * its own Main region, leaving existing navigation/chat behavior unchanged. * Local-panel placement and resize constraints use this layout in both cases; disabling * the chat-layout gate does not restore the previous panel-system layout engine. */ export function PanelLayout(props) { const parentSizing = useContext(LayoutAreaSizingContext); const layoutRef = useRef(null); const content = /*#__PURE__*/React.createElement(CoordinatedPanelLayout, { layoutRef: layoutRef, panel: props.panel, isPanelOpen: props.isPanelOpen, panelKey: props.panelKey, panelWidth: props.panelWidth, defaultPanelWidth: props.defaultPanelWidth, compactDefaultPanelWidth: props.compactDefaultPanelWidth, panelMinWidth: props.panelMinWidth, panelMaxWidth: props.panelMaxWidth, onPanelWidthChange: props.onPanelWidthChange, onResizeEnd: props.onResizeEnd, panelLabel: props.panelLabel, resizeLabel: props.resizeLabel, testId: props.testId }, props.children); return parentSizing ? content : /*#__PURE__*/React.createElement(LayoutAreaSizingProvider, { layoutRef: layoutRef }, content); }