@atlaskit/navigation-system
Version:
The latest navigation system for Atlassian apps.
193 lines (191 loc) • 9.44 kB
JavaScript
/* local-panel-surface.tsx generated by @compiled/babel-plugin v3.0.2 */
import "./local-panel-surface.compiled.css";
import * as React from 'react';
import { ax, ix } from "@compiled/react/runtime";
import { useCallback, useContext, useMemo, useRef } from 'react';
import mergeRefs from '@atlaskit/ds-lib/merge-refs';
import { useLayoutEffect } from '@atlaskit/ds-lib/use-layout-effect';
import useStableRef from '@atlaskit/ds-lib/use-stable-ref';
import { useMotion } from '@atlaskit/motion/entering/use-motion';
import ExitingPersistence from '@atlaskit/motion/exiting-persistence';
import { fg } from '@atlaskit/platform-feature-flags/fg';
import { localPanelWidthVar } from './constants';
import { DangerouslyHoistSlotSizes } from './hoist-slot-sizes-context';
import { PanelSplitter } from './panel-splitter/panel-splitter';
import { PanelSplitterProvider } from './panel-splitter/provider';
const localPanelVar = '--p_lclPnlW';
const panelSplitterResizingVar = '--p_lclPnlRsz';
const localPanelSplitterId = Symbol('LocalPanel PanelSplitter');
// Compiled preserves the existing container-relative widths and discrete motion styles.
/* eslint-disable @compiled/shorthand-property-sorting -- Content motion overrides only the animation delay. */
const styles = {
panelWrapper: "_nd5l1gzg _kqsw1if8 _bfhk1bhr _vchhusvi _4t3ieqxy _152tidpf _10fph9n0 _1bsb13q4",
managedInline: "_nd5l1c6w _bfhkvuon _152tidpf _1bsb1c5w",
unmeasured: "_nd5l32aw _191wbqna _1bsb1cku _bfhk1g1j _16qs1dhr",
managedOverlay: "_nd5l1gzg _bfhk1bhr _1bsb1c5w",
panelScrollContainer: "_1reo1wug _18m91wug _4t3i1osq",
border: "_191wglyw _16qs130s",
managedInlineBorder: "_191wia51 _16qsglyw",
entering: "_k1af1nn1 _10t81ia4 _xrrp147i _1wsa1ign _1vrh1a5r _1kmh1n9t _105abamb",
exiting: "_k1af14vv _bq641n9t _105aeydr",
contentEntering: "_y44v1mph",
contentExiting: "_y44vf1qi",
contentChangeContainer: "_4t3i1osq"
};
const contentEnteringDelayStyles = {
root: "_ju25dn4k"
};
/* eslint-enable @compiled/shorthand-property-sorting */
const PanelContentMotion = ({
children
}) => {
const {
state,
ref
} = useMotion();
return /*#__PURE__*/React.createElement("div", {
ref: ref,
className: ax([styles.contentChangeContainer, state === 'entering' && styles.contentEntering, state === 'exiting' && styles.contentExiting])
}, children);
};
/**
* Internal surface for PanelLayout. The host supplies content; navigation owns its geometry.
*/
export function LocalPanelSurface({
children,
contentKey,
label,
resizeLabel,
defaultWidth,
minWidth,
maxWidth,
testId,
layoutRef,
managedSizing,
getManagedResizeBounds,
prepareResize,
onManagedResizeStart,
onManagedResize,
onManagedResizeEnd,
onWidthChange,
onResizeEnd
}) {
var _managedSizing$mode, _managedSizing$width;
const ref = useRef(null);
const dangerouslyHoistSlotSizes = useContext(DangerouslyHoistSlotSizes);
useLayoutEffect(() => {
const panel = ref.current;
if (!dangerouslyHoistSlotSizes || !panel) {
return;
}
// Container-relative CSS owns the initial width, before the sizing runtime loads.
// Publish geometry directly for external controls; never feed it into saved preferences.
let previousWidth;
const publishWidth = width => {
if (width !== previousWidth) {
previousWidth = width;
panel.ownerDocument.documentElement.style.setProperty(localPanelWidthVar, `${width}px`);
}
};
publishWidth(panel.getBoundingClientRect().width);
const observer = new ResizeObserver(([entry]) => {
var _entry$borderBoxSize$, _entry$borderBoxSize, _entry$borderBoxSize$2;
publishWidth((_entry$borderBoxSize$ = (_entry$borderBoxSize = entry.borderBoxSize) === null || _entry$borderBoxSize === void 0 ? void 0 : (_entry$borderBoxSize$2 = _entry$borderBoxSize[0]) === null || _entry$borderBoxSize$2 === void 0 ? void 0 : _entry$borderBoxSize$2.inlineSize) !== null && _entry$borderBoxSize$ !== void 0 ? _entry$borderBoxSize$ : panel.getBoundingClientRect().width);
});
observer.observe(panel, {
box: 'border-box'
});
return () => {
observer.disconnect();
panel.ownerDocument.documentElement.style.removeProperty(localPanelWidthVar);
};
}, [dangerouslyHoistSlotSizes]);
const scrollRef = useRef(null);
useLayoutEffect(() => {
if (scrollRef.current) {
scrollRef.current.scrollTop = 0;
}
}, [contentKey]);
const {
state,
ref: motionRef
} = useMotion();
const isMotionUpliftEnabled = fg('platform-dst-motion-uplift-panel');
const isContentMotionEnabled = fg('platform-dst-motion-uplift-panel-content');
const mergedRef = useMemo(() => isMotionUpliftEnabled ? mergeRefs([ref, motionRef]) : ref, [isMotionUpliftEnabled, motionRef]);
const mode = (_managedSizing$mode = managedSizing === null || managedSizing === void 0 ? void 0 : managedSizing.mode) !== null && _managedSizing$mode !== void 0 ? _managedSizing$mode : 'overlay';
const managedMode = useStableRef(managedSizing === null || managedSizing === void 0 ? void 0 : managedSizing.mode);
// Resize preparation can load the coordinator after the interaction starts.
// Read its latest mode instead of the pre-load callback's captured fallback.
const getMode = useCallback(() => {
if (managedMode.current) {
return managedMode.current;
}
return ref.current && getComputedStyle(ref.current).gridArea.split('/')[0].trim() === 'panel' ? 'inline' : 'overlay';
}, [managedMode]);
const dragMode = useRef('overlay');
const completedResizeModeRef = useRef(mode);
const renderedWidth = (_managedSizing$width = managedSizing === null || managedSizing === void 0 ? void 0 : managedSizing.width) !== null && _managedSizing$width !== void 0 ? _managedSizing$width : Math.max(minWidth, Math.min(defaultWidth, maxWidth !== null && maxWidth !== void 0 ? maxWidth : Infinity));
// Before the first measurement, CSS caps the surface to its containing region.
// Resize preparation waits for the coordinator to resolve the same bounds for both inputs.
const getResizeBounds = useCallback(() => {
var _getManagedResizeBoun;
return (_getManagedResizeBoun = getManagedResizeBounds()) !== null && _getManagedResizeBoun !== void 0 ? _getManagedResizeBoun : {
min: '0px',
max: '0px'
};
}, [getManagedResizeBounds]);
const onCompleteResize = useCallback(width => {
// Keyboard analytics are debounced; retain the mode that produced this width,
// rather than the layout mode when the delayed callback eventually runs.
const mode = getMode();
completedResizeModeRef.current = mode;
onManagedResizeEnd(mode, width);
onWidthChange(width);
}, [getMode, onManagedResizeEnd, onWidthChange]);
const onResizeStartInternal = useCallback(() => {
dragMode.current = getMode();
onManagedResizeStart(dragMode.current);
}, [getMode, onManagedResizeStart]);
const onResizeInternal = useCallback(width => onManagedResize(dragMode.current, width), [onManagedResize]);
const handleResizeEnd = useCallback(event => onResizeEnd({
...event,
mode: completedResizeModeRef.current
}), [onResizeEnd]);
return /*#__PURE__*/React.createElement("section", {
"aria-label": label,
"data-testid": testId,
"data-layout-with-panel-slot": "",
style: {
// eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop, @atlaskit/ui-styling-standard/no-imported-style-values
[localPanelVar]: `${renderedWidth}px`
},
ref: mergedRef,
className: ax([isMotionUpliftEnabled && state === 'entering' && styles.entering, isMotionUpliftEnabled && state === 'exiting' && styles.exiting, styles.panelWrapper, styles.border, !managedSizing && styles.unmeasured, (managedSizing === null || managedSizing === void 0 ? void 0 : managedSizing.mode) === 'inline' && styles.managedInline, (managedSizing === null || managedSizing === void 0 ? void 0 : managedSizing.mode) === 'overlay' && styles.managedOverlay, (managedSizing === null || managedSizing === void 0 ? void 0 : managedSizing.mode) === 'inline' && styles.managedInlineBorder])
}, /*#__PURE__*/React.createElement(PanelSplitterProvider, {
panelId: localPanelSplitterId,
panelRef: ref,
resizingElementRef: layoutRef,
panelWidth: renderedWidth,
onCompleteResize: onCompleteResize,
prepareResize: prepareResize,
onResizeStartInternal: onResizeStartInternal,
onResizeInternal: onResizeInternal,
getResizeBounds: getResizeBounds,
isEnabled: managedSizing !== undefined || prepareResize !== undefined,
resizingCssVar: panelSplitterResizingVar,
position: "start"
}, /*#__PURE__*/React.createElement("div", {
ref: scrollRef,
"data-testid": testId && `${testId}--scroll-container`,
className: ax([styles.panelScrollContainer, state === 'entering' && styles.contentEntering, isMotionUpliftEnabled && state === 'entering' && contentEnteringDelayStyles.root, state === 'exiting' && styles.contentExiting])
}, isContentMotionEnabled && contentKey !== undefined ? /*#__PURE__*/React.createElement(ExitingPersistence, {
exitThenEnter: true
}, /*#__PURE__*/React.createElement(PanelContentMotion, {
key: contentKey
}, children)) : children, /*#__PURE__*/React.createElement(PanelSplitter, {
label: resizeLabel,
testId: testId && `${testId}--splitter`,
onResizeEnd: handleResizeEnd
}))));
}