diff --git a/demo/e2e/screenshot.spec.ts-snapshots/button-dark.png b/demo/e2e/screenshot.spec.ts-snapshots/button-dark.png index 3e7037ef..5e19d2c2 100644 Binary files a/demo/e2e/screenshot.spec.ts-snapshots/button-dark.png and b/demo/e2e/screenshot.spec.ts-snapshots/button-dark.png differ diff --git a/demo/e2e/screenshot.spec.ts-snapshots/button.png b/demo/e2e/screenshot.spec.ts-snapshots/button.png index 741d8030..0e53ffc6 100644 Binary files a/demo/e2e/screenshot.spec.ts-snapshots/button.png and b/demo/e2e/screenshot.spec.ts-snapshots/button.png differ diff --git a/demo/e2e/screenshot.spec.ts-snapshots/duo-split-menu-flat.png b/demo/e2e/screenshot.spec.ts-snapshots/duo-split-menu-flat.png index 04733a20..c792bea8 100644 Binary files a/demo/e2e/screenshot.spec.ts-snapshots/duo-split-menu-flat.png and b/demo/e2e/screenshot.spec.ts-snapshots/duo-split-menu-flat.png differ diff --git a/demo/e2e/screenshot.spec.ts-snapshots/duo-split-menu-half-open.png b/demo/e2e/screenshot.spec.ts-snapshots/duo-split-menu-half-open.png index aadb0d0b..2373615d 100644 Binary files a/demo/e2e/screenshot.spec.ts-snapshots/duo-split-menu-half-open.png and b/demo/e2e/screenshot.spec.ts-snapshots/duo-split-menu-half-open.png differ diff --git a/demo/e2e/screenshot.spec.ts-snapshots/modal-card-dark.png b/demo/e2e/screenshot.spec.ts-snapshots/modal-card-dark.png index de307a2e..e13227a5 100644 Binary files a/demo/e2e/screenshot.spec.ts-snapshots/modal-card-dark.png and b/demo/e2e/screenshot.spec.ts-snapshots/modal-card-dark.png differ diff --git a/demo/e2e/screenshot.spec.ts-snapshots/modal-card.png b/demo/e2e/screenshot.spec.ts-snapshots/modal-card.png index 74fe686d..fc977782 100644 Binary files a/demo/e2e/screenshot.spec.ts-snapshots/modal-card.png and b/demo/e2e/screenshot.spec.ts-snapshots/modal-card.png differ diff --git a/demo/e2e/screenshot.spec.ts-snapshots/modal-normal-dark.png b/demo/e2e/screenshot.spec.ts-snapshots/modal-normal-dark.png index befc076c..7d79d04a 100644 Binary files a/demo/e2e/screenshot.spec.ts-snapshots/modal-normal-dark.png and b/demo/e2e/screenshot.spec.ts-snapshots/modal-normal-dark.png differ diff --git a/demo/e2e/screenshot.spec.ts-snapshots/modal-normal.png b/demo/e2e/screenshot.spec.ts-snapshots/modal-normal.png index 8c1b81ef..796432a1 100644 Binary files a/demo/e2e/screenshot.spec.ts-snapshots/modal-normal.png and b/demo/e2e/screenshot.spec.ts-snapshots/modal-normal.png differ diff --git a/demo/e2e/screenshot.spec.ts-snapshots/modal-sheet-dark.png b/demo/e2e/screenshot.spec.ts-snapshots/modal-sheet-dark.png index fcd10c9f..3b12557a 100644 Binary files a/demo/e2e/screenshot.spec.ts-snapshots/modal-sheet-dark.png and b/demo/e2e/screenshot.spec.ts-snapshots/modal-sheet-dark.png differ diff --git a/demo/e2e/screenshot.spec.ts-snapshots/modal-sheet.png b/demo/e2e/screenshot.spec.ts-snapshots/modal-sheet.png index 6dc36fe9..43bbf18c 100644 Binary files a/demo/e2e/screenshot.spec.ts-snapshots/modal-sheet.png and b/demo/e2e/screenshot.spec.ts-snapshots/modal-sheet.png differ diff --git a/demo/e2e/screenshot.spec.ts-snapshots/searchbar-dark.png b/demo/e2e/screenshot.spec.ts-snapshots/searchbar-dark.png index 00abd4c9..b59dce09 100644 Binary files a/demo/e2e/screenshot.spec.ts-snapshots/searchbar-dark.png and b/demo/e2e/screenshot.spec.ts-snapshots/searchbar-dark.png differ diff --git a/demo/e2e/screenshot.spec.ts-snapshots/searchbar.png b/demo/e2e/screenshot.spec.ts-snapshots/searchbar.png index 86f632ae..23ac6adb 100644 Binary files a/demo/e2e/screenshot.spec.ts-snapshots/searchbar.png and b/demo/e2e/screenshot.spec.ts-snapshots/searchbar.png differ diff --git a/demo/e2e/screenshot.spec.ts-snapshots/select-modal-dark.png b/demo/e2e/screenshot.spec.ts-snapshots/select-modal-dark.png index b31e7d03..2449510c 100644 Binary files a/demo/e2e/screenshot.spec.ts-snapshots/select-modal-dark.png and b/demo/e2e/screenshot.spec.ts-snapshots/select-modal-dark.png differ diff --git a/demo/e2e/screenshot.spec.ts-snapshots/select-modal.png b/demo/e2e/screenshot.spec.ts-snapshots/select-modal.png index 3b715bb7..33281ace 100644 Binary files a/demo/e2e/screenshot.spec.ts-snapshots/select-modal.png and b/demo/e2e/screenshot.spec.ts-snapshots/select-modal.png differ diff --git a/demo/e2e/screenshot.spec.ts-snapshots/toolbar-dark.png b/demo/e2e/screenshot.spec.ts-snapshots/toolbar-dark.png index 944345a7..cb5f1509 100644 Binary files a/demo/e2e/screenshot.spec.ts-snapshots/toolbar-dark.png and b/demo/e2e/screenshot.spec.ts-snapshots/toolbar-dark.png differ diff --git a/demo/e2e/screenshot.spec.ts-snapshots/toolbar.png b/demo/e2e/screenshot.spec.ts-snapshots/toolbar.png index 33a147cc..bfb4eae4 100644 Binary files a/demo/e2e/screenshot.spec.ts-snapshots/toolbar.png and b/demo/e2e/screenshot.spec.ts-snapshots/toolbar.png differ diff --git a/demo/e2e/screenshot.spec.ts-snapshots/vertical-bars-back-button-ltr.png b/demo/e2e/screenshot.spec.ts-snapshots/vertical-bars-back-button-ltr.png index 4781def7..a91b2767 100644 Binary files a/demo/e2e/screenshot.spec.ts-snapshots/vertical-bars-back-button-ltr.png and b/demo/e2e/screenshot.spec.ts-snapshots/vertical-bars-back-button-ltr.png differ diff --git a/demo/e2e/screenshot.spec.ts-snapshots/vertical-bars-back-button-rtl.png b/demo/e2e/screenshot.spec.ts-snapshots/vertical-bars-back-button-rtl.png index 9b5f4615..42f5626b 100644 Binary files a/demo/e2e/screenshot.spec.ts-snapshots/vertical-bars-back-button-rtl.png and b/demo/e2e/screenshot.spec.ts-snapshots/vertical-bars-back-button-rtl.png differ diff --git a/demo/e2e/screenshot.spec.ts-snapshots/vertical-bars-toolbar-actions.png b/demo/e2e/screenshot.spec.ts-snapshots/vertical-bars-toolbar-actions.png index aeaabbac..0886e70d 100644 Binary files a/demo/e2e/screenshot.spec.ts-snapshots/vertical-bars-toolbar-actions.png and b/demo/e2e/screenshot.spec.ts-snapshots/vertical-bars-toolbar-actions.png differ diff --git a/demo/src/app/index/pages/toolbar/toolbar.page.html b/demo/src/app/index/pages/toolbar/toolbar.page.html index 910d0187..c5d623af 100644 --- a/demo/src/app/index/pages/toolbar/toolbar.page.html +++ b/demo/src/app/index/pages/toolbar/toolbar.page.html @@ -106,6 +106,85 @@

