Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build-tools/utils/custom-css-properties.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const customCssPropertiesList = [
'toolsMaxWidth',
'toolsWidth',
'toolsAnimationStartingOpacity',
'activeGlobalBottomDrawerHeight',
// Annotation Context Custom Properties
'contentScrollMargin',
// Flashbar Custom Properties
Expand Down
3 changes: 3 additions & 0 deletions pages/app-layout/utils/external-widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ awsuiPlugins.appLayout.registerDrawer({

isExpandable: true,

movable: true,
position: 'bottom',

ariaLabels: {
closeButton: 'Close button',
content: 'Content',
Expand Down
5 changes: 5 additions & 0 deletions src/app-layout/runtime-drawer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import React, { useContext, useEffect, useRef } from 'react';
import { fireNonCancelableEvent, NonCancelableEventHandler } from '../../internal/events';
import {
DrawerConfig as RuntimeDrawerConfig,
DrawerPosition,
DrawerPositionChangeParams,
DrawerStateChangeParams,
} from '../../internal/plugins/controllers/drawers';
import { sortByPriority } from '../../internal/plugins/helpers/utils';
Expand All @@ -15,6 +17,9 @@ import styles from './styles.css.js';

export interface RuntimeDrawer extends AppLayoutProps.Drawer {
onToggle?: NonCancelableEventHandler<DrawerStateChangeParams>;
movable?: boolean;
position?: DrawerPosition;
onPositionChange?: NonCancelableEventHandler<DrawerPositionChangeParams>;
}

export interface DrawersLayout {
Expand Down
2 changes: 1 addition & 1 deletion src/app-layout/utils/use-drawers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ function getToolsDrawerItem(props: ToolsProps): AppLayoutProps.Drawer | null {
};
}

const DRAWERS_LIMIT = 2;
const DRAWERS_LIMIT = 3;

const DEFAULT_ON_CHANGE_PARAMS = { initiatedByUserAction: true };

Expand Down
11 changes: 8 additions & 3 deletions src/app-layout/visual-refresh-toolbar/compute-layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,15 @@ interface VerticalLayoutInput {
toolbarHeight: number;
stickyNotifications: boolean;
notificationsHeight: number;
activeBottomDrawerHeight: number;
}

export interface VerticalLayoutOutput {
toolbar: number;
notifications: number;
header: number;
drawers: number;
bottomDrawer: number;
}

export function computeVerticalLayout({
Expand All @@ -96,6 +98,7 @@ export function computeVerticalLayout({
toolbarHeight,
stickyNotifications,
notificationsHeight,
activeBottomDrawerHeight,
}: VerticalLayoutInput): VerticalLayoutOutput {
const toolbar = topOffset;
let notifications = topOffset;
Expand All @@ -110,7 +113,7 @@ export function computeVerticalLayout({
header += notificationsHeight;
}

return { toolbar, notifications, header, drawers };
return { toolbar, notifications, header, drawers, bottomDrawer: activeBottomDrawerHeight };
}

interface SplitPanelOffsetInput {
Expand Down Expand Up @@ -150,8 +153,10 @@ export function getDrawerStyles(
): {
drawerTopOffset: number;
drawerHeight: string;
globalDrawerHeight: string;
} {
const drawerTopOffset = isMobile ? verticalOffsets.toolbar : (verticalOffsets.drawers ?? placement.insetBlockStart);
const drawerHeight = `calc(100vh - ${drawerTopOffset}px - ${placement.insetBlockEnd}px)`;
return { drawerTopOffset, drawerHeight };
const drawerHeight = `calc(100vh - ${drawerTopOffset}px - ${placement.insetBlockEnd}px - ${verticalOffsets.bottomDrawer}px)`;
const globalDrawerHeight = `calc(100vh - ${drawerTopOffset}px - ${placement.insetBlockEnd}px)`;
return { drawerTopOffset, drawerHeight, globalDrawerHeight };
}
42 changes: 32 additions & 10 deletions src/app-layout/visual-refresh-toolbar/drawer/global-drawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,20 @@ function AppLayoutGlobalDrawerImplementation({
content: activeGlobalDrawer ? activeGlobalDrawer.ariaLabels?.drawerName : ariaLabels?.tools,
};

const { drawerTopOffset, drawerHeight } = getDrawerStyles(verticalOffsets, isMobile, placement);
const getMaxHeight = () => {
const availableHeight = document.documentElement.clientHeight - placement.insetBlockStart - placement.insetBlockEnd;
// If the page is likely zoomed in at 200%, allow the bottom panel to fill the content area
return availableHeight < 400 ? availableHeight - 40 : availableHeight - 250;
};
const MIN_HEIGHT = 160;

const position = activeGlobalDrawer?.position ?? 'side';
const { drawerTopOffset, globalDrawerHeight } = getDrawerStyles(verticalOffsets, isMobile, placement);
const activeDrawerSize = (activeDrawerId ? activeGlobalDrawersSizes[activeDrawerId] : 0) ?? 0;
const minDrawerSize = (activeDrawerId ? minGlobalDrawersSizes[activeDrawerId] : 0) ?? 0;
const maxDrawerSize = (activeDrawerId ? maxGlobalDrawersSizes[activeDrawerId] : 0) ?? 0;
const minDrawerSize =
position === 'side' ? ((activeDrawerId ? minGlobalDrawersSizes[activeDrawerId] : 0) ?? 0) : MIN_HEIGHT;
const maxDrawerSize =
position === 'side' ? ((activeDrawerId ? maxGlobalDrawersSizes[activeDrawerId] : 0) ?? 0) : getMaxHeight();
const refs = globalDrawersFocusControl.refs[activeDrawerId];
const resizeProps = useResize({
currentWidth: activeDrawerSize,
Expand All @@ -64,6 +74,7 @@ function AppLayoutGlobalDrawerImplementation({
panelRef: drawerRef,
handleRef: refs?.slider,
onResize: size => onActiveDrawerResize({ id: activeDrawerId!, size }),
position,
});
const size = getLimitedValue(minDrawerSize, activeDrawerSize, maxDrawerSize);
const lastOpenedDrawerId = drawersOpenQueue.length ? drawersOpenQueue[0] : null;
Expand All @@ -73,6 +84,7 @@ function AppLayoutGlobalDrawerImplementation({
const animationDisabled =
(activeGlobalDrawer?.defaultActive && !drawersOpenQueue.includes(activeGlobalDrawer.id)) ||
(wasExpanded && !isExpanded);
const motionClassName = `with-motion-${position === 'side' ? 'horizontal' : 'vertical'}`;

return (
<Transition nodeRef={drawerRef} in={show || isExpanded} appear={show || isExpanded} timeout={0}>
Expand All @@ -86,8 +98,9 @@ function AppLayoutGlobalDrawerImplementation({
styles.drawer,
styles['drawer-global'],
styles[state],
!animationDisabled && sharedStyles['with-motion-horizontal'],
!animationDisabled && sharedStyles[motionClassName],
!animationDisabled && isExpanded && styles['with-expanded-motion'],
styles[position],
{
[styles['drawer-hidden']]: !show,
[styles['last-opened']]: lastOpenedDrawerId === activeDrawerId || isExpanded,
Expand All @@ -114,21 +127,23 @@ function AppLayoutGlobalDrawerImplementation({
}
}}
style={{
blockSize: drawerHeight,
insetBlockStart: drawerTopOffset,
...(!isMobile && {
[customCssProps.drawerSize]: `${['entering', 'entered'].includes(state) ? (isExpanded ? '100%' : size + 'px') : 0}`,
}),
...(position === 'side' && {
insetBlockStart: drawerTopOffset,
blockSize: globalDrawerHeight,
}),
}}
data-testid={`awsui-app-layout-drawer-${activeDrawerId}`}
>
<div className={clsx(styles['global-drawer-wrapper'])}>
{!isMobile && <div className={styles['drawer-gap']}></div>}
{!isMobile && <div className={clsx(styles['drawer-gap'], styles[position])}></div>}
{!isMobile && activeGlobalDrawer?.resizable && !isExpanded && (
<div className={styles['drawer-slider']}>
<PanelResizeHandle
ref={refs?.slider}
position="side"
position={position}
className={testutilStyles['drawers-slider']}
ariaLabel={activeGlobalDrawer?.ariaLabels?.resizeHandle}
tooltipText={activeGlobalDrawer?.ariaLabels?.resizeHandleTooltipText}
Expand All @@ -141,7 +156,7 @@ function AppLayoutGlobalDrawerImplementation({
)}

<div
className={clsx(styles['drawer-content-container'], sharedStyles['with-motion-horizontal'])}
className={clsx(styles['drawer-content-container'], sharedStyles[motionClassName])}
data-testid={`awsui-app-layout-drawer-content-${activeDrawerId}`}
>
<div className={styles['drawer-actions']}>
Expand Down Expand Up @@ -174,7 +189,14 @@ function AppLayoutGlobalDrawerImplementation({
/>
</div>
</div>
<div className={styles['drawer-content']} style={{ blockSize: drawerHeight }}>
<div
className={styles['drawer-content']}
style={{
...(position === 'side' && {
blockSize: globalDrawerHeight,
}),
}}
>
{activeGlobalDrawer?.content}
</div>
</div>
Expand Down
61 changes: 55 additions & 6 deletions src/app-layout/visual-refresh-toolbar/drawer/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,13 @@ $drawer-resize-handle-size: awsui.$space-m;
}

@include desktop-only {
&:not(.legacy) {
&:not(.bottom):not(.legacy) {
border-inline-start: awsui.$border-divider-section-width solid awsui.$color-border-layout;
}

&.bottom:not(.legacy) {
border-block-start: awsui.$border-divider-section-width solid awsui.$color-border-layout;
}
}

@include mobile-only {
Expand Down Expand Up @@ -171,13 +175,25 @@ $drawer-resize-handle-size: awsui.$space-m;
}

> .drawer-gap {
grid-column: 1;
grid-row: 1;
block-size: 100%;
inline-size: $global-drawer-gap-size;
background: awsui.$color-gap-global-drawer;
border-inline-end: awsui.$border-divider-section-width solid awsui.$color-border-layout;
box-sizing: border-box;

&.side {
grid-column: 1;
grid-row: 1;
block-size: 100%;
inline-size: $global-drawer-gap-size;
border-inline-end: awsui.$border-divider-section-width solid awsui.$color-border-layout;
}

&.bottom {
position: absolute;
inset-block-start: 0;
inset-inline-start: 0;
inline-size: 100%;
block-size: $global-drawer-gap-size;
border-block-end: awsui.$border-divider-section-width solid awsui.$color-border-layout;
}
}

> .drawer-slider {
Expand Down Expand Up @@ -237,4 +253,37 @@ $drawer-resize-handle-size: awsui.$space-m;
}
}
}

&.bottom {
@include desktop-only {
position: fixed;
inset-block-end: 0;
inset-inline-start: 0;
inline-size: 100%;
overflow: auto;
block-size: var(#{custom-props.$drawerSize});
z-index: 840;

> .global-drawer-wrapper {
display: block;
overflow-y: auto;
position: relative;
min-inline-size: 0;

> .drawer-slider {
position: absolute;
inset-block-start: $global-drawer-gap-size;
inset-inline-start: 0;
inline-size: 100%;
display: flex;
justify-content: center;
z-index: 2;
}

> .drawer-content-container {
block-size: var(#{custom-props.$drawerSize});
}
}
}
}
}
5 changes: 3 additions & 2 deletions src/app-layout/visual-refresh-toolbar/drawer/use-resize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ interface ResizeProps {
panelRef: React.RefObject<HTMLDivElement>;
handleRef: React.RefObject<HTMLDivElement>;
onResize: (newWidth: number) => void;
position?: 'side' | 'bottom';
}

export function useResize({ currentWidth, minWidth, maxWidth, panelRef, handleRef, onResize }: ResizeProps) {
export function useResize({ currentWidth, minWidth, maxWidth, panelRef, handleRef, onResize, position }: ResizeProps) {
const onResizeHandler = (newWidth: number) => {
const size = getLimitedValue(minWidth, newWidth, maxWidth);

Expand All @@ -26,7 +27,7 @@ export function useResize({ currentWidth, minWidth, maxWidth, panelRef, handleRe
};

const sizeControlProps: SizeControlProps = {
position: 'side',
position: position ?? 'side',
panelRef,
handleRef,
onResize: onResizeHandler,
Expand Down
21 changes: 19 additions & 2 deletions src/app-layout/visual-refresh-toolbar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,9 @@ const AppLayoutVisualRefreshToolbar = React.forwardRef<AppLayoutProps.Ref, AppLa
focusNavigation: () => navigationFocusControl.setFocus(true),
}));

const activeBottomDrawerId = activeGlobalDrawers.find(drawer => drawer.position === 'bottom')?.id;
const activeBottomDrawerHeight = activeBottomDrawerId ? activeGlobalDrawersSizes[activeBottomDrawerId] : 0;

const resolvedStickyNotifications = !!stickyNotifications && !isMobile;
//navigation must be null if hidden so toolbar knows to hide the toggle button
const resolvedNavigation = navigationHide ? null : navigation || <></>;
Expand All @@ -265,7 +268,15 @@ const AppLayoutVisualRefreshToolbar = React.forwardRef<AppLayoutProps.Ref, AppLa
splitPanelOpen,
splitPanelPosition: splitPanelPreferences?.position,
isMobile,
activeGlobalDrawersSizes,
activeGlobalDrawersSizes: Object.keys(activeGlobalDrawersSizes)
.filter(key => key !== activeBottomDrawerId)
.reduce(
(acc, curr) => ({
...acc,
[curr]: activeGlobalDrawersSizes[curr],
}),
{}
),
});

const { ref: intersectionObserverRef, isIntersecting } = useIntersectionObserver({ initialState: true });
Expand Down Expand Up @@ -311,6 +322,7 @@ const AppLayoutVisualRefreshToolbar = React.forwardRef<AppLayoutProps.Ref, AppLa
notificationsHeight: notificationsHeight ?? 0,
toolbarHeight: toolbarHeight ?? 0,
stickyNotifications: resolvedStickyNotifications,
activeBottomDrawerHeight: activeBottomDrawerHeight,
});

const appLayoutInternals: AppLayoutInternals = {
Expand Down Expand Up @@ -385,7 +397,10 @@ const AppLayoutVisualRefreshToolbar = React.forwardRef<AppLayoutProps.Ref, AppLa
};

const closeFirstDrawer = useStableCallback(() => {
const drawerToClose = drawersOpenQueue[drawersOpenQueue.length - 1];
const sideDrawersOpenQueue = drawersOpenQueue.filter(
drawerId => globalDrawers.find(globalDrawer => globalDrawer.id === drawerId)?.position !== 'bottom'
);
const drawerToClose = sideDrawersOpenQueue[sideDrawersOpenQueue.length - 1];
if (activeDrawer && activeDrawer?.id === drawerToClose) {
onActiveDrawerChange(null, { initiatedByUserAction: true });
} else if (activeGlobalDrawersIds.includes(drawerToClose)) {
Expand Down Expand Up @@ -553,6 +568,8 @@ const AppLayoutVisualRefreshToolbar = React.forwardRef<AppLayoutProps.Ref, AppLa
contentType={contentType}
maxContentWidth={maxContentWidth}
disableContentPaddings={disableContentPaddings}
activeBottomDrawerId={activeBottomDrawerId}
activeBottomDrawerSize={activeBottomDrawerHeight}
/>
</AppLayoutVisibilityContext.Provider>
);
Expand Down
5 changes: 5 additions & 0 deletions src/app-layout/visual-refresh-toolbar/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import React from 'react';

import { BreadcrumbGroupProps } from '../../breadcrumb-group/interfaces';
import { SplitPanelSideToggleProps } from '../../internal/context/split-panel-context';
import { NonCancelableEventHandler } from '../../internal/events';
import { DrawerPosition, DrawerPositionChangeParams } from '../../internal/plugins/controllers/drawers';
import { AppLayoutProps, AppLayoutPropsWithDefaults } from '../interfaces';
import { OnChangeParams } from '../utils/use-drawers';
import { FocusControlMultipleStates, FocusControlState } from '../utils/use-focus-control';
Expand All @@ -19,6 +21,9 @@ export type InternalDrawer = AppLayoutProps.Drawer & {
defaultActive?: boolean;
isExpandable?: boolean;
ariaLabels: AppLayoutProps.Drawer['ariaLabels'] & { expandedModeButton?: string };
movable?: boolean;
position?: DrawerPosition;
onPositionChange?: NonCancelableEventHandler<DrawerPositionChangeParams>;
};

// Widgetization notice: structures in this file are shared multiple app layout instances, possibly different minor versions.
Expand Down
Loading
Loading