fix(react, vue): inline modals apply ion-page class (#27481)

Issue number: resolves #27470

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying. -->

Passing multiple elements in to an inline modal causes `.ion-page` to
not get set. This causes content to get pushed off the bottom of the
modal equal to the height of the header. React has some special CSS that
prevents this:
eb2772c0ce/packages/react/src/components/createInlineOverlayComponent.tsx (L137-L140)

However, I think this should be delegated to `.ion-page` instead so the
behavior is consistent across frameworks. For example, Angular uses
`.ion-page`:
eb2772c0ce/angular/src/directives/overlays/modal.ts (L82)

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->

- Inline overlays in Ionic React and Ionic Vue wrap child content in
`.ion-delegate-host.ion-page`.
- Removed the custom flex styles from Ionic React as `.ion-page` has its
own styles.

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

<!-- If this introduces a breaking change, please describe the impact
and migration path for existing applications below. -->


## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->

Revised Design Doc:
https://github.com/ionic-team/ionic-framework-design-documents/pull/84
This commit is contained in:
Liam DeBeasi
2023-05-24 09:37:02 -04:00
committed by GitHub
parent e114fe40d0
commit 02678f3652
11 changed files with 107 additions and 14 deletions

View File

@ -25,7 +25,8 @@ function generateOverlays() {
},
{
tag: 'ion-modal',
name: 'IonModal'
name: 'IonModal',
hasDelegateHost: true
},
{
tag: 'ion-popover',
@ -44,8 +45,10 @@ function generateOverlays() {
componentImports.push(`import { defineCustomElement as ${defineCustomElementFn} } from '@ionic/core/components/${component.tag}.js'`);
const delegateHostString = component.hasDelegateHost ? ', true' : '';
componentDefinitions.push(`
export const ${component.name} = /*@__PURE__*/ defineOverlayContainer<JSX.${component.name}>('${component.tag}', ${defineCustomElementFn}, [${props.join(', ')}]);
export const ${component.name} = /*@__PURE__*/ defineOverlayContainer<JSX.${component.name}>('${component.tag}', ${defineCustomElementFn}, [${props.join(', ')}]${delegateHostString});
`);
});

View File

@ -27,7 +27,7 @@ export const IonPicker = /*@__PURE__*/ defineOverlayContainer<JSX.IonPicker>('io
export const IonToast = /*@__PURE__*/ defineOverlayContainer<JSX.IonToast>('ion-toast', defineIonToastCustomElement, ['animated', 'buttons', 'color', 'cssClass', 'duration', 'enterAnimation', 'header', 'htmlAttributes', 'icon', 'isOpen', 'keyboardClose', 'layout', 'leaveAnimation', 'message', 'mode', 'position', 'translucent', 'trigger']);
export const IonModal = /*@__PURE__*/ defineOverlayContainer<JSX.IonModal>('ion-modal', defineIonModalCustomElement, ['animated', 'backdropBreakpoint', 'backdropDismiss', 'breakpoints', 'canDismiss', 'enterAnimation', 'handle', 'handleBehavior', 'htmlAttributes', 'initialBreakpoint', 'isOpen', 'keepContentsMounted', 'keyboardClose', 'leaveAnimation', 'mode', 'presentingElement', 'showBackdrop', 'trigger']);
export const IonModal = /*@__PURE__*/ defineOverlayContainer<JSX.IonModal>('ion-modal', defineIonModalCustomElement, ['animated', 'backdropBreakpoint', 'backdropDismiss', 'breakpoints', 'canDismiss', 'enterAnimation', 'handle', 'handleBehavior', 'htmlAttributes', 'initialBreakpoint', 'isOpen', 'keepContentsMounted', 'keyboardClose', 'leaveAnimation', 'mode', 'presentingElement', 'showBackdrop', 'trigger'], true);
export const IonPopover = /*@__PURE__*/ defineOverlayContainer<JSX.IonPopover>('ion-popover', defineIonPopoverCustomElement, ['alignment', 'animated', 'arrow', 'backdropDismiss', 'component', 'componentProps', 'dismissOnSelect', 'enterAnimation', 'event', 'htmlAttributes', 'isOpen', 'keepContentsMounted', 'keyboardClose', 'leaveAnimation', 'mode', 'reference', 'showBackdrop', 'side', 'size', 'translucent', 'trigger', 'triggerAction']);

View File

@ -9,7 +9,7 @@ export interface OverlayProps {
const EMPTY_PROP = Symbol();
const DEFAULT_EMPTY_PROP = { default: EMPTY_PROP };
export const defineOverlayContainer = <Props extends object>(name: string, defineCustomElement: () => void, componentProps: string[] = [], controller?: any) => {
export const defineOverlayContainer = <Props extends object>(name: string, defineCustomElement: () => void, componentProps: string[] = [], hasDelegateHost?: boolean, controller?: any) => {
const createControllerComponent = () => {
return defineComponent<Props & OverlayProps>((props, { slots, emit }) => {
@ -162,10 +162,22 @@ export const defineOverlayContainer = <Props extends object>(name: string, defin
}
}
/**
* Some overlays need a wrapper element so content
* takes up the full size of the parent overlay.
*/
const renderChildren = () => {
if (hasDelegateHost) {
return h('div', { className: 'ion-delegate-host ion-page' }, slots);
}
return slots;
}
return h(
name,
{ ...restOfProps, ref: elementRef },
(isOpen.value || restOfProps.keepContentsMounted) ? slots : undefined
(isOpen.value || restOfProps.keepContentsMounted) ? renderChildren() : undefined
)
}
});

View File

@ -29,6 +29,10 @@ const routes: Array<RouteRecordRaw> = [
path: '/overlays',
component: () => import('@/views/Overlays.vue')
},
{
path: '/modal-multiple-children',
component: () => import('@/views/ModalMultipleChildren.vue')
},
{
path: '/keep-contents-mounted',
component: () => import('@/views/OverlaysKeepContentsMounted.vue')

View File

@ -0,0 +1,16 @@
<template>
<ion-page data-pageid="modal-multiple-children">
<ion-content class="ion-padding" :fullscreen="true">
<ion-button id="show-modal">Show Modal</ion-button>
<ion-modal trigger="show-modal">
<div class="child-content">Content A</div>
<div class="child-content">Content B</div>
</ion-modal>
</ion-content>
</ion-page>
</template>
<script setup lang="ts">
import { IonButton, IonContent, IonPage, IonModal } from '@ionic/vue';
</script>

View File

@ -0,0 +1,12 @@
describe('modal - multiple children', () => {
it('should render a root .ion-page when passed multiple children', () => {
cy.visit('/modal-multiple-children');
cy.get('ion-button#show-modal').click();
cy.get('ion-modal').should('be.visible');
cy.get('ion-modal .ion-page').should('have.length', 1);
cy.get('ion-modal .ion-page .child-content').should('have.length', 2);
});
})