diff --git a/angular/src/directives/proxies.ts b/angular/src/directives/proxies.ts
index 45ccbabe63..c15791990f 100644
--- a/angular/src/directives/proxies.ts
+++ b/angular/src/directives/proxies.ts
@@ -75,7 +75,7 @@ export class IonButton {
proxyInputs(IonButton, ['buttonType', 'color', 'disabled', 'download', 'expand', 'fill', 'href', 'mode', 'rel', 'routerDirection', 'shape', 'size', 'strong', 'target', 'type']);
export declare interface IonButtons extends Components.IonButtons {}
-@Component({ selector: 'ion-buttons', changeDetection: ChangeDetectionStrategy.OnPush, template: '' })
+@Component({ selector: 'ion-buttons', changeDetection: ChangeDetectionStrategy.OnPush, template: '', inputs: ['collapse'] })
export class IonButtons {
protected el: HTMLElement;
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
@@ -83,6 +83,7 @@ export class IonButtons {
this.el = r.nativeElement;
}
}
+proxyInputs(IonButtons, ['collapse']);
export declare interface IonCard extends Components.IonCard {}
@Component({ selector: 'ion-card', changeDetection: ChangeDetectionStrategy.OnPush, template: '', inputs: ['button', 'color', 'disabled', 'download', 'href', 'mode', 'rel', 'routerDirection', 'target', 'type'] })
@@ -269,7 +270,7 @@ export class IonGrid {
proxyInputs(IonGrid, ['fixed']);
export declare interface IonHeader extends Components.IonHeader {}
-@Component({ selector: 'ion-header', changeDetection: ChangeDetectionStrategy.OnPush, template: '', inputs: ['mode', 'translucent'] })
+@Component({ selector: 'ion-header', changeDetection: ChangeDetectionStrategy.OnPush, template: '', inputs: ['collapse', 'mode', 'translucent'] })
export class IonHeader {
protected el: HTMLElement;
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
@@ -277,7 +278,7 @@ export class IonHeader {
this.el = r.nativeElement;
}
}
-proxyInputs(IonHeader, ['mode', 'translucent']);
+proxyInputs(IonHeader, ['collapse', 'mode', 'translucent']);
export declare interface IonIcon extends Components.IonIcon {}
@Component({ selector: 'ion-icon', changeDetection: ChangeDetectionStrategy.OnPush, template: '', inputs: ['ariaLabel', 'color', 'flipRtl', 'icon', 'ios', 'lazy', 'md', 'mode', 'name', 'size', 'src'] })
@@ -895,7 +896,7 @@ export class IonThumbnail {
}
export declare interface IonTitle extends Components.IonTitle {}
-@Component({ selector: 'ion-title', changeDetection: ChangeDetectionStrategy.OnPush, template: '', inputs: ['color'] })
+@Component({ selector: 'ion-title', changeDetection: ChangeDetectionStrategy.OnPush, template: '', inputs: ['color', 'size'] })
export class IonTitle {
protected el: HTMLElement;
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
@@ -903,7 +904,7 @@ export class IonTitle {
this.el = r.nativeElement;
}
}
-proxyInputs(IonTitle, ['color']);
+proxyInputs(IonTitle, ['color', 'size']);
export declare interface IonToggle extends Components.IonToggle {}
@Component({ selector: 'ion-toggle', changeDetection: ChangeDetectionStrategy.OnPush, template: '', inputs: ['checked', 'color', 'disabled', 'mode', 'name', 'value'] })
diff --git a/core/api.txt b/core/api.txt
index f8e0f3756f..71c9c5ebc5 100644
--- a/core/api.txt
+++ b/core/api.txt
@@ -178,6 +178,7 @@ ion-button,css-prop,--ripple-color
ion-button,css-prop,--transition
ion-buttons,scoped
+ion-buttons,prop,collapse,boolean,false,false,false
ion-card,scoped
ion-card,prop,button,boolean,false,false,false
@@ -399,6 +400,7 @@ ion-grid,css-prop,--ion-grid-width-xl
ion-grid,css-prop,--ion-grid-width-xs
ion-header,none
+ion-header,prop,collapse,boolean,false,false,false
ion-header,prop,mode,"ios" | "md",undefined,false,false
ion-header,prop,translucent,boolean,false,false,false
@@ -1204,6 +1206,7 @@ ion-thumbnail,css-prop,--size
ion-title,shadow
ion-title,prop,color,string | undefined,undefined,false,false
+ion-title,prop,size,"large" | undefined,undefined,false,false
ion-title,css-prop,--color
ion-toast,shadow
diff --git a/core/src/components.d.ts b/core/src/components.d.ts
index 48fcd2dc1e..874bb03165 100644
--- a/core/src/components.d.ts
+++ b/core/src/components.d.ts
@@ -389,7 +389,12 @@ export namespace Components {
*/
'type': 'submit' | 'reset' | 'button';
}
- interface IonButtons {}
+ interface IonButtons {
+ /**
+ * If true, buttons will disappear when its parent toolbar has fully collapsed if the toolbar is not the first toolbar. If the toolbar is the first toolbar, the buttons will be hidden and will only be shown once all toolbars have fully collapsed. Only applies in `ios` mode with `collapse` set to `true` on `ion-header`
+ */
+ 'collapse': boolean;
+ }
interface IonCard {
/**
* If `true`, a button tag will be rendered and the card will be tappable.
@@ -865,6 +870,10 @@ export namespace Components {
'fixed': boolean;
}
interface IonHeader {
+ /**
+ * If `true`, the header will collapse on scroll of the content. Only applies in `ios` mode.
+ */
+ 'collapse': boolean;
/**
* The mode determines which platform styles to use.
*/
@@ -2696,6 +2705,10 @@ export namespace Components {
* The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics).
*/
'color'?: Color;
+ /**
+ * The size of the toolbar title. Only applies in `ios` mode.
+ */
+ 'size'?: 'large' | undefined;
}
interface IonToast {
/**
@@ -3883,7 +3896,12 @@ declare namespace LocalJSX {
*/
'type'?: 'submit' | 'reset' | 'button';
}
- interface IonButtons extends JSXBase.HTMLAttributes {}
+ interface IonButtons extends JSXBase.HTMLAttributes {
+ /**
+ * If true, buttons will disappear when its parent toolbar has fully collapsed if the toolbar is not the first toolbar. If the toolbar is the first toolbar, the buttons will be hidden and will only be shown once all toolbars have fully collapsed. Only applies in `ios` mode with `collapse` set to `true` on `ion-header`
+ */
+ 'collapse'?: boolean;
+ }
interface IonCard extends JSXBase.HTMLAttributes {
/**
* If `true`, a button tag will be rendered and the card will be tappable.
@@ -4371,6 +4389,10 @@ declare namespace LocalJSX {
'fixed'?: boolean;
}
interface IonHeader extends JSXBase.HTMLAttributes {
+ /**
+ * If `true`, the header will collapse on scroll of the content. Only applies in `ios` mode.
+ */
+ 'collapse'?: boolean;
/**
* The mode determines which platform styles to use.
*/
@@ -6012,6 +6034,10 @@ declare namespace LocalJSX {
* The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics).
*/
'color'?: Color;
+ /**
+ * The size of the toolbar title. Only applies in `ios` mode.
+ */
+ 'size'?: 'large' | undefined;
}
interface IonToast extends JSXBase.HTMLAttributes {
/**
diff --git a/core/src/components/buttons/buttons.tsx b/core/src/components/buttons/buttons.tsx
index bfd8b681f3..adaf48aafe 100644
--- a/core/src/components/buttons/buttons.tsx
+++ b/core/src/components/buttons/buttons.tsx
@@ -1,4 +1,4 @@
-import { Component, ComponentInterface, Host, h } from '@stencil/core';
+import { Component, ComponentInterface, Host, Prop, h } from '@stencil/core';
import { getIonMode } from '../../global/ionic-global';
@@ -12,6 +12,18 @@ import { getIonMode } from '../../global/ionic-global';
})
export class Buttons implements ComponentInterface {
+ /**
+ * If true, buttons will disappear when its
+ * parent toolbar has fully collapsed if the toolbar
+ * is not the first toolbar. If the toolbar is the
+ * first toolbar, the buttons will be hidden and will
+ * only be shown once all toolbars have fully collapsed.
+ *
+ * Only applies in `ios` mode with `collapse` set to
+ * `true` on `ion-header`
+ */
+ @Prop() collapse = false;
+
render() {
return (
diff --git a/core/src/components/buttons/readme.md b/core/src/components/buttons/readme.md
index 8977d0fd59..8dbc0a1724 100644
--- a/core/src/components/buttons/readme.md
+++ b/core/src/components/buttons/readme.md
@@ -204,6 +204,13 @@ export const ButtonsExample: React.FC = () => (
+## Properties
+
+| Property | Attribute | Description | Type | Default |
+| ---------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ------- |
+| `collapse` | `collapse` | If true, buttons will disappear when its parent toolbar has fully collapsed if the toolbar is not the first toolbar. If the toolbar is the first toolbar, the buttons will be hidden and will only be shown once all toolbars have fully collapsed. Only applies in `ios` mode with `collapse` set to `true` on `ion-header` | `boolean` | `false` |
+
+
----------------------------------------------
*Built with [StencilJS](https://stenciljs.com/)*
diff --git a/core/src/components/header/header.ios.scss b/core/src/components/header/header.ios.scss
index 2214f68c81..0b42259dec 100644
--- a/core/src/components/header/header.ios.scss
+++ b/core/src/components/header/header.ios.scss
@@ -16,9 +16,58 @@
.header-translucent-ios {
backdrop-filter: $header-ios-translucent-filter;
}
-
+
.header-translucent-ios ion-toolbar {
--opacity: .8;
--backdrop-filter: #{$header-ios-translucent-filter};
}
+}
+
+// iOS Header - Collapse
+// --------------------------------------------------
+.header-collapse-ios {
+ z-index: 9;
+}
+
+.header-collapse-ios ion-toolbar {
+ position: sticky;
+ top: 0;
+}
+
+.header-collapse-ios ion-toolbar:first-child {
+ padding-top: 7px;
+
+ z-index: 1;
+}
+
+.header-collapse-ios ion-toolbar {
+ z-index: 0;
+}
+
+.header-collapse-ios ion-toolbar ion-searchbar {
+ height: 48px;
+
+ padding-top: 0px;
+ padding-bottom: 13px;
+}
+
+ion-toolbar.in-toolbar ion-title,
+ion-toolbar.in-toolbar ion-buttons {
+ transition: all 0.2s ease-in-out;
+}
+
+/**
+ * There is a bug in Safari where animating the opacity
+ * on an element in a scrollable container while scrolling
+ * causes the scroll position to jump to the top
+ */
+.header-collapse-ios ion-toolbar ion-title,
+.header-collapse-ios ion-toolbar ion-buttons {
+ transition: none;
+}
+
+.header-collapse-ios-inactive ion-toolbar.in-toolbar ion-title,
+.header-collapse-ios-inactive ion-toolbar.in-toolbar ion-buttons[collapse] {
+ opacity: 0;
+ pointer-events: none;
}
\ No newline at end of file
diff --git a/core/src/components/header/header.md.scss b/core/src/components/header/header.md.scss
index 300f8b6abf..71d03249b2 100644
--- a/core/src/components/header/header.md.scss
+++ b/core/src/components/header/header.md.scss
@@ -25,3 +25,7 @@
.header-md[no-border]::after {
display: none;
}
+
+.header-collapse-md {
+ display: none;
+}
\ No newline at end of file
diff --git a/core/src/components/header/header.tsx b/core/src/components/header/header.tsx
index 46f05db10f..3002104c2d 100644
--- a/core/src/components/header/header.tsx
+++ b/core/src/components/header/header.tsx
@@ -1,7 +1,8 @@
-import { Component, ComponentInterface, Host, Prop, h } from '@stencil/core';
+import { Component, ComponentInterface, Element, Host, Prop, h, readTask, writeTask } from '@stencil/core';
import { getIonMode } from '../../global/ionic-global';
+import { cloneElement, createHeaderIndex, handleContentScroll, handleToolbarIntersection, setHeaderActive } from './header.utils';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
*/
@@ -14,6 +15,17 @@ import { getIonMode } from '../../global/ionic-global';
})
export class Header implements ComponentInterface {
+ private scrollEl?: HTMLElement;
+ private contentScrollCallback?: any;
+
+ @Element() el!: HTMLElement;
+
+ /**
+ * If `true`, the header will collapse on scroll of the content.
+ * Only applies in `ios` mode.
+ */
+ @Prop() collapse = false;
+
/**
* If `true`, the header will be translucent.
* Only applies when the mode is `"ios"` and the device supports
@@ -24,6 +36,76 @@ export class Header implements ComponentInterface {
*/
@Prop() translucent = false;
+ async componentDidLoad() {
+ // Determine if the header can collapse
+ const canCollapse = (this.collapse && getIonMode(this) === 'ios') ? this.collapse : false;
+
+ const tabs = this.el.closest('ion-tabs');
+ const page = this.el.closest('ion-app,ion-page,.ion-page,page-inner');
+ const contentEl = tabs ? tabs.querySelector('ion-content') : page!.querySelector('ion-content');
+
+ if (canCollapse) {
+ await this.setupCollapsableHeader(contentEl, (tabs) ? tabs : page!);
+ }
+ }
+
+ componentDidUnload() {
+ if (this.scrollEl && this.contentScrollCallback) {
+ this.scrollEl.removeEventListener('scroll', this.contentScrollCallback);
+ }
+ }
+
+ private async setupCollapsableHeader(contentEl: HTMLIonContentElement | null, pageEl: Element) {
+ if (!contentEl) { console.error('ion-header requires a content to collapse, make sure there is an ion-content.'); }
+
+ this.scrollEl = await contentEl!.getScrollElement();
+
+ readTask(() => {
+ const headers = pageEl.querySelectorAll('ion-header');
+ const mainHeader = Array.from(headers).find((header: any) => !header.collapse) as HTMLElement | undefined;
+
+ if (!mainHeader || !this.scrollEl) { return; }
+
+ const mainHeaderIndex = createHeaderIndex(mainHeader);
+ const scrollHeaderIndex = createHeaderIndex(this.el);
+
+ if (!mainHeaderIndex || !scrollHeaderIndex) { return; }
+
+ setHeaderActive(mainHeaderIndex, false);
+
+ // TODO: Find a better way to do this
+ let remainingHeight = 0;
+ for (let i = 1; i <= scrollHeaderIndex.toolbars.length - 1; i++) {
+ remainingHeight += scrollHeaderIndex.toolbars[i].el.clientHeight;
+ }
+
+ /**
+ * Handle interaction between toolbar collapse and
+ * showing/hiding content in the primary ion-header
+ */
+ const toolbarIntersection = (ev: any) => { handleToolbarIntersection(ev, mainHeaderIndex, scrollHeaderIndex); };
+
+ readTask(() => {
+ const mainHeaderHeight = mainHeaderIndex.el.clientHeight;
+ const intersectionObserver = new IntersectionObserver(toolbarIntersection, { threshold: 0.25, rootMargin: `-${mainHeaderHeight}px 0px 0px 0px` });
+ intersectionObserver.observe(scrollHeaderIndex.toolbars[0].el);
+ });
+
+ /**
+ * Handle scaling of large iOS titles and
+ * showing/hiding border on last toolbar
+ * in primary header
+ */
+ this.contentScrollCallback = () => { handleContentScroll(this.scrollEl!, mainHeaderIndex, scrollHeaderIndex, remainingHeight); };
+ this.scrollEl.addEventListener('scroll', this.contentScrollCallback);
+ });
+
+ writeTask(() => {
+ cloneElement('ion-title');
+ cloneElement('ion-back-button');
+ });
+ }
+
render() {
const mode = getIonMode(this);
return (
@@ -36,6 +118,7 @@ export class Header implements ComponentInterface {
[`header-${mode}`]: true,
[`header-translucent`]: this.translucent,
+ [`header-collapse-${mode}`]: this.collapse,
[`header-translucent-${mode}`]: this.translucent,
}}
>
diff --git a/core/src/components/header/header.utils.ts b/core/src/components/header/header.utils.ts
new file mode 100644
index 0000000000..b796220dca
--- /dev/null
+++ b/core/src/components/header/header.utils.ts
@@ -0,0 +1,142 @@
+import { readTask, writeTask } from '@stencil/core';
+
+import { clamp } from '../../utils/helpers';
+
+const TRANSITION = 'all 0.2s ease-in-out';
+
+interface HeaderIndex {
+ el: HTMLElement;
+ toolbars: ToolbarIndex[] | [];
+}
+
+interface ToolbarIndex {
+ el: HTMLElement;
+ background: HTMLElement;
+ ionTitleEl: HTMLIonTitleElement | undefined;
+ innerTitleEl: HTMLElement;
+ ionButtonsEl: HTMLElement[] | [];
+}
+
+export const cloneElement = (tagName: string) => {
+ const getCachedEl = document.querySelector(`${tagName}.ion-cloned-element`);
+ if (getCachedEl !== null) { return getCachedEl; }
+
+ const clonedEl = document.createElement(tagName);
+ clonedEl.classList.add('ion-cloned-element');
+ clonedEl.style.setProperty('display', 'none');
+ document.body.appendChild(clonedEl);
+
+ return clonedEl;
+};
+
+export const createHeaderIndex = (headerEl: HTMLElement | undefined): HeaderIndex | undefined => {
+ if (!headerEl) { return; }
+
+ const toolbars = headerEl.querySelectorAll('ion-toolbar');
+
+ return {
+ el: headerEl,
+ toolbars: Array.from(toolbars).map((toolbar: any) => {
+ const ionTitleEl = toolbar.querySelector('ion-title');
+ return {
+ el: toolbar,
+ background: toolbar.shadowRoot!.querySelector('.toolbar-background'),
+ ionTitleEl,
+ innerTitleEl: (ionTitleEl) ? ionTitleEl.shadowRoot!.querySelector('.toolbar-title') : null,
+ ionButtonsEl: Array.from(toolbar.querySelectorAll('ion-buttons')) || []
+ } as ToolbarIndex;
+ }) || [[]]
+ } as HeaderIndex;
+};
+
+export const handleContentScroll = (scrollEl: HTMLElement, mainHeaderIndex: HeaderIndex, scrollHeaderIndex: HeaderIndex, remainingHeight = 0) => {
+ readTask(() => {
+ const scrollTop = scrollEl.scrollTop;
+ const lastMainToolbar = mainHeaderIndex.toolbars[mainHeaderIndex.toolbars.length - 1];
+
+ const scale = clamp(1, 1 + (-scrollTop / 500), 1.1);
+
+ const borderOpacity = clamp(0, (scrollTop - remainingHeight) / lastMainToolbar.el.clientHeight, 1);
+ const maxOpacity = 1;
+ const scaledOpacity = borderOpacity * maxOpacity;
+
+ writeTask(() => {
+ scaleLargeTitles(scrollHeaderIndex.toolbars, scale);
+ setToolbarBackgroundOpacity(mainHeaderIndex.toolbars[0], (scaledOpacity === 1) ? undefined : scaledOpacity);
+ });
+ });
+};
+
+const setToolbarBackgroundOpacity = (toolbar: ToolbarIndex, opacity: number | undefined) => {
+ if (opacity === undefined) {
+ toolbar.background.style.removeProperty('--opacity');
+ } else {
+ toolbar.background.style.setProperty('--opacity', opacity.toString());
+ }
+};
+
+/**
+ * If toolbars are intersecting, hide the scrollable toolbar content
+ * and show the primary toolbar content. If the toolbars are not intersecting,
+ * hide the primary toolbar content and show the scrollable toolbar content
+ */
+export const handleToolbarIntersection = (ev: any, mainHeaderIndex: HeaderIndex, scrollHeaderIndex: HeaderIndex) => {
+ console.log(ev);
+ writeTask(() => {
+ const event = ev[0];
+ const intersection = event.intersectionRect;
+ const intersectionArea = intersection.width * intersection.height;
+ const rootArea = event.rootBounds.width * event.rootBounds.height;
+
+ const isPageHidden = intersectionArea === 0 && rootArea === 0;
+ const isPageTransitioning = intersectionArea > 0 && (intersection.left !== event.rootBounds.left || intersection.right !== event.rootBounds.right);
+
+ if (isPageHidden || isPageTransitioning) {
+ return;
+ }
+
+ if (event.isIntersecting) {
+ setHeaderActive(mainHeaderIndex, false);
+ setHeaderActive(scrollHeaderIndex);
+ } else {
+ /**
+ * There is a bug with IntersectionObserver on Safari
+ * where `event.isIntersecting === false` when cancelling
+ * a swipe to go back gesture. Checking the intersection
+ * x, y, width, and height provides a workaround. This bug
+ * does not happen when using Safari + Web Animations,
+ * only Safari + CSS Animations.
+ */
+
+ const hasValidIntersection = (intersection.x === 0 && intersection.y === 0) || (intersection.width !== 0 && intersection.height !== 0);
+
+ if (hasValidIntersection) {
+ setHeaderActive(mainHeaderIndex);
+ setHeaderActive(scrollHeaderIndex, false);
+ }
+ }
+ });
+};
+
+export const setHeaderActive = (headerIndex: HeaderIndex, active = true) => {
+ writeTask(() => {
+ if (active) {
+ headerIndex.el.classList.remove('header-collapse-ios-inactive');
+ } else {
+ headerIndex.el.classList.add('header-collapse-ios-inactive');
+ }
+ setToolbarBackgroundOpacity(headerIndex.toolbars[0], (active) ? undefined : 0);
+ });
+};
+
+export const scaleLargeTitles = (toolbars: ToolbarIndex[] = [], scale = 1, transition = false) => {
+ toolbars.forEach(toolbar => {
+ const ionTitle = toolbar.ionTitleEl;
+ const titleDiv = toolbar.innerTitleEl;
+ if (!ionTitle || ionTitle.size !== 'large') { return; }
+
+ titleDiv.style.transformOrigin = 'left center';
+ titleDiv.style.transition = (transition) ? TRANSITION : '';
+ titleDiv.style.transform = `scale3d(${scale}, ${scale}, 1)`;
+ });
+};
diff --git a/core/src/components/header/readme.md b/core/src/components/header/readme.md
index 1f8883ef3d..633dbbc780 100644
--- a/core/src/components/header/readme.md
+++ b/core/src/components/header/readme.md
@@ -62,6 +62,7 @@ export const HeaderExample: React.FC = () => (
| Property | Attribute | Description | Type | Default |
| ------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | ----------- |
+| `collapse` | `collapse` | If `true`, the header will collapse on scroll of the content. Only applies in `ios` mode. | `boolean` | `false` |
| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` |
| `translucent` | `translucent` | If `true`, the header will be translucent. Only applies when the mode is `"ios"` and the device supports [`backdrop-filter`](https://developer.mozilla.org/en-US/docs/Web/CSS/backdrop-filter#Browser_compatibility). Note: In order to scroll content behind the header, the `fullscreen` attribute needs to be set on the content. | `boolean` | `false` |
diff --git a/core/src/components/searchbar/searchbar.ios.scss b/core/src/components/searchbar/searchbar.ios.scss
index f80e59aed4..61f804a754 100644
--- a/core/src/components/searchbar/searchbar.ios.scss
+++ b/core/src/components/searchbar/searchbar.ios.scss
@@ -50,7 +50,7 @@
height: 100%;
- font-size: 14px;
+ font-size: 16px;
font-weight: 400;
contain: strict;
diff --git a/core/src/components/title/readme.md b/core/src/components/title/readme.md
index dd554b1e84..1a2e70e132 100644
--- a/core/src/components/title/readme.md
+++ b/core/src/components/title/readme.md
@@ -25,9 +25,10 @@
## Properties
-| Property | Attribute | Description | Type | Default |
-| -------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | ----------- |
-| `color` | `color` | The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). | `string \| undefined` | `undefined` |
+| Property | Attribute | Description | Type | Default |
+| -------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | ----------- |
+| `color` | `color` | The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). | `string \| undefined` | `undefined` |
+| `size` | `size` | The size of the toolbar title. Only applies in `ios` mode. | `"large" \| undefined` | `undefined` |
## CSS Custom Properties
diff --git a/core/src/components/title/test/scenarios/index.html b/core/src/components/title/test/scenarios/index.html
new file mode 100644
index 0000000000..a8b73e5c1f
--- /dev/null
+++ b/core/src/components/title/test/scenarios/index.html
@@ -0,0 +1,89 @@
+
+
+
+
+
+ Toolbar - Title
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Edit
+
+
+ Inbox
+
+
+
+
+
+
+
+
+
+ Edit
+
+
+ Inbox
+
+
+
+
+
+
+
+
+
+ Edit
+
+
+ Inbox
+
+
+
+
+
+
+
+
+ Title
+
+
+
+
+ Title
+
+
+
+
+
+
+ Toggle Size
+
+
+
+
+
+
+
diff --git a/core/src/components/title/title.scss b/core/src/components/title/title.scss
index f54f65a788..e2d829f922 100644
--- a/core/src/components/title/title.scss
+++ b/core/src/components/title/title.scss
@@ -33,8 +33,6 @@
font-size: 17px;
font-weight: 600;
- letter-spacing: -.03em;
-
text-align: center;
box-sizing: border-box;
pointer-events: none;
@@ -64,4 +62,25 @@
overflow: hidden;
pointer-events: auto;
-}
\ No newline at end of file
+}
+
+
+// Title: Large
+// --------------------------------------------------
+
+:host(.title-ios-large) {
+ @include padding(0, 16px);
+
+ bottom: 0;
+
+ align-items: flex-end;
+
+ min-width: 100%;
+
+ padding-bottom: 6px;
+
+ font-size: 34px;
+ font-weight: 700;
+
+ text-align: left;
+}
diff --git a/core/src/components/title/title.tsx b/core/src/components/title/title.tsx
index 60fdc5b6e2..057375eee2 100644
--- a/core/src/components/title/title.tsx
+++ b/core/src/components/title/title.tsx
@@ -1,7 +1,7 @@
-import { Component, ComponentInterface, Element, Host, Prop, h } from '@stencil/core';
+import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Prop, Watch, h } from '@stencil/core';
import { getIonMode } from '../../global/ionic-global';
-import { Color } from '../../interface';
+import { Color, StyleEventDetail } from '../../interface';
import { createColorClasses } from '../../utils/theme';
@Component({
@@ -20,6 +20,37 @@ export class ToolbarTitle implements ComponentInterface {
*/
@Prop() color?: Color;
+ /**
+ * The size of the toolbar title.
+ * Only applies in `ios` mode.
+ */
+ @Prop() size?: 'large' | undefined;
+
+ /**
+ * Emitted when the styles change.
+ * @internal
+ */
+ @Event() ionStyle!: EventEmitter;
+
+ @Watch('size')
+ protected sizeChanged() {
+ this.emitStyle();
+ }
+
+ connectedCallback() {
+ this.emitStyle();
+ }
+
+ private emitStyle() {
+ this.ionStyle.emit({
+ [`title-${this.getSize()}`]: true
+ });
+ }
+
+ private getSize() {
+ return (this.size !== undefined) ? this.size : 'standard';
+ }
+
private getMode() {
const mode = getIonMode(this);
const toolbar = this.el.closest('ion-toolbar');
@@ -33,6 +64,7 @@ export class ToolbarTitle implements ComponentInterface {
class={{
[mode]: true,
[`title-${mode}`]: true,
+ [`title-${mode}-${this.getSize()}`]: true,
...createColorClasses(this.color),
}}
diff --git a/core/src/components/toolbar/test/title/index.html b/core/src/components/toolbar/test/title/index.html
new file mode 100644
index 0000000000..93effe25f9
--- /dev/null
+++ b/core/src/components/toolbar/test/title/index.html
@@ -0,0 +1,204 @@
+
+
+
+
+
+ Toolbar - Title
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+