@atlaskit/navigation-system
Version:
The latest navigation system for Atlassian apps.
147 lines (145 loc) • 7.01 kB
JavaScript
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);
}