feat(modal): add default ionic theme styles (#29876)

Issue number: resolves internal

---------

<!-- 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. -->

No default modal styles for the Ionic theme

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

- Splits common and native styles into separate stylesheets
- Adds default styles to modals for Ionic theme

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

<!--
  If this introduces a breaking change:
1. Describe the impact and migration path for existing applications
below.
  2. Update the BREAKING.md file with the breaking change.
3. Add "BREAKING CHANGE: [...]" to the commit description when merging.
See
https://github.com/ionic-team/ionic-framework/blob/main/docs/CONTRIBUTING.md#footer
for more information.
-->


## Other information

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

I only applied some of the global styles to the sheet modal vairation
since it is unclear whether the card variation will be used in the
widgets
This commit is contained in:
Tanner Reits
2024-09-20 15:26:56 -04:00
committed by GitHub
parent 05cf900fa2
commit bde1d0978d
36 changed files with 217 additions and 64 deletions

View File

@ -1,6 +1,6 @@
@import "./modal.vars";
@import "../../themes/mixins.scss";
// Modals
// Modals: Common Styles
// --------------------------------------------------
:host {
@ -29,13 +29,10 @@
--min-height: auto;
--max-height: auto;
--overflow: hidden;
--border-radius: 0;
--border-width: 0;
--border-style: none;
--border-color: transparent;
--background: #{$background-color};
--box-shadow: none;
--backdrop-opacity: 0;
@include position(0, 0, 0, 0);
@ -47,8 +44,6 @@
outline: none;
color: $modal-text-color;
contain: strict;
}
@ -90,37 +85,14 @@ ion-backdrop {
background: transparent;
}
@media only screen and (min-width: $modal-inset-min-width) and (min-height: $modal-inset-min-height-small) {
:host {
--width: #{$modal-inset-width};
--height: #{$modal-inset-height-small};
--ion-safe-area-top: 0px;
--ion-safe-area-bottom: 0px;
--ion-safe-area-right: 0px;
--ion-safe-area-left: 0px;
}
}
@media only screen and (min-width: $modal-inset-min-width) and (min-height: $modal-inset-min-height-large) {
:host {
--width: #{$modal-inset-width};
--height: #{$modal-inset-height-large};
}
}
// Sheet Modal
// --------------------------------------------------
.modal-handle {
@include position(5px, 0px, null, 0px);
@include border-radius(8px, 8px, 8px, 8px);
@include margin(null, auto, null, auto);
position: absolute;
width: 36px;
height: 5px;
/**
* This allows the handle to appear
* on top of user content in WebKit.
@ -129,8 +101,6 @@ ion-backdrop {
border: 0;
background: var(--ion-color-step-350, var(--ion-background-color-step-350, #c0c0be));
cursor: pointer;
z-index: 11;
@ -144,9 +114,6 @@ ion-backdrop {
position: absolute;
width: 36px;
height: 5px;
transform: translate(-50%, -50%);
content: "";

View File

@ -1,11 +1,15 @@
@use "../../themes/ionic/ionic.globals.scss" as globals;
@import "./modal";
@use "./modal.common";
// Ionic Modal
// --------------------------------------------------
:host {
--background: #{globals.$ionic-color-base-white};
--box-shadow: #{globals.$ionic-elevation-300};
--backdrop-opacity: 1;
color: globals.$ionic-color-neutral-1200;
}
// Shape
@ -21,3 +25,20 @@
:host(.modal-rectangular) {
--border-radius: #{globals.$ionic-border-radius-0};
}
// Sheet Modal
// --------------------------------------------------
.modal-handle {
@include globals.position(globals.$ionic-space-300, 0px, null, 0px);
@include globals.border-radius(globals.$ionic-border-radius-100);
width: globals.$ionic-scale-1100;
height: globals.$ionic-scale-100;
background-color: globals.$ionic-color-neutral-300;
}
:host(.modal-sheet) .modal-wrapper {
@include globals.border-radius(var(--border-radius), var(--border-radius), 0, 0);
}

View File

@ -1,4 +1,4 @@
@import "./modal";
@import "./modal.native";
@import "./modal.ios.vars";
// iOS Modals

View File

@ -1,4 +1,4 @@
@import "./modal";
@import "./modal.native";
@import "./modal.md.vars";
// Material Design Modals

View File

@ -0,0 +1,50 @@
@use "./modal.common";
@use "./modal.vars" as vars;
@forward "./modal.vars.scss";
// Modals: Native Styles
// --------------------------------------------------
:host {
--background: #{vars.$background-color};
--border-radius: 0;
--backdrop-opacity: 0;
color: vars.$modal-text-color;
}
@media only screen and (min-width: vars.$modal-inset-min-width) and (min-height: vars.$modal-inset-min-height-small) {
:host {
--width: #{vars.$modal-inset-width};
--height: #{vars.$modal-inset-height-small};
--ion-safe-area-top: 0px;
--ion-safe-area-bottom: 0px;
--ion-safe-area-right: 0px;
--ion-safe-area-left: 0px;
}
}
@media only screen and (min-width: vars.$modal-inset-min-width) and (min-height: vars.$modal-inset-min-height-large) {
:host {
--width: #{vars.$modal-inset-width};
--height: #{vars.$modal-inset-height-large};
}
}
// Sheet Modal
// --------------------------------------------------
.modal-handle {
@include vars.position(5px, 0px, null, 0px);
@include vars.border-radius(8px, 8px, 8px, 8px);
width: 36px;
height: 5px;
background: var(--ion-color-step-350, var(--ion-background-color-step-350, #c0c0be));
&::before {
width: 36px;
height: 5px;
}
}

View File

@ -14,10 +14,6 @@
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
<style>
ion-modal {
--box-shadow: 0px 0px 30px 10px rgba(0, 0, 0, 0.1);
}
.container {
margin-bottom: 20px;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -72,6 +72,10 @@
.grid-item {
height: 200px;
}
.half-sheet {
--height: 50%;
}
</style>
</head>
@ -140,6 +144,13 @@
Backdrop is inactive
</button>
<button
id="half-sheet"
onclick="presentHalfSheetModal({ cssClass: 'half-sheet', backdropBreakpoint: 0.5, initialBreakpoint: 1, breakpoints: [1] })"
>
Half Sheet w/ Footer
</button>
<div class="grid">
<div class="grid-item red"></div>
<div class="grid-item green"></div>
@ -214,12 +225,65 @@
modal.remove();
}
async function presentCardModal() {
const presentingEl = document.querySelectorAll('.ion-page')[1];
const modal = createModal('card', {
presentingElement: presentingEl,
async function presentHalfSheetModal(options) {
let items = '';
for (var i = 0; i < 3; i++) {
items += `<ion-item>Item ${i}</ion-item>`;
}
// create component to open
const element = document.createElement('div');
element.innerHTML = `
<ion-header>
<ion-toolbar>
<ion-title>Super Modal</ion-title>
<ion-buttons slot="end">
<ion-button class="dismiss">Dismiss Modal</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
${items}
</ion-list>
</ion-content>
<ion-footer>
<ion-toolbar>
<ion-button expand="block">Action</ion-button>
</ion-toolbar>
</ion-footer>
`;
let extraOptions = {
initialBreakpoint: 0.25,
breakpoints: [0, 0.25, 0.5, 0.75, 1],
};
if (options) {
extraOptions = {
...extraOptions,
...options,
};
}
// present the modal
const modalElement = Object.assign(document.createElement('ion-modal'), {
component: element,
...extraOptions,
});
await modal.present();
// listen for close event
const button = element.querySelector('ion-button');
button.addEventListener('click', () => {
modalElement.dismiss();
});
document.body.appendChild(modalElement);
await modalElement.present();
await modalElement.onDidDismiss();
modalElement.remove();
}
</script>
</body>

View File

@ -25,6 +25,30 @@ configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
});
});
configs({ modes: ['ionic-md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
test.describe(title('sheet modal: half screen rendering'), () => {
test('should not have visual regressions', async ({ page }) => {
await page.goto('/src/components/modal/test/sheet', config);
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
await page.click('#half-sheet');
await ionModalDidPresent.next();
await expect(page).toHaveScreenshot(screenshot(`modal-half-sheet-present`), {
/**
* Animations must be enabled to capture the screenshot.
* By default, animations are disabled with toHaveScreenshot,
* and when capturing the screenshot will call animation.finish().
* This will cause the modal to close and the screenshot capture
* to be invalid.
*/
animations: 'allow',
});
});
});
});
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('sheet modal: backdrop'), () => {
test.beforeEach(async ({ page }) => {

View File

@ -36,37 +36,68 @@ body.backdrop-no-scroll {
// Modal - Card Style
// --------------------------------------------------
html.ionic ion-modal ion-header {
box-shadow: none;
}
/**
* Card style modal needs additional padding on the
* Modals need additional padding on the
* top of the header. We accomplish this by targeting
* the first toolbar in the header.
* Footer also needs this. We do not adjust the bottom
* padding though because of the safe area.
*/
html.ios ion-modal.modal-card ion-header ion-toolbar:first-of-type,
html.ios ion-modal.modal-sheet ion-header ion-toolbar:first-of-type,
html.ios ion-modal ion-footer ion-toolbar:first-of-type {
padding-top: globals.$ionic-space-150;
html.ionic ion-modal.modal-card ion-header ion-toolbar:first-of-type,
html.ionic ion-modal.modal-sheet ion-header ion-toolbar:first-of-type,
html.ionic ion-modal ion-footer ion-toolbar:first-of-type {
padding-top: globals.$ionic-space-400;
}
/**
* Card style modal needs additional padding on the
* Modals need additional padding on the
* bottom of the header. We accomplish this by targeting
* the last toolbar in the header.
*/
html.ios ion-modal.modal-card ion-header ion-toolbar:last-of-type,
html.ios ion-modal.modal-sheet ion-header ion-toolbar:last-of-type {
padding-bottom: globals.$ionic-space-150;
html.ionic ion-modal.modal-card ion-header ion-toolbar:last-of-type,
html.ionic ion-modal.modal-sheet ion-header ion-toolbar:last-of-type,
html.ionic ion-modal ion-footer ion-toolbar:last-of-type {
padding-bottom: globals.$ionic-space-400;
}
/**
* Add padding on the left and right
* of toolbars while accounting for
* safe area values when in landscape.
* Add additional padding to the left and right of toolbars while accounting
* for the safe area insets.
*/
html.ios ion-modal ion-toolbar {
padding-right: calc(var(--ion-safe-area-right) + globals.$ionic-space-200);
padding-left: calc(var(--ion-safe-area-left) + globals.$ionic-space-200);
html.ionic ion-modal ion-toolbar {
--padding-start: calc(var(--ion-safe-area-right) + #{globals.$ionic-space-400});
--padding-end: calc(var(--ion-safe-area-left) + #{globals.$ionic-space-400});
}
/**
* Center an `ion-title` within a modal header. This works around
* limitations with `:host-context()` not being supported in all browsers.
*/
html.ionic ion-modal ion-header ion-toolbar ion-title {
@include globals.position(0, null, null, 0);
position: absolute;
width: 100%;
height: 100%;
transform: translateZ(0);
text-align: center;
}
/**
* Add padding to the left, right, and bottom of `ion-content`
* within a modal.
*/
html.ionic ion-modal.modal-sheet ion-content {
--padding-start: #{globals.$ionic-space-400};
--padding-end: #{globals.$ionic-space-400};
--padding-bottom: #{globals.$ionic-space-400};
}
/**
@ -74,7 +105,7 @@ html.ios ion-modal ion-toolbar {
* should only have backdrop on first instance.
*/
@media screen and (min-width: 768px) {
html.ios ion-modal.modal-card:first-of-type {
html.ionic ion-modal.modal-card:first-of-type {
--backdrop-opacity: 0.18;
}
}
@ -121,7 +152,7 @@ ion-modal.modal-default.show-modal ~ ion-modal.modal-default {
* radius no matter the platform.
* This behavior only applies to card modals.
*/
html.ios ion-modal.modal-card .ion-page {
html.ionic ion-modal.modal-card .ion-page {
border-top-left-radius: var(--border-radius);
}