diff --git a/core/src/components/action-sheet/action-sheet.common.scss b/core/src/components/action-sheet/action-sheet.common.scss index 7e857346ac9..efc6c2e43b2 100644 --- a/core/src/components/action-sheet/action-sheet.common.scss +++ b/core/src/components/action-sheet/action-sheet.common.scss @@ -243,10 +243,18 @@ align-items: center; } +.action-sheet-button-label-has-rich-content, .select-option-content { flex: 1; } +.select-option-start, +.select-option-end { + display: flex; + + align-items: center; +} + .select-option-description { display: block; } diff --git a/core/src/components/action-sheet/action-sheet.ionic.scss b/core/src/components/action-sheet/action-sheet.ionic.scss index b2c749d4e0a..486f06e1c77 100644 --- a/core/src/components/action-sheet/action-sheet.ionic.scss +++ b/core/src/components/action-sheet/action-sheet.ionic.scss @@ -1,10 +1,96 @@ @use "../../themes/ionic/ionic.globals.scss" as globals; +@use "../../themes/mixins" as mixins; +@use "../../themes/functions.font" as fontMixins; @use "./action-sheet.common"; -@use "./action-sheet.md" as action-sheet-md; // Ionic Action Sheet // -------------------------------------------------- +:host { + --background: #{globals.$ion-bg-surface-default}; + --backdrop-opacity: 0.7; + --button-background: transparent; + --button-background-selected: currentColor; + --button-background-selected-opacity: 0; + --button-background-activated: transparent; + --button-background-activated-opacity: 0; + --button-background-hover: currentColor; + --button-background-hover-opacity: 0.04; + --button-background-focused: currentColor; + --button-background-focused-opacity: 0.12; + --button-color: #{globals.$ion-text-default}; + --button-color-disabled: var(--button-color); + --color: #{globals.$ion-text-default}; +} + +// Action Sheet Wrapper +// ----------------------------------------- + +.action-sheet-wrapper { + @include mixins.margin(var(--ion-safe-area-top, 0), auto, 0, auto); +} + +.action-sheet-title { + @include mixins.padding(globals.$ion-scale-400); + @include globals.typography(globals.$ion-heading-h6-medium); + + color: var(--color); + + text-align: start; +} + +.action-sheet-sub-title { + @include globals.typography(globals.$ion-body-lg-regular); +} + +// Action Sheet Group +// ----------------------------------------- + +.action-sheet-group:first-child { + @include mixins.padding(globals.$ion-scale-400, null, null, null); +} + +.action-sheet-group:last-child { + @include mixins.padding(null, null, globals.$ion-scale-400, null); +} + +// Action Sheet Buttons +// ----------------------------------------- + +.action-sheet-button { + @include mixins.padding( + globals.$ion-scale-300, + globals.$ion-scale-400, + globals.$ion-scale-300, + globals.$ion-scale-400 + ); + @include globals.typography(globals.$ion-body-md-regular); + + position: relative; + + min-height: 52px; + + text-align: start; + + contain: content; + overflow: hidden; +} + +.action-sheet-icon { + @include mixins.margin(globals.$ion-scale-0, globals.$ion-scale-600, globals.$ion-scale-0, globals.$ion-scale-0); + @include globals.typography(globals.$ion-body-md-regular); + + color: var(--color, globals.$ion-text-default); +} + +.action-sheet-button-inner { + justify-content: flex-start; +} + +.action-sheet-selected { + font-weight: bold; +} + // Action Sheet: Select Option // -------------------------------------------------- @@ -12,11 +98,14 @@ gap: globals.$ion-space-300; } +.select-option-start, +.select-option-end { + gap: globals.$ion-space-200; +} + .select-option-description { @include globals.typography(globals.$ion-body-md-regular); @include globals.padding(0); color: globals.$ion-text-subtle; - - font-size: globals.$ion-font-size-350; } diff --git a/core/src/components/action-sheet/action-sheet.md.scss b/core/src/components/action-sheet/action-sheet.md.scss index e46f06085b3..900efa05ec8 100644 --- a/core/src/components/action-sheet/action-sheet.md.scss +++ b/core/src/components/action-sheet/action-sheet.md.scss @@ -1,7 +1,7 @@ @import "./action-sheet.native"; @import "./action-sheet.md.vars"; -// Material Design Action Sheet Title +// Material Design Action Sheet // ----------------------------------------- :host { diff --git a/core/src/components/action-sheet/action-sheet.native.scss b/core/src/components/action-sheet/action-sheet.native.scss index affa6aeb126..a58d152753b 100644 --- a/core/src/components/action-sheet/action-sheet.native.scss +++ b/core/src/components/action-sheet/action-sheet.native.scss @@ -10,6 +10,11 @@ gap: 12px; } +.select-option-start, +.select-option-end { + gap: 8px; +} + .select-option-description { @include mixins.padding(5px, 0, 0, 0); diff --git a/core/src/components/alert/alert.common.scss b/core/src/components/alert/alert.common.scss index 84e35eca5c3..4f6a22cef37 100644 --- a/core/src/components/alert/alert.common.scss +++ b/core/src/components/alert/alert.common.scss @@ -262,6 +262,13 @@ textarea.alert-input { flex: 1; } +.select-option-start, +.select-option-end { + display: flex; + + align-items: center; +} + .select-option-description { display: block; } diff --git a/core/src/components/alert/alert.ionic.scss b/core/src/components/alert/alert.ionic.scss index 3c54136b477..a06c0bd28e6 100644 --- a/core/src/components/alert/alert.ionic.scss +++ b/core/src/components/alert/alert.ionic.scss @@ -13,6 +13,11 @@ gap: globals.$ion-space-300; } +.select-option-start, +.select-option-end { + gap: globals.$ion-space-200; +} + .select-option-description { @include globals.typography(globals.$ion-body-md-regular); @include globals.padding(0); diff --git a/core/src/components/alert/alert.native.scss b/core/src/components/alert/alert.native.scss index e2d5a87b8a5..9cfb0e327ab 100644 --- a/core/src/components/alert/alert.native.scss +++ b/core/src/components/alert/alert.native.scss @@ -11,6 +11,11 @@ gap: 12px; } +.select-option-start, +.select-option-end { + gap: 8px; +} + .select-option-description { @include mixins.padding(5px, 0, 0, 0); diff --git a/core/src/components/select-modal/select-modal.common.scss b/core/src/components/select-modal/select-modal.common.scss index 3bbb48b557d..3716e27ff22 100644 --- a/core/src/components/select-modal/select-modal.common.scss +++ b/core/src/components/select-modal/select-modal.common.scss @@ -14,6 +14,26 @@ align-items: center; } +ion-radio.select-option-has-rich-content::part(label), +ion-checkbox.select-option-has-rich-content::part(label), +.select-option-content { + flex: 1; + + /** + * Let rich content wrap instead of inheriting the label part's + * single-line truncation, so arbitrary slotted elements render + * without clipping. + */ + white-space: normal; +} + +.select-option-start, +.select-option-end { + display: flex; + + align-items: center; +} + .select-option-description { display: block; } diff --git a/core/src/components/select-modal/select-modal.ionic.scss b/core/src/components/select-modal/select-modal.ionic.scss index ca137a075d3..8a7483b9126 100644 --- a/core/src/components/select-modal/select-modal.ionic.scss +++ b/core/src/components/select-modal/select-modal.ionic.scss @@ -85,6 +85,11 @@ ion-content { gap: globals.$ion-space-300; } +.select-option-start, +.select-option-end { + gap: globals.$ion-space-200; +} + .select-option-description { @include globals.typography(globals.$ion-body-md-regular); diff --git a/core/src/components/select-modal/select-modal.native.scss b/core/src/components/select-modal/select-modal.native.scss index 29b81819fcf..59f44fd9bbe 100644 --- a/core/src/components/select-modal/select-modal.native.scss +++ b/core/src/components/select-modal/select-modal.native.scss @@ -10,6 +10,11 @@ gap: 12px; } +.select-option-start, +.select-option-end { + gap: 8px; +} + .select-option-description { @include mixins.padding(5px, 0, 0, 0); diff --git a/core/src/components/select-modal/select-modal.tsx b/core/src/components/select-modal/select-modal.tsx index ca21e73a1f0..dc5db517ce1 100644 --- a/core/src/components/select-modal/select-modal.tsx +++ b/core/src/components/select-modal/select-modal.tsx @@ -127,6 +127,7 @@ export class SelectModal implements ComponentInterface { * part of the public `SelectModalOption` interface. */ const richOption = option as SelectOverlayOption; + const hasRichContent = !!richOption.startContent || !!richOption.endContent || !!richOption.description; const optionLabelOptions = { id: `modal-option-${index}`, label: richOption.text, @@ -145,6 +146,9 @@ export class SelectModal implements ComponentInterface { }} > this.dismissParentPopover()} diff --git a/core/src/components/select/test/rich-content-option/index.html b/core/src/components/select/test/rich-content-option/index.html index 7bdf2881d3a..907f5d0611b 100644 --- a/core/src/components/select/test/rich-content-option/index.html +++ b/core/src/components/select/test/rich-content-option/index.html @@ -54,7 +54,7 @@ - + NEW @@ -98,11 +98,22 @@ NEW + + + + + This is a very long option label that demonstrates how the start and end slots stay at their intrinsic + widths while the content area absorbs the remaining row width + NEW + - + NEW @@ -146,11 +157,22 @@ NEW + + + + + This is a very long option label that demonstrates how the start and end slots stay at their intrinsic + widths while the content area absorbs the remaining row width + NEW + - + NEW @@ -194,11 +216,22 @@ NEW + + + + + This is a very long option label that demonstrates how the start and end slots stay at their intrinsic + widths while the content area absorbs the remaining row width + NEW + - + NEW @@ -242,6 +275,17 @@ NEW + + + + + This is a very long option label that demonstrates how the start and end slots stay at their intrinsic + widths while the content area absorbs the remaining row width + NEW + @@ -296,11 +340,22 @@ NEW + + + + + This is a very long option label that demonstrates how the start and end slots stay at their intrinsic + widths while the content area absorbs the remaining row width + NEW + - + NEW @@ -344,11 +399,22 @@ NEW + + + + + This is a very long option label that demonstrates how the start and end slots stay at their intrinsic + widths while the content area absorbs the remaining row width + NEW + - + NEW @@ -392,6 +458,17 @@ NEW + + + + + This is a very long option label that demonstrates how the start and end slots stay at their intrinsic + widths while the content area absorbs the remaining row width + NEW + diff --git a/core/src/components/select/test/rich-content-option/select.e2e.ts b/core/src/components/select/test/rich-content-option/select.e2e.ts index 78a2fa4e4a1..e73ff85e3d8 100644 --- a/core/src/components/select/test/rich-content-option/select.e2e.ts +++ b/core/src/components/select/test/rich-content-option/select.e2e.ts @@ -1,6 +1,54 @@ import { expect } from '@playwright/test'; import { configs, test } from '@utils/test/playwright'; +configs().forEach(({ title, screenshot, config }) => { + test.describe(title('select: rich content options (visual checks)'), () => { + test.beforeEach(async ({ page }) => { + await page.goto('/src/components/select/test/rich-content-option', config); + }); + + test('should not have visual regressions for the alert interface', async ({ page }) => { + const ionAlertDidPresent = await page.spyOnEvent('ionAlertDidPresent'); + + await page.locator('#alert-select').click(); + await ionAlertDidPresent.next(); + + const alert = page.locator('ion-alert'); + await expect(alert).toHaveScreenshot(screenshot(`select-rich-content-alert`)); + }); + + test('should not have visual regressions for the action sheet interface', async ({ page }) => { + const ionActionSheetDidPresent = await page.spyOnEvent('ionActionSheetDidPresent'); + + await page.locator('#action-sheet-select').click(); + await ionActionSheetDidPresent.next(); + + const actionSheet = page.locator('ion-action-sheet'); + await expect(actionSheet).toHaveScreenshot(screenshot(`select-rich-content-action-sheet`)); + }); + + test('should not have visual regressions for the popover interface', async ({ page }) => { + const ionPopoverDidPresent = await page.spyOnEvent('ionPopoverDidPresent'); + + await page.locator('#popover-select').click(); + await ionPopoverDidPresent.next(); + + const popover = page.locator('ion-popover'); + await expect(popover).toHaveScreenshot(screenshot(`select-rich-content-popover`)); + }); + + test('should not have visual regressions for the modal interface', async ({ page }) => { + const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); + + await page.locator('#modal-select').click(); + await ionModalDidPresent.next(); + + const modal = page.locator('ion-modal'); + await expect(modal).toHaveScreenshot(screenshot(`select-rich-content-modal`)); + }); + }); +}); + /** * This behavior does not vary across modes/directions */ diff --git a/core/src/utils/select-option-render.tsx b/core/src/utils/select-option-render.tsx index a8e11e3302f..abd7dae4d16 100644 --- a/core/src/utils/select-option-render.tsx +++ b/core/src/utils/select-option-render.tsx @@ -109,7 +109,7 @@ export const renderOptionLabel = ( // Render label with rich content (start, end, description) return ( - + {startContent && renderClonedContent(id, startContent, 'select-option-start', useSpan)} {labelEl}