@awsui/components-react
Version:
On July 19th, 2022, we launched [Cloudscape Design System](https://cloudscape.design). Cloudscape is an evolution of AWS-UI. It consists of user interface guidelines, front-end components, design resources, and development tools for building intuitive, en
194 lines • 13.3 kB
JavaScript
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React, { useEffect, useRef } from 'react';
import { Transition } from 'react-transition-group';
import clsx from 'clsx';
import ButtonGroup from '../../../button-group/internal';
import PanelResizeHandle from '../../../internal/components/panel-resize-handle';
import customCssProps from '../../../internal/generated/custom-css-properties';
import { usePrevious } from '../../../internal/hooks/use-previous';
import { getLimitedValue } from '../../../split-panel/utils/size-utils';
import { getDrawerStyles } from '../compute-layout';
import { useResize } from './use-resize';
import sharedStyles from '../../resize/styles.css.js';
import testutilStyles from '../../test-classes/styles.css.js';
import styles from './styles.css.js';
export function AppLayoutBottomDrawerWrapper({ widgetizedState }) {
const { activeGlobalBottomDrawerId, bottomDrawers } = widgetizedState;
const openBottomDrawersHistory = useRef(new Set());
useEffect(() => {
if (activeGlobalBottomDrawerId) {
openBottomDrawersHistory.current.add(activeGlobalBottomDrawerId);
}
}, [activeGlobalBottomDrawerId]);
return (React.createElement(React.Fragment, null, bottomDrawers.map(drawer => {
return (React.createElement(AppLayoutGlobalBottomDrawerImplementation, { key: drawer.id, activeDrawer: activeGlobalBottomDrawerId === drawer.id ||
(drawer.preserveInactiveContent && openBottomDrawersHistory.current.has(drawer.id))
? drawer
: undefined, show: activeGlobalBottomDrawerId === drawer.id, widgetizedState: widgetizedState }));
})));
}
const GAP_HEIGHT = 10;
const RESIZE_HANDLER_HEIGHT = 18;
function AppLayoutGlobalBottomDrawerImplementation({ widgetizedState, show, activeDrawer, }) {
var _a, _b, _c, _d, _e, _f;
const { ariaLabels, isMobile, onActiveGlobalBottomDrawerChange, onActiveBottomDrawerResize, minGlobalBottomDrawerSize, activeGlobalBottomDrawerSize, drawersOpenQueue, expandedDrawerId, setExpandedDrawerId, activeAiDrawer, bottomDrawersFocusControl, getMaxGlobalBottomDrawerHeight, reportBottomDrawerSize, verticalOffsets, placement, } = widgetizedState;
const drawerRef = useRef(null);
const headerRef = useRef(null);
const activeDrawerId = (_a = activeDrawer === null || activeDrawer === void 0 ? void 0 : activeDrawer.id) !== null && _a !== void 0 ? _a : '';
const computedAriaLabels = {
closeButton: activeDrawer ? (_b = activeDrawer.ariaLabels) === null || _b === void 0 ? void 0 : _b.closeButton : ariaLabels === null || ariaLabels === void 0 ? void 0 : ariaLabels.toolsClose,
content: activeDrawer ? (_c = activeDrawer.ariaLabels) === null || _c === void 0 ? void 0 : _c.drawerName : ariaLabels === null || ariaLabels === void 0 ? void 0 : ariaLabels.tools,
};
const { drawerTopOffset: mobileDrawerTopOffset, drawerHeight: drawerFullScreenHeight } = getDrawerStyles(verticalOffsets, isMobile, placement);
const activeDrawerSize = activeGlobalBottomDrawerSize !== null && activeGlobalBottomDrawerSize !== void 0 ? activeGlobalBottomDrawerSize : 0;
const minDrawerSize = minGlobalBottomDrawerSize !== null && minGlobalBottomDrawerSize !== void 0 ? minGlobalBottomDrawerSize : 0;
const maxDrawerSize = getMaxGlobalBottomDrawerHeight();
const refs = bottomDrawersFocusControl.refs;
const resizeProps = useResize({
currentWidth: activeDrawerSize,
minWidth: minDrawerSize,
maxWidth: maxDrawerSize,
panelRef: drawerRef,
handleRef: refs === null || refs === void 0 ? void 0 : refs.slider,
onResize: size => onActiveBottomDrawerResize({ id: activeDrawerId, size }),
position: 'bottom',
});
const size = getLimitedValue(minDrawerSize, activeDrawerSize, maxDrawerSize);
const lastOpenedDrawerId = drawersOpenQueue.length ? drawersOpenQueue[0] : null;
const hasTriggerButton = !!(activeDrawer === null || activeDrawer === void 0 ? void 0 : activeDrawer.trigger);
const isExpanded = (activeDrawer === null || activeDrawer === void 0 ? void 0 : activeDrawer.isExpandable) && expandedDrawerId === activeDrawerId;
const wasExpanded = usePrevious(isExpanded);
const animationDisabled = ((activeDrawer === null || activeDrawer === void 0 ? void 0 : activeDrawer.defaultActive) && !drawersOpenQueue.includes(activeDrawer.id)) || (wasExpanded && !isExpanded);
// Prevent main content scroll when bottom drawer opens with animations
useEffect(() => {
if (show && drawerRef.current) {
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
const abortController = new AbortController();
// Temporarily prevent scrolling during animation
const preventScroll = () => {
document.documentElement.scrollTop = scrollTop;
document.body.scrollTop = scrollTop;
};
// Handle transition end to remove scroll prevention
const handleTransitionEnd = (event) => {
// Only handle transitions on the drawer element itself
if (event.target === drawerRef.current) {
abortController.abort();
}
};
// Add scroll prevention during animation
document.addEventListener('scroll', preventScroll, {
passive: false,
signal: abortController.signal,
});
drawerRef.current.addEventListener('transitionend', handleTransitionEnd, {
signal: abortController.signal,
});
return () => {
abortController.abort();
};
}
}, [show]);
let drawerActions = [
{
type: 'icon-button',
id: 'close',
iconName: isMobile ? 'close' : 'angle-down',
text: (_d = computedAriaLabels.closeButton) !== null && _d !== void 0 ? _d : '',
analyticsAction: 'close',
},
];
if (!isMobile && (activeDrawer === null || activeDrawer === void 0 ? void 0 : activeDrawer.isExpandable)) {
drawerActions = [
{
type: 'icon-toggle-button',
id: 'expand',
iconName: isExpanded ? 'shrink' : 'expand',
text: (_f = (_e = activeDrawer === null || activeDrawer === void 0 ? void 0 : activeDrawer.ariaLabels) === null || _e === void 0 ? void 0 : _e.expandedModeButton) !== null && _f !== void 0 ? _f : '',
pressed: !!isExpanded,
analyticsAction: isExpanded ? 'expand' : 'collapse',
},
...drawerActions,
];
}
if (activeDrawer === null || activeDrawer === void 0 ? void 0 : activeDrawer.headerActions) {
drawerActions = [
{
type: 'group',
text: 'Actions',
items: activeDrawer.headerActions,
},
...drawerActions,
];
}
useEffect(() => {
reportBottomDrawerSize(size);
}, [reportBottomDrawerSize, size]);
return (React.createElement(Transition, { nodeRef: drawerRef, in: show || isExpanded, appear: show || isExpanded, mountOnEnter: true, timeout: 250 }, state => {
var _a, _b, _c, _d, _e;
return (React.createElement("aside", { id: activeDrawerId, "aria-hidden": !show, "aria-label": computedAriaLabels.content, className: clsx(styles.drawer, styles['bottom-drawer'], styles[state], !animationDisabled && sharedStyles['with-motion-vertical'], !animationDisabled && isExpanded && styles['with-expanded-motion'], {
[styles['drawer-hidden']]: !show && state === 'exited',
[styles['last-opened']]: (!activeAiDrawer && lastOpenedDrawerId === activeDrawerId) || isExpanded,
[testutilStyles['active-drawer']]: show,
[styles['drawer-expanded']]: isExpanded,
}), ref: drawerRef, onBlur: e => {
// Drawers with trigger buttons follow this restore focus logic:
// If a previously focused element exists, restore focus on it; otherwise, focus on the associated trigger button.
// This function resets the previously focused element.
// If the drawer has no trigger button and loses focus on the previously focused element, it defaults to document.body,
// which ideally should never happen.
if (!hasTriggerButton) {
return;
}
if (!e.relatedTarget || !e.currentTarget.contains(e.relatedTarget)) {
bottomDrawersFocusControl.loseFocus();
}
}, style: {
...(isMobile && {
blockSize: drawerFullScreenHeight,
insetBlockStart: mobileDrawerTopOffset,
}),
...(!isMobile && {
[customCssProps.bottomDrawerSize]: `${['entering', 'entered'].includes(state) ? (isExpanded ? drawerFullScreenHeight : size + 'px') : 0}`,
}),
}, "data-testid": `awsui-app-layout-drawer-${activeDrawerId}` },
React.createElement("div", { className: clsx(styles['global-drawer-wrapper']) },
!isMobile && !isExpanded && React.createElement("div", { className: styles['drawer-gap'] }),
!isMobile && (activeDrawer === null || activeDrawer === void 0 ? void 0 : activeDrawer.resizable) && !isExpanded && (
// Prevents receiving focus in Firefox
React.createElement("div", { className: styles['drawer-slider'], tabIndex: -1 },
React.createElement(PanelResizeHandle, { ref: refs === null || refs === void 0 ? void 0 : refs.slider, position: "bottom", className: testutilStyles['drawers-slider'], ariaLabel: (_a = activeDrawer === null || activeDrawer === void 0 ? void 0 : activeDrawer.ariaLabels) === null || _a === void 0 ? void 0 : _a.resizeHandle, tooltipText: (_b = activeDrawer === null || activeDrawer === void 0 ? void 0 : activeDrawer.ariaLabels) === null || _b === void 0 ? void 0 : _b.resizeHandleTooltipText, ariaValuenow: resizeProps.relativeSize, onKeyDown: resizeProps.onKeyDown, onDirectionClick: resizeProps.onDirectionClick, onPointerDown: resizeProps.onPointerDown }))),
React.createElement("header", { className: styles['bottom-drawer-content-header'], ref: headerRef },
React.createElement("div", { className: styles['bottom-drawer-content-header-content'] }, (_c = activeDrawer === null || activeDrawer === void 0 ? void 0 : activeDrawer.header) !== null && _c !== void 0 ? _c : React.createElement("div", null),
React.createElement("div", { className: styles['bottom-drawer-actions'] },
React.createElement(ButtonGroup, { dropdownExpandToViewport: false, variant: "icon", onItemClick: event => {
var _a;
switch (event.detail.id) {
case 'close':
onActiveGlobalBottomDrawerChange(null, { initiatedByUserAction: true });
break;
case 'expand':
setExpandedDrawerId(isExpanded ? null : activeDrawerId);
break;
default:
(_a = activeDrawer === null || activeDrawer === void 0 ? void 0 : activeDrawer.onHeaderActionClick) === null || _a === void 0 ? void 0 : _a.call(activeDrawer, event);
}
}, ariaLabel: "Global panel actions", items: drawerActions, __internalRootRef: (root) => {
if (!root) {
return;
}
refs.close = {
current: root.querySelector('[data-itemid="close"]'),
};
} })))),
React.createElement("div", { className: clsx(styles['drawer-content-container'], sharedStyles['with-motion-horizontal']), "data-testid": `awsui-app-layout-drawer-content-${activeDrawerId}` },
React.createElement("div", { className: styles['drawer-content'], style: {
blockSize: isMobile || isExpanded
? drawerFullScreenHeight
: `${size - GAP_HEIGHT - RESIZE_HANDLER_HEIGHT - ((_e = (_d = headerRef === null || headerRef === void 0 ? void 0 : headerRef.current) === null || _d === void 0 ? void 0 : _d.clientHeight) !== null && _e !== void 0 ? _e : 0)}px`,
} }, activeDrawer === null || activeDrawer === void 0 ? void 0 : activeDrawer.content)))));
}));
}
export default AppLayoutGlobalBottomDrawerImplementation;
//# sourceMappingURL=global-bottom-drawer.js.map