toolbar

Menu Button + + + + +

Toolbar spacing

+

Compare the outer inset and spacing between visible actions.

+
+
+
+
+ + @for (slot of ['start', 'end']; track slot) { + {{ slot }}: single group + + Save + + + {{ slot }}: two groups + + Save + Edit + + + {{ slot }}: standalone button and group + + Save + Edit + + + {{ slot }}: hidden menu group and visible group + + + Save + + + {{ slot }}: hidden child and visible child + + + + Save + + Edit + + + {{ slot }}: empty group between visible groups + + Save + + Edit + + } + @for ( + slots of [ + ['start', 'secondary'], + ['primary', 'end'], + ]; + track slots[0] + ) { + {{ slots[0] }} / {{ slots[1] }} + + Save + Edit + + } + diff --git a/src/styles/components/ion-button.scss b/src/styles/components/ion-button.scss index 5a0cacfb..7de71e36 100644 --- a/src/styles/components/ion-button.scss +++ b/src/styles/components/ion-button.scss @@ -436,3 +436,50 @@ ion-button.ios:not(.ios-theme-disabled, .ios26-disabled) { ion-back-button.ios:not(.ios-theme-disabled, .ios26-disabled) { @include theme-button($is-back-button: true); } + +// Toolbar button typography and spacing follow system toolbar controls. +ion-toolbar.ios:not(.ios-theme-disabled, .ios26-disabled) { + ion-button.ios:not(.ios-theme-disabled, .ios26-disabled):not(.button-small, .button-large):not( + :where(ion-buttons.ios-theme-disabled *, ion-buttons.ios26-disabled *) + ) { + font-size: 1rem; + font-weight: 400; + // TODO(v2.0.0): Remove .back-button-has-icon-only; Ionic only adds it to ion-back-button. + &:not(.button-has-icon-only, .back-button-has-icon-only) { + --padding-start: 16px; + --padding-end: 16px; + } + } + + ion-buttons.ios:not(.ios-theme-disabled, .ios26-disabled):has(ion-button ~ ion-button):not( + :has( + ion-back-button, + ion-button:not(.button-clear), + ion-menu-button, + .ios-theme-disabled, + .ios26-disabled, + .button-small, + .button-large + ) + ) { + height: 44px; + padding-inline: 3.5px; + // SwiftUI toolbar items share glass with 16pt between their inner controls. + gap: 16px; + + ion-button.ios:not(.ios-theme-disabled, .ios26-disabled).button-clear { + // Keep the action's hit area through the group's height, while the + // inner plain control follows the system's 36px visual layout. + height: 100%; + display: inline-flex; + align-items: center; + --padding-start: 7px; + --padding-end: 7px; + &::part(native) { + width: auto; + height: 36px; + min-height: 36px; + } + } + } +} diff --git a/src/styles/components/ion-toolbar.scss b/src/styles/components/ion-toolbar.scss index 96576690..1a93a9c2 100644 --- a/src/styles/components/ion-toolbar.scss +++ b/src/styles/components/ion-toolbar.scss @@ -13,22 +13,27 @@ ion-header.ios:not(.ios-theme-disabled, .ios26-disabled) ion-toolbar:not(.ios-th ion-modal.ios:not(.ios-theme-disabled, .ios26-disabled).modal-card, ion-modal.ios:not(.ios-theme-disabled, .ios26-disabled).modal-sheet { ion-header.ios:not(.ios-theme-disabled, .ios26-disabled) ion-toolbar.ios:not(.ios-theme-disabled, .ios26-disabled):first-of-type { - padding-top: 0; + // The system's modal safe area places the 44px control 16px below the + // content edge, with or without a sheet drag indicator. + padding-top: 6px; padding-bottom: 0; --min-height: 64px; - --padding-start: 3px; - --padding-end: 3px; } } @media (min-width: 768px) and (min-height: 600px) { - ion-modal.ios:not(.ios-theme-disabled, .ios26-disabled) { + ion-modal.ios:not(.ios-theme-disabled, .ios26-disabled):not(.modal-sheet) { ion-header.ios:not(.ios-theme-disabled, .ios26-disabled) ion-toolbar.ios:not(.ios-theme-disabled, .ios26-disabled):first-of-type { - padding-top: 0; + // Center a 44px control at 10px, retaining the safe area in fullscreen modals. + padding-top: max(calc(var(--ion-safe-area-top, 0px) - 10px), 0px); padding-bottom: 0; --min-height: 64px; - --padding-start: 3px; - --padding-end: 3px; + } + } + // Ionic keeps the root safe area in card modals, including centered dialogs. + ion-modal.ios:not(.ios-theme-disabled, .ios26-disabled).modal-card { + ion-header.ios:not(.ios-theme-disabled, .ios26-disabled) ion-toolbar.ios:not(.ios-theme-disabled, .ios26-disabled):first-of-type { + padding-top: 0; } } } @@ -106,4 +111,54 @@ ion-toolbar.ios:not(.ios-theme-disabled, .ios26-disabled) { ion-buttons > ion-back-button { margin-inline: 0; } + + // Keep the outer inset for individual actions too. Adjacent actions in the + // same slot share a 12px gap rather than adding two outer margins together. + > ion-button:not(.ios-theme-disabled, .ios26-disabled) { + margin-inline: 12px; + } + // Rail sources leave toolbar layout; ordinary native projection retains it. + $visible: ':not([hidden], .ios-theme-native-ui-shell-prehidden, [data-native-ui-shell][data-native-ui-shell-vertical-bars], ion-menu-button.menu-button-hidden, ion-back-button:not(.show-back-button))'; + $button: ':is(ion-button, ion-back-button)#{$visible}'; + $group: 'ion-buttons#{$visible}'; + $action: ':is(#{$button}, #{$group}:has(> #{$visible}))'; + $optOut: ':is(.ios-theme-disabled, .ios26-disabled)'; + + > ion-buttons:not(:has(> #{$visible})) { + display: none; + } + + @each $slot in start, end, primary, secondary { + // Keep standalone margins when the slot includes a visible opted-out action. + &:not(.toolbar-searchbar):not(:has(> #{$button}#{$optOut}[slot='#{$slot}'], > #{$group}#{$optOut}[slot='#{$slot}'] > #{$visible})) { + > #{$action}[slot='#{$slot}'] { + margin-inline: 6px; + } + > :nth-child(1 of #{$action}[slot='#{$slot}']) { + margin-inline-start: 12px; + } + > :nth-last-child(1 of #{$action}[slot='#{$slot}']) { + margin-inline-end: 12px; + } + } + } + // Ionic orders these slots together, independently of their DOM order. + @each $first, $second in (start, secondary), (primary, end) { + &:not(.toolbar-searchbar):has(> #{$button}[slot='#{$first}'], > #{$group}[slot='#{$first}'] > #{$visible}):has( + > #{$button}[slot='#{$second}'], + > #{$group}[slot='#{$second}'] > #{$visible} + ):not( + :has( + > #{$button}#{$optOut}:is([slot='#{$first}'], [slot='#{$second}']), + > #{$group}#{$optOut}:is([slot='#{$first}'], [slot='#{$second}']) > #{$visible} + ) + ) { + > :nth-last-child(1 of #{$action}[slot='#{$first}']) { + margin-inline-end: 6px; + } + > :nth-child(1 of #{$action}[slot='#{$second}']) { + margin-inline-start: 6px; + } + } + } }