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
Original file line number Diff line number Diff line change
Expand Up @@ -17484,6 +17484,28 @@ render to an element under \`document.body\`.",
"optional": true,
"type": "HTMLElement",
},
{
"defaultValue": "'center'",
"description": "Controls the vertical positioning of the modal.

- \`center\` (default) - Modal is vertically centered in viewport and re-centers
when content height changes. Use for dialogs with static, predictable content.

- \`top\` - Modal anchors at fixed distance and grows downward
as content expands. Use when content changes dynamically to prevent disruptive
vertical repositioning that causes users to lose focus.",
"inlineType": {
"name": "ModalProps.Position",
"type": "union",
"values": [
"center",
"top",
],
},
"name": "position",
"optional": true,
"type": "string",
},
{
"description": "Use this property when \`getModalRoot\` is used to clean up the modal root
element after a user closes the dialog. The function receives the return value
Expand Down
9 changes: 9 additions & 0 deletions src/modal/__tests__/modal.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,15 @@
});
});

describe('position property', () => {
it('displays correct position', () => {
(['center', 'top'] as ModalProps.Position[]).forEach(position => {
const wrapper = renderModal({ position });
expect(wrapper.findDialog().getElement()).toHaveClass(styles[position]);

Check failure on line 126 in src/modal/__tests__/modal.test.tsx

View workflow job for this annotation

GitHub Actions / build (React 18) / build

Modal component › position property › displays correct position

expect(element).toHaveClass(expected) At least one expected class must be provided. at src/modal/__tests__/modal.test.tsx:126:51 at Array.forEach (<anonymous>) at Object.<anonymous> (src/modal/__tests__/modal.test.tsx:124:52)

Check failure on line 126 in src/modal/__tests__/modal.test.tsx

View workflow job for this annotation

GitHub Actions / build / build

Modal component › position property › displays correct position

expect(element).toHaveClass(expected) At least one expected class must be provided. at src/modal/__tests__/modal.test.tsx:126:51 at Array.forEach (<anonymous>) at Object.<anonymous> (src/modal/__tests__/modal.test.tsx:124:52)

Check failure on line 126 in src/modal/__tests__/modal.test.tsx

View workflow job for this annotation

GitHub Actions / dry-run / Components unit tests

Modal component › position property › displays correct position

expect(element).toHaveClass(expected) At least one expected class must be provided. at src/modal/__tests__/modal.test.tsx:126:51 at Array.forEach (<anonymous>) at Object.<anonymous> (src/modal/__tests__/modal.test.tsx:124:52)
});
});
});

describe('dismiss on click', () => {
it('closes the dialog when clicked on the overlay section of the container', () => {
const onDismissSpy = jest.fn();
Expand Down
14 changes: 12 additions & 2 deletions src/modal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,15 @@ function ModalWithAnalyticsFunnel({
);
}

export default function Modal({ size = 'medium', ...props }: ModalProps) {
export default function Modal({ size = 'medium', position = 'center', ...props }: ModalProps) {
const { isInFunnel } = useFunnel();
const analyticsMetadata = getAnalyticsMetadataProps(props as BasePropsWithAnalyticsMetadata);
const baseComponentProps = useBaseComponent(
'Modal',
{
props: {
size,
position,
disableContentPaddings: props.disableContentPaddings,
flowType: analyticsMetadata.flowType,
},
Expand All @@ -95,12 +96,21 @@ export default function Modal({ size = 'medium', ...props }: ModalProps) {
analyticsMetadata={analyticsMetadata}
baseComponentProps={baseComponentProps}
size={size}
position={position}
{...props}
/>
);
}

return <InternalModal size={size} {...props} {...baseComponentProps} __injectAnalyticsComponentMetadata={true} />;
return (
<InternalModal
size={size}
position={position}
{...props}
{...baseComponentProps}
__injectAnalyticsComponentMetadata={true}
/>
);
}

applyDisplayName(Modal, 'Modal');
12 changes: 12 additions & 0 deletions src/modal/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ export interface ModalProps extends BaseComponentProps, BaseModalProps {
* `small` (320px), `medium` (600px), `large` (820px), `x-large` (1024px), `xx-large` (1280px).
*/
size?: ModalProps.Size;
/**
* Controls the vertical positioning of the modal.
*
* - `center` (default) - Modal is vertically centered in viewport and re-centers
* when content height changes. Use for dialogs with static, predictable content.
*
* - `top` - Modal anchors at fixed distance and grows downward
* as content expands. Use when content changes dynamically to prevent disruptive
* vertical repositioning that causes users to lose focus.
*/
position?: ModalProps.Position;
/**
* Determines whether the modal is displayed on the screen. Modals are hidden by default.
* Set this property to `true` to show them.
Expand Down Expand Up @@ -82,6 +93,7 @@ export interface ModalProps extends BaseComponentProps, BaseModalProps {

export namespace ModalProps {
export type Size = 'small' | 'medium' | 'large' | 'x-large' | 'xx-large' | 'max';
export type Position = 'center' | 'top';

export interface DismissDetail {
reason: string;
Expand Down
8 changes: 7 additions & 1 deletion src/modal/internal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ function PortaledModal({
children,
footer,
disableContentPaddings,
position = 'center',
onButtonClick = () => {},
onDismiss,
__internalRootRef,
Expand Down Expand Up @@ -247,7 +248,12 @@ function PortaledModal({
style={footerHeight ? { scrollPaddingBottom: footerHeight } : undefined}
data-awsui-referrer-id={subStepRef.current?.id || referrerId}
>
<FocusLock disabled={!visible} autoFocus={true} restoreFocus={true} className={styles['focus-lock']}>
<FocusLock
disabled={!visible}
autoFocus={true}
restoreFocus={true}
className={clsx(styles['focus-lock'], position === 'top' && styles['position-top'])}
>
<div
className={clsx(
styles.dialog,
Expand Down
4 changes: 4 additions & 0 deletions src/modal/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ $modal-z-index: 5000;
padding-inline: 0;
z-index: $modal-z-index;
pointer-events: none;

&.position-top {
margin-block-start: awsui.$space-s;
}
}

.dialog {
Expand Down
Loading