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