UNPKG

@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
// 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