mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-18 11:17:19 +08:00
feat(header, footer): add ios fading header style (#24011)
This commit is contained in:
@ -519,13 +519,13 @@ export class IonFabList {
|
|||||||
|
|
||||||
export declare interface IonFooter extends Components.IonFooter {}
|
export declare interface IonFooter extends Components.IonFooter {}
|
||||||
@ProxyCmp({
|
@ProxyCmp({
|
||||||
inputs: ['mode', 'translucent']
|
inputs: ['collapse', 'mode', 'translucent']
|
||||||
})
|
})
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ion-footer',
|
selector: 'ion-footer',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
template: '<ng-content></ng-content>',
|
template: '<ng-content></ng-content>',
|
||||||
inputs: ['mode', 'translucent']
|
inputs: ['collapse', 'mode', 'translucent']
|
||||||
})
|
})
|
||||||
export class IonFooter {
|
export class IonFooter {
|
||||||
protected el: HTMLElement;
|
protected el: HTMLElement;
|
||||||
|
@ -463,6 +463,7 @@ ion-fab-list,prop,activated,boolean,false,false,false
|
|||||||
ion-fab-list,prop,side,"bottom" | "end" | "start" | "top",'bottom',false,false
|
ion-fab-list,prop,side,"bottom" | "end" | "start" | "top",'bottom',false,false
|
||||||
|
|
||||||
ion-footer,none
|
ion-footer,none
|
||||||
|
ion-footer,prop,collapse,"fade" | undefined,undefined,false,false
|
||||||
ion-footer,prop,mode,"ios" | "md",undefined,false,false
|
ion-footer,prop,mode,"ios" | "md",undefined,false,false
|
||||||
ion-footer,prop,translucent,boolean,false,false,false
|
ion-footer,prop,translucent,boolean,false,false,false
|
||||||
|
|
||||||
@ -482,7 +483,7 @@ ion-grid,css-prop,--ion-grid-width-xl
|
|||||||
ion-grid,css-prop,--ion-grid-width-xs
|
ion-grid,css-prop,--ion-grid-width-xs
|
||||||
|
|
||||||
ion-header,none
|
ion-header,none
|
||||||
ion-header,prop,collapse,"condense" | undefined,undefined,false,false
|
ion-header,prop,collapse,"condense" | "fade" | undefined,undefined,false,false
|
||||||
ion-header,prop,mode,"ios" | "md",undefined,false,false
|
ion-header,prop,mode,"ios" | "md",undefined,false,false
|
||||||
ion-header,prop,translucent,boolean,false,false,false
|
ion-header,prop,translucent,boolean,false,false,false
|
||||||
|
|
||||||
|
16
core/src/components.d.ts
vendored
16
core/src/components.d.ts
vendored
@ -920,6 +920,10 @@ export namespace Components {
|
|||||||
"side": 'start' | 'end' | 'top' | 'bottom';
|
"side": 'start' | 'end' | 'top' | 'bottom';
|
||||||
}
|
}
|
||||||
interface IonFooter {
|
interface IonFooter {
|
||||||
|
/**
|
||||||
|
* Describes the scroll effect that will be applied to the footer. Only applies in iOS mode.
|
||||||
|
*/
|
||||||
|
"collapse"?: 'fade';
|
||||||
/**
|
/**
|
||||||
* The mode determines which platform styles to use.
|
* The mode determines which platform styles to use.
|
||||||
*/
|
*/
|
||||||
@ -937,9 +941,9 @@ export namespace Components {
|
|||||||
}
|
}
|
||||||
interface IonHeader {
|
interface IonHeader {
|
||||||
/**
|
/**
|
||||||
* Describes the scroll effect that will be applied to the header `condense` only applies in iOS mode. Typically used for [Collapsible Large Titles](https://ionicframework.com/docs/api/title#collapsible-large-titles)
|
* Describes the scroll effect that will be applied to the header. Only applies in iOS mode. Typically used for [Collapsible Large Titles](https://ionicframework.com/docs/api/title#collapsible-large-titles)
|
||||||
*/
|
*/
|
||||||
"collapse"?: 'condense';
|
"collapse"?: 'condense' | 'fade';
|
||||||
/**
|
/**
|
||||||
* The mode determines which platform styles to use.
|
* The mode determines which platform styles to use.
|
||||||
*/
|
*/
|
||||||
@ -4606,6 +4610,10 @@ declare namespace LocalJSX {
|
|||||||
"side"?: 'start' | 'end' | 'top' | 'bottom';
|
"side"?: 'start' | 'end' | 'top' | 'bottom';
|
||||||
}
|
}
|
||||||
interface IonFooter {
|
interface IonFooter {
|
||||||
|
/**
|
||||||
|
* Describes the scroll effect that will be applied to the footer. Only applies in iOS mode.
|
||||||
|
*/
|
||||||
|
"collapse"?: 'fade';
|
||||||
/**
|
/**
|
||||||
* The mode determines which platform styles to use.
|
* The mode determines which platform styles to use.
|
||||||
*/
|
*/
|
||||||
@ -4623,9 +4631,9 @@ declare namespace LocalJSX {
|
|||||||
}
|
}
|
||||||
interface IonHeader {
|
interface IonHeader {
|
||||||
/**
|
/**
|
||||||
* Describes the scroll effect that will be applied to the header `condense` only applies in iOS mode. Typically used for [Collapsible Large Titles](https://ionicframework.com/docs/api/title#collapsible-large-titles)
|
* Describes the scroll effect that will be applied to the header. Only applies in iOS mode. Typically used for [Collapsible Large Titles](https://ionicframework.com/docs/api/title#collapsible-large-titles)
|
||||||
*/
|
*/
|
||||||
"collapse"?: 'condense';
|
"collapse"?: 'condense' | 'fade';
|
||||||
/**
|
/**
|
||||||
* The mode determines which platform styles to use.
|
* The mode determines which platform styles to use.
|
||||||
*/
|
*/
|
||||||
|
@ -24,3 +24,10 @@
|
|||||||
.footer-ios.ion-no-border ion-toolbar:first-of-type {
|
.footer-ios.ion-no-border ion-toolbar:first-of-type {
|
||||||
--border-width: 0;
|
--border-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// iOS Footer - Collapse Fade
|
||||||
|
// --------------------------------------------------
|
||||||
|
|
||||||
|
.footer-collapse-fade ion-toolbar {
|
||||||
|
--opacity-scale: inherit;
|
||||||
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import { Component, ComponentInterface, Host, Prop, h } from '@stencil/core';
|
import { Component, ComponentInterface, Element, Host, Prop, h } from '@stencil/core';
|
||||||
|
|
||||||
import { getIonMode } from '../../global/ionic-global';
|
import { getIonMode } from '../../global/ionic-global';
|
||||||
|
import { componentOnReady } from '../../utils/helpers';
|
||||||
|
|
||||||
|
import { handleFooterFade } from './footer.utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
|
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
|
||||||
@ -13,6 +16,16 @@ import { getIonMode } from '../../global/ionic-global';
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
export class Footer implements ComponentInterface {
|
export class Footer implements ComponentInterface {
|
||||||
|
private scrollEl?: HTMLElement;
|
||||||
|
private contentScrollCallback: any;
|
||||||
|
|
||||||
|
@Element() el!: HTMLIonFooterElement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the scroll effect that will be applied to the footer.
|
||||||
|
* Only applies in iOS mode.
|
||||||
|
*/
|
||||||
|
@Prop() collapse?: 'fade';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If `true`, the footer will be translucent.
|
* If `true`, the footer will be translucent.
|
||||||
@ -24,9 +37,56 @@ export class Footer implements ComponentInterface {
|
|||||||
*/
|
*/
|
||||||
@Prop() translucent = false;
|
@Prop() translucent = false;
|
||||||
|
|
||||||
render() {
|
componentDidLoad() {
|
||||||
|
this.checkCollapsibleFooter();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
this.checkCollapsibleFooter();
|
||||||
|
}
|
||||||
|
|
||||||
|
private checkCollapsibleFooter = () => {
|
||||||
|
const mode = getIonMode(this);
|
||||||
|
if (mode !== 'ios') { return; }
|
||||||
|
|
||||||
|
const { collapse } = this;
|
||||||
|
const hasFade = collapse === 'fade';
|
||||||
|
|
||||||
|
this.destroyCollapsibleFooter();
|
||||||
|
|
||||||
|
if (hasFade) {
|
||||||
|
const pageEl = this.el.closest('ion-app,ion-page,.ion-page,page-inner');
|
||||||
|
const contentEl = (pageEl) ? pageEl.querySelector('ion-content') : null;
|
||||||
|
|
||||||
|
this.setupFadeFooter(contentEl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupFadeFooter = async (contentEl: HTMLIonContentElement | null) => {
|
||||||
|
if (!contentEl) { console.error('ion-footer requires a content to collapse. Make sure there is an ion-content.'); return; }
|
||||||
|
|
||||||
|
await new Promise(resolve => componentOnReady(contentEl, resolve));
|
||||||
|
const scrollEl = this.scrollEl = await contentEl.getScrollElement();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle fading of toolbars on scroll
|
||||||
|
*/
|
||||||
|
this.contentScrollCallback = () => { handleFooterFade(scrollEl, this.el); };
|
||||||
|
scrollEl.addEventListener('scroll', this.contentScrollCallback);
|
||||||
|
|
||||||
|
handleFooterFade(scrollEl, this.el);
|
||||||
|
}
|
||||||
|
|
||||||
|
private destroyCollapsibleFooter() {
|
||||||
|
if (this.scrollEl && this.contentScrollCallback) {
|
||||||
|
this.scrollEl.removeEventListener('scroll', this.contentScrollCallback);
|
||||||
|
this.contentScrollCallback = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { translucent, collapse } = this;
|
||||||
const mode = getIonMode(this);
|
const mode = getIonMode(this);
|
||||||
const translucent = this.translucent;
|
|
||||||
return (
|
return (
|
||||||
<Host
|
<Host
|
||||||
role="contentinfo"
|
role="contentinfo"
|
||||||
@ -38,6 +98,8 @@ export class Footer implements ComponentInterface {
|
|||||||
|
|
||||||
[`footer-translucent`]: translucent,
|
[`footer-translucent`]: translucent,
|
||||||
[`footer-translucent-${mode}`]: translucent,
|
[`footer-translucent-${mode}`]: translucent,
|
||||||
|
|
||||||
|
[`footer-collapse-${collapse}`]: collapse !== undefined,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{ mode === 'ios' && translucent &&
|
{ mode === 'ios' && translucent &&
|
||||||
|
36
core/src/components/footer/footer.utils.ts
Normal file
36
core/src/components/footer/footer.utils.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { readTask, writeTask } from '@stencil/core';
|
||||||
|
|
||||||
|
import { clamp } from '../../utils/helpers';
|
||||||
|
|
||||||
|
export const handleFooterFade = (scrollEl: HTMLElement, baseEl: HTMLElement) => {
|
||||||
|
readTask(() => {
|
||||||
|
const scrollTop = scrollEl.scrollTop;
|
||||||
|
const maxScroll = scrollEl.scrollHeight - scrollEl.clientHeight;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toolbar background will fade
|
||||||
|
* out over fadeDuration in pixels.
|
||||||
|
*/
|
||||||
|
const fadeDuration = 10;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begin fading out maxScroll - 30px
|
||||||
|
* from the bottom of the content.
|
||||||
|
* Also determine how close we are
|
||||||
|
* to starting the fade. If we are
|
||||||
|
* before the starting point, the
|
||||||
|
* scale value will get clamped to 0.
|
||||||
|
* If we are after the maxScroll (rubber
|
||||||
|
* band scrolling), the scale value will
|
||||||
|
* get clamped to 1.
|
||||||
|
*/
|
||||||
|
const fadeStart = maxScroll - fadeDuration;
|
||||||
|
const distanceToStart = scrollTop - fadeStart;
|
||||||
|
|
||||||
|
const scale = clamp(0, 1 - (distanceToStart / fadeDuration), 1);
|
||||||
|
|
||||||
|
writeTask(() => {
|
||||||
|
baseEl.style.setProperty('--opacity-scale', scale.toString());
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
@ -3,6 +3,10 @@
|
|||||||
Footer is a root component of a page that sits at the bottom of the page.
|
Footer is a root component of a page that sits at the bottom of the page.
|
||||||
Footer can be a wrapper for ion-toolbar to make sure the content area is sized correctly.
|
Footer can be a wrapper for ion-toolbar to make sure the content area is sized correctly.
|
||||||
|
|
||||||
|
## Fade Footer
|
||||||
|
|
||||||
|
The `collapse` property can be set to `'fade'` on a page's `ion-footer` to have the background color of the toolbars fade in as users scroll. This provides the same fade effect that is found in many native iOS applications.
|
||||||
|
|
||||||
<!-- Auto Generated Below -->
|
<!-- Auto Generated Below -->
|
||||||
|
|
||||||
|
|
||||||
@ -25,6 +29,13 @@ Footer can be a wrapper for ion-toolbar to make sure the content area is sized c
|
|||||||
<ion-title>Footer</ion-title>
|
<ion-title>Footer</ion-title>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-footer>
|
</ion-footer>
|
||||||
|
|
||||||
|
<!-- Fade Footer -->
|
||||||
|
<ion-footer collapse="fade">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Footer</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-footer>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@ -50,6 +61,13 @@ export const FooterExample: React.FC = () => (
|
|||||||
<IonTitle>Footer</IonTitle>
|
<IonTitle>Footer</IonTitle>
|
||||||
</IonToolbar>
|
</IonToolbar>
|
||||||
</IonFooter>
|
</IonFooter>
|
||||||
|
|
||||||
|
{/*-- Fade Footer --*/}
|
||||||
|
<IonFooter collapse="fade">
|
||||||
|
<IonToolbar>
|
||||||
|
<IonTitle>Footer</IonTitle>
|
||||||
|
</IonToolbar>
|
||||||
|
</IonFooter>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
@ -69,7 +87,7 @@ export class FooterExample {
|
|||||||
return [
|
return [
|
||||||
<ion-content></ion-content>,
|
<ion-content></ion-content>,
|
||||||
|
|
||||||
// Footer without a border
|
{/*-- Footer without a border --*/}
|
||||||
<ion-footer class="ion-no-border">
|
<ion-footer class="ion-no-border">
|
||||||
<ion-toolbar>
|
<ion-toolbar>
|
||||||
<ion-title>Footer - No Border</ion-title>
|
<ion-title>Footer - No Border</ion-title>
|
||||||
@ -80,6 +98,13 @@ export class FooterExample {
|
|||||||
<ion-toolbar>
|
<ion-toolbar>
|
||||||
<ion-title>Footer</ion-title>
|
<ion-title>Footer</ion-title>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
|
</ion-footer>,
|
||||||
|
|
||||||
|
{/*-- Fade Footer --*/}
|
||||||
|
<ion-footer collapse="fade">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Footer</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
</ion-footer>
|
</ion-footer>
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -105,6 +130,13 @@ export class FooterExample {
|
|||||||
<ion-title>Footer</ion-title>
|
<ion-title>Footer</ion-title>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-footer>
|
</ion-footer>
|
||||||
|
|
||||||
|
<!-- Fade Footer -->
|
||||||
|
<ion-footer collapse="fade">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Footer</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-footer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@ -122,7 +154,8 @@ export default defineComponent({
|
|||||||
## Properties
|
## Properties
|
||||||
|
|
||||||
| Property | Attribute | Description | Type | Default |
|
| Property | Attribute | Description | Type | Default |
|
||||||
| ------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | ----------- |
|
| ------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | ----------- |
|
||||||
|
| `collapse` | `collapse` | Describes the scroll effect that will be applied to the footer. Only applies in iOS mode. | `"fade" \| undefined` | `undefined` |
|
||||||
| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` |
|
| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` |
|
||||||
| `translucent` | `translucent` | If `true`, the footer 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 footer, the `fullscreen` attribute needs to be set on the content. | `boolean` | `false` |
|
| `translucent` | `translucent` | If `true`, the footer 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 footer, the `fullscreen` attribute needs to be set on the content. | `boolean` | `false` |
|
||||||
|
|
||||||
|
10
core/src/components/footer/test/fade/e2e.ts
Normal file
10
core/src/components/footer/test/fade/e2e.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { newE2EPage } from '@stencil/core/testing';
|
||||||
|
|
||||||
|
test('footer: fade', async () => {
|
||||||
|
const page = await newE2EPage({
|
||||||
|
url: '/src/components/footer/test/fade?ionic:_testing=true'
|
||||||
|
});
|
||||||
|
|
||||||
|
const compare = await page.compareScreenshot();
|
||||||
|
expect(compare).toMatchScreenshot();
|
||||||
|
});
|
89
core/src/components/footer/test/fade/index.html
Normal file
89
core/src/components/footer/test/fade/index.html
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" dir="ltr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Footer - Fade</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||||
|
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet">
|
||||||
|
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet">
|
||||||
|
<script src="../../../../../scripts/testing/scripts.js"></script>
|
||||||
|
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
|
||||||
|
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||||
|
<style>
|
||||||
|
.red {
|
||||||
|
background-color: #ea445a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.green {
|
||||||
|
background-color: #76d672;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blue {
|
||||||
|
background-color: #3478f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yellow {
|
||||||
|
background-color: #ffff80;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pink {
|
||||||
|
background-color: #ff6b86;
|
||||||
|
}
|
||||||
|
|
||||||
|
.purple {
|
||||||
|
background-color: #7e34f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.black {
|
||||||
|
background-color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orange {
|
||||||
|
background-color: #f69234;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
grid-gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-item {
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<ion-app>
|
||||||
|
<div class="ion-page">
|
||||||
|
<ion-header translucent="true">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Mailboxes</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content fullscreen="true">
|
||||||
|
<ion-header collapse="condense">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title size="large">Mailboxes</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
<div class="grid ion-padding">
|
||||||
|
<div class="grid-item red"></div>
|
||||||
|
<div class="grid-item green"></div>
|
||||||
|
<div class="grid-item blue"></div>
|
||||||
|
<div class="grid-item yellow"></div>
|
||||||
|
<div class="grid-item pink"></div>
|
||||||
|
<div class="grid-item purple"></div>
|
||||||
|
<div class="grid-item black"></div>
|
||||||
|
<div class="grid-item orange"></div>
|
||||||
|
</div>
|
||||||
|
</ion-content>
|
||||||
|
<ion-footer collapse="fade" translucent="true">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Updated Just Now</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-footer>
|
||||||
|
</div>
|
||||||
|
</ion-app>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -13,4 +13,11 @@
|
|||||||
<ion-title>Footer</ion-title>
|
<ion-title>Footer</ion-title>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-footer>
|
</ion-footer>
|
||||||
|
|
||||||
|
<!-- Fade Footer -->
|
||||||
|
<ion-footer collapse="fade">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Footer</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-footer>
|
||||||
```
|
```
|
||||||
|
@ -13,4 +13,11 @@
|
|||||||
<ion-title>Footer</ion-title>
|
<ion-title>Footer</ion-title>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-footer>
|
</ion-footer>
|
||||||
|
|
||||||
|
<!-- Fade Footer -->
|
||||||
|
<ion-footer collapse="fade">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Footer</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-footer>
|
||||||
```
|
```
|
||||||
|
@ -18,6 +18,13 @@ export const FooterExample: React.FC = () => (
|
|||||||
<IonTitle>Footer</IonTitle>
|
<IonTitle>Footer</IonTitle>
|
||||||
</IonToolbar>
|
</IonToolbar>
|
||||||
</IonFooter>
|
</IonFooter>
|
||||||
|
|
||||||
|
{/*-- Fade Footer --*/}
|
||||||
|
<IonFooter collapse="fade">
|
||||||
|
<IonToolbar>
|
||||||
|
<IonTitle>Footer</IonTitle>
|
||||||
|
</IonToolbar>
|
||||||
|
</IonFooter>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
@ -10,7 +10,7 @@ export class FooterExample {
|
|||||||
return [
|
return [
|
||||||
<ion-content></ion-content>,
|
<ion-content></ion-content>,
|
||||||
|
|
||||||
// Footer without a border
|
{/*-- Footer without a border --*/}
|
||||||
<ion-footer class="ion-no-border">
|
<ion-footer class="ion-no-border">
|
||||||
<ion-toolbar>
|
<ion-toolbar>
|
||||||
<ion-title>Footer - No Border</ion-title>
|
<ion-title>Footer - No Border</ion-title>
|
||||||
@ -21,6 +21,13 @@ export class FooterExample {
|
|||||||
<ion-toolbar>
|
<ion-toolbar>
|
||||||
<ion-title>Footer</ion-title>
|
<ion-title>Footer</ion-title>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
|
</ion-footer>,
|
||||||
|
|
||||||
|
{/*-- Fade Footer --*/}
|
||||||
|
<ion-footer collapse="fade">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Footer</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
</ion-footer>
|
</ion-footer>
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,13 @@
|
|||||||
<ion-title>Footer</ion-title>
|
<ion-title>Footer</ion-title>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-footer>
|
</ion-footer>
|
||||||
|
|
||||||
|
<!-- Fade Footer -->
|
||||||
|
<ion-footer collapse="fade">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Footer</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-footer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -33,7 +33,13 @@
|
|||||||
--border-width: 0;
|
--border-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// iOS Header - Collapse
|
// iOS Header - Collapse Fade
|
||||||
|
// --------------------------------------------------
|
||||||
|
.header-collapse-fade ion-toolbar {
|
||||||
|
--opacity-scale: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// iOS Header - Collapse Condense
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
.header-collapse-condense {
|
.header-collapse-condense {
|
||||||
z-index: 9;
|
z-index: 9;
|
||||||
@ -71,6 +77,15 @@
|
|||||||
padding-bottom: 13px;
|
padding-bottom: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.header-collapse-main {
|
||||||
|
--opacity-scale: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-collapse-main ion-toolbar {
|
||||||
|
--opacity-scale: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
.header-collapse-main ion-toolbar.in-toolbar ion-title,
|
.header-collapse-main ion-toolbar.in-toolbar ion-title,
|
||||||
.header-collapse-main ion-toolbar.in-toolbar ion-buttons {
|
.header-collapse-main ion-toolbar.in-toolbar ion-buttons {
|
||||||
transition: all 0.2s ease-in-out;
|
transition: all 0.2s ease-in-out;
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { Component, ComponentInterface, Element, Host, Prop, h, writeTask } from '@stencil/core';
|
import { Component, ComponentInterface, Element, Host, Prop, h, writeTask } from '@stencil/core';
|
||||||
|
|
||||||
import { getIonMode } from '../../global/ionic-global';
|
import { getIonMode } from '../../global/ionic-global';
|
||||||
import { inheritAttributes } from '../../utils/helpers';
|
import { componentOnReady, inheritAttributes } from '../../utils/helpers';
|
||||||
|
|
||||||
import { cloneElement, createHeaderIndex, handleContentScroll, handleToolbarIntersection, setHeaderActive, setToolbarBackgroundOpacity } from './header.utils';
|
import { cloneElement, createHeaderIndex, handleContentScroll, handleHeaderFade, handleToolbarIntersection, setHeaderActive, setToolbarBackgroundOpacity } from './header.utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
|
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
|
||||||
@ -16,8 +16,6 @@ import { cloneElement, createHeaderIndex, handleContentScroll, handleToolbarInte
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
export class Header implements ComponentInterface {
|
export class Header implements ComponentInterface {
|
||||||
|
|
||||||
private collapsibleHeaderInitialized = false;
|
|
||||||
private scrollEl?: HTMLElement;
|
private scrollEl?: HTMLElement;
|
||||||
private contentScrollCallback?: any;
|
private contentScrollCallback?: any;
|
||||||
private intersectionObserver?: any;
|
private intersectionObserver?: any;
|
||||||
@ -27,12 +25,12 @@ export class Header implements ComponentInterface {
|
|||||||
@Element() el!: HTMLElement;
|
@Element() el!: HTMLElement;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes the scroll effect that will be applied to the header
|
* Describes the scroll effect that will be applied to the header.
|
||||||
* `condense` only applies in iOS mode.
|
* Only applies in iOS mode.
|
||||||
*
|
*
|
||||||
* Typically used for [Collapsible Large Titles](https://ionicframework.com/docs/api/title#collapsible-large-titles)
|
* Typically used for [Collapsible Large Titles](https://ionicframework.com/docs/api/title#collapsible-large-titles)
|
||||||
*/
|
*/
|
||||||
@Prop() collapse?: 'condense';
|
@Prop() collapse?: 'condense' | 'fade';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If `true`, the header will be translucent.
|
* If `true`, the header will be translucent.
|
||||||
@ -48,12 +46,12 @@ export class Header implements ComponentInterface {
|
|||||||
this.inheritedAttributes = inheritAttributes(this.el, ['role']);
|
this.inheritedAttributes = inheritAttributes(this.el, ['role']);
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidLoad() {
|
componentDidLoad() {
|
||||||
await this.checkCollapsibleHeader();
|
this.checkCollapsibleHeader();
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
await this.checkCollapsibleHeader();
|
this.checkCollapsibleHeader();
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
@ -61,13 +59,17 @@ export class Header implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async checkCollapsibleHeader() {
|
private async checkCollapsibleHeader() {
|
||||||
|
const mode = getIonMode(this);
|
||||||
|
|
||||||
|
if (mode !== 'ios') { return; }
|
||||||
|
|
||||||
|
const { collapse } = this;
|
||||||
|
const hasCondense = collapse === 'condense';
|
||||||
|
const hasFade = collapse === 'fade';
|
||||||
|
|
||||||
// Determine if the header can collapse
|
|
||||||
const hasCollapse = this.collapse === 'condense';
|
|
||||||
const canCollapse = (hasCollapse && getIonMode(this) === 'ios') ? hasCollapse : false;
|
|
||||||
if (!canCollapse && this.collapsibleHeaderInitialized) {
|
|
||||||
this.destroyCollapsibleHeader();
|
this.destroyCollapsibleHeader();
|
||||||
} else if (canCollapse && !this.collapsibleHeaderInitialized) {
|
|
||||||
|
if (hasCondense) {
|
||||||
const pageEl = this.el.closest('ion-app,ion-page,.ion-page,page-inner');
|
const pageEl = this.el.closest('ion-app,ion-page,.ion-page,page-inner');
|
||||||
const contentEl = (pageEl) ? pageEl.querySelector('ion-content') : null;
|
const contentEl = (pageEl) ? pageEl.querySelector('ion-content') : null;
|
||||||
|
|
||||||
@ -78,10 +80,31 @@ export class Header implements ComponentInterface {
|
|||||||
cloneElement('ion-back-button');
|
cloneElement('ion-back-button');
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.setupCollapsibleHeader(contentEl, pageEl);
|
await this.setupCondenseHeader(contentEl, pageEl);
|
||||||
|
} else if (hasFade) {
|
||||||
|
const pageEl = this.el.closest('ion-app,ion-page,.ion-page,page-inner');
|
||||||
|
const contentEl = (pageEl) ? pageEl.querySelector('ion-content') : null;
|
||||||
|
const condenseHeader = (contentEl) ? contentEl.querySelector('ion-header[collapse="condense"]') as HTMLElement | null : null;
|
||||||
|
await this.setupFadeHeader(contentEl, condenseHeader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private setupFadeHeader = async (contentEl: HTMLIonContentElement | null, condenseHeader: HTMLElement | null) => {
|
||||||
|
if (!contentEl) { console.error('ion-header requires a content to collapse. Make sure there is an ion-content.'); return; }
|
||||||
|
|
||||||
|
await new Promise(resolve => componentOnReady(contentEl, resolve));
|
||||||
|
const scrollEl = this.scrollEl = await contentEl.getScrollElement();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle fading of toolbars on scroll
|
||||||
|
*/
|
||||||
|
|
||||||
|
this.contentScrollCallback = () => { handleHeaderFade(this.scrollEl!, this.el, condenseHeader); };
|
||||||
|
scrollEl!.addEventListener('scroll', this.contentScrollCallback);
|
||||||
|
|
||||||
|
handleHeaderFade(this.scrollEl!, this.el, condenseHeader);
|
||||||
|
}
|
||||||
|
|
||||||
private destroyCollapsibleHeader() {
|
private destroyCollapsibleHeader() {
|
||||||
if (this.intersectionObserver) {
|
if (this.intersectionObserver) {
|
||||||
this.intersectionObserver.disconnect();
|
this.intersectionObserver.disconnect();
|
||||||
@ -99,10 +122,11 @@ export class Header implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async setupCollapsibleHeader(contentEl: HTMLIonContentElement | null, pageEl: Element | null) {
|
private async setupCondenseHeader(contentEl: HTMLIonContentElement | null, pageEl: Element | null) {
|
||||||
if (!contentEl || !pageEl) { console.error('ion-header requires a content to collapse, make sure there is an ion-content.'); return; }
|
if (!contentEl || !pageEl) { console.error('ion-header requires a content to collapse, make sure there is an ion-content.'); return; }
|
||||||
if (typeof (IntersectionObserver as any) === 'undefined') { return; }
|
if (typeof (IntersectionObserver as any) === 'undefined') { return; }
|
||||||
|
|
||||||
|
await new Promise(resolve => componentOnReady(contentEl, resolve));
|
||||||
this.scrollEl = await contentEl.getScrollElement();
|
this.scrollEl = await contentEl.getScrollElement();
|
||||||
|
|
||||||
const headers = pageEl.querySelectorAll('ion-header');
|
const headers = pageEl.querySelectorAll('ion-header');
|
||||||
@ -116,10 +140,7 @@ export class Header implements ComponentInterface {
|
|||||||
if (!mainHeaderIndex || !scrollHeaderIndex) { return; }
|
if (!mainHeaderIndex || !scrollHeaderIndex) { return; }
|
||||||
|
|
||||||
setHeaderActive(mainHeaderIndex, false);
|
setHeaderActive(mainHeaderIndex, false);
|
||||||
|
setToolbarBackgroundOpacity(mainHeaderIndex.el, 0);
|
||||||
mainHeaderIndex.toolbars.forEach(toolbar => {
|
|
||||||
setToolbarBackgroundOpacity(toolbar, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle interaction between toolbar collapse and
|
* Handle interaction between toolbar collapse and
|
||||||
@ -145,8 +166,6 @@ export class Header implements ComponentInterface {
|
|||||||
this.collapsibleMainHeader.classList.add('header-collapse-main');
|
this.collapsibleMainHeader.classList.add('header-collapse-main');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.collapsibleHeaderInitialized = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -5,7 +5,7 @@ import { clamp } from '../../utils/helpers';
|
|||||||
const TRANSITION = 'all 0.2s ease-in-out';
|
const TRANSITION = 'all 0.2s ease-in-out';
|
||||||
|
|
||||||
interface HeaderIndex {
|
interface HeaderIndex {
|
||||||
el: HTMLElement;
|
el: HTMLIonHeaderElement;
|
||||||
toolbars: ToolbarIndex[] | [];
|
toolbars: ToolbarIndex[] | [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,11 +64,19 @@ export const handleContentScroll = (scrollEl: HTMLElement, scrollHeaderIndex: He
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setToolbarBackgroundOpacity = (toolbar: ToolbarIndex, opacity?: number) => {
|
export const setToolbarBackgroundOpacity = (headerEl: HTMLIonHeaderElement, opacity?: number) => {
|
||||||
|
/**
|
||||||
|
* Fading in the backdrop opacity
|
||||||
|
* should happen after the large title
|
||||||
|
* has collapsed, so it is handled
|
||||||
|
* by handleHeaderFade()
|
||||||
|
*/
|
||||||
|
if (headerEl.collapse === 'fade') { return; }
|
||||||
|
|
||||||
if (opacity === undefined) {
|
if (opacity === undefined) {
|
||||||
toolbar.background.style.removeProperty('--opacity');
|
headerEl.style.removeProperty('--opacity-scale');
|
||||||
} else {
|
} else {
|
||||||
toolbar.background.style.setProperty('--opacity', opacity.toString());
|
headerEl.style.setProperty('--opacity-scale', opacity.toString());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -88,9 +96,7 @@ const handleToolbarBorderIntersection = (ev: any, mainHeaderIndex: HeaderIndex,
|
|||||||
*/
|
*/
|
||||||
const scale = (ev[0].intersectionRatio > 0.9 || scrollTop <= 0) ? 0 : ((1 - ev[0].intersectionRatio) * 100) / 75;
|
const scale = (ev[0].intersectionRatio > 0.9 || scrollTop <= 0) ? 0 : ((1 - ev[0].intersectionRatio) * 100) / 75;
|
||||||
|
|
||||||
mainHeaderIndex.toolbars.forEach(toolbar => {
|
setToolbarBackgroundOpacity(mainHeaderIndex.el, (scale === 1) ? undefined : scale);
|
||||||
setToolbarBackgroundOpacity(toolbar, (scale === 1) ? undefined : scale);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -136,7 +142,7 @@ export const handleToolbarIntersection = (ev: any, mainHeaderIndex: HeaderIndex,
|
|||||||
if (hasValidIntersection && scrollTop > 0) {
|
if (hasValidIntersection && scrollTop > 0) {
|
||||||
setHeaderActive(mainHeaderIndex);
|
setHeaderActive(mainHeaderIndex);
|
||||||
setHeaderActive(scrollHeaderIndex, false);
|
setHeaderActive(scrollHeaderIndex, false);
|
||||||
setToolbarBackgroundOpacity(mainHeaderIndex.toolbars[0]);
|
setToolbarBackgroundOpacity(mainHeaderIndex.el);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -160,3 +166,37 @@ export const scaleLargeTitles = (toolbars: ToolbarIndex[] = [], scale = 1, trans
|
|||||||
titleDiv.style.transform = `scale3d(${scale}, ${scale}, 1)`;
|
titleDiv.style.transform = `scale3d(${scale}, ${scale}, 1)`;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const handleHeaderFade = (scrollEl: HTMLElement, baseEl: HTMLElement, condenseHeader: HTMLElement | null) => {
|
||||||
|
readTask(() => {
|
||||||
|
const scrollTop = scrollEl.scrollTop;
|
||||||
|
const baseElHeight = baseEl.clientHeight;
|
||||||
|
const fadeStart = (condenseHeader) ? condenseHeader.clientHeight : 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If we are using fade header with a condense
|
||||||
|
* header, then the toolbar backgrounds should
|
||||||
|
* not begin to fade in until the condense
|
||||||
|
* header has fully collapsed.
|
||||||
|
*
|
||||||
|
* Additionally, the main content should not
|
||||||
|
* overflow out of the container until the
|
||||||
|
* condense header has fully collapsed. When
|
||||||
|
* using just the condense header the content
|
||||||
|
* should overflow out of the container.
|
||||||
|
*/
|
||||||
|
if ((condenseHeader !== null) && (scrollTop < fadeStart)) {
|
||||||
|
baseEl.style.setProperty('--opacity-scale', '0');
|
||||||
|
scrollEl.style.setProperty('clip-path', `inset(${baseElHeight}px 0px 0px 0px)`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const distanceToStart = scrollTop - fadeStart;
|
||||||
|
const fadeDuration = 10;
|
||||||
|
const scale = clamp(0, (distanceToStart / fadeDuration), 1);
|
||||||
|
writeTask(() => {
|
||||||
|
scrollEl.style.removeProperty('clip-path');
|
||||||
|
baseEl.style.setProperty('--opacity-scale', scale.toString());
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -3,6 +3,11 @@
|
|||||||
Header is a parent component that holds the toolbar component.
|
Header is a parent component that holds the toolbar component.
|
||||||
It's important to note that ion-header needs to be the one of the three root elements of a page
|
It's important to note that ion-header needs to be the one of the three root elements of a page
|
||||||
|
|
||||||
|
## Fade Header
|
||||||
|
|
||||||
|
The `collapse` property can be set to `'fade'` on a page's main `ion-header` to have the background color of the toolbars fade in as users scroll. This provides the same fade effect that is found in many native iOS applications.
|
||||||
|
|
||||||
|
This functionality can be combined with [Collapsible Large Titles](https://ionicframework.com/docs/api/title#collapsible-large-titles) as well. The `collapse="condense"` value should be set on the `ion-header` inside of your `ion-content`. The `collapse="fade"` value should be set on the `ion-header` outside of your `ion-content`.
|
||||||
|
|
||||||
|
|
||||||
<!-- Auto Generated Below -->
|
<!-- Auto Generated Below -->
|
||||||
@ -10,7 +15,7 @@ It's important to note that ion-header needs to be the one of the three root ele
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Angular / javascript
|
### Angular
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<ion-header>
|
<ion-header>
|
||||||
@ -40,6 +45,69 @@ It's important to note that ion-header needs to be the one of the three root ele
|
|||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|
||||||
|
<!-- Fade Header with collapse header -->
|
||||||
|
<ion-header collapse="fade" [translucent]="true">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Header</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content [fullscreen]="true">
|
||||||
|
<ion-header collapse="condense">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title size="large">Header</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
</ion-content>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Javascript
|
||||||
|
|
||||||
|
```html
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-buttons slot="start">
|
||||||
|
<ion-back-button></ion-back-button>
|
||||||
|
</ion-buttons>
|
||||||
|
<ion-title>My Navigation Bar</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Subheader</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<!-- Header without a border -->
|
||||||
|
<ion-header class="ion-no-border">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Header - No Border</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content>
|
||||||
|
<ion-header collapse="condense">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title size="large">My Navigation Bar</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
</ion-content>
|
||||||
|
|
||||||
|
<!-- Fade Header with collapse header -->
|
||||||
|
<ion-header collapse="fade" translucent="true">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Header</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content fullscreen="true">
|
||||||
|
<ion-header collapse="condense">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title size="large">Header</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
</ion-content>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@ -78,6 +146,21 @@ export const HeaderExample: React.FC = () => (
|
|||||||
</IonToolbar>
|
</IonToolbar>
|
||||||
</IonHeader>
|
</IonHeader>
|
||||||
</IonContent>
|
</IonContent>
|
||||||
|
|
||||||
|
{/*-- Fade Header with collapse header --*/}
|
||||||
|
<IonHeader collapse="fade" translucent={true}>
|
||||||
|
<IonToolbar>
|
||||||
|
<IonTitle>Header</IonTitle>
|
||||||
|
</IonToolbar>
|
||||||
|
</IonHeader>
|
||||||
|
|
||||||
|
<IonContent fullscreen={true}>
|
||||||
|
<IonHeader collapse="condense">
|
||||||
|
<IonToolbar>
|
||||||
|
<IonTitle size="large">Header</IonTitle>
|
||||||
|
</IonToolbar>
|
||||||
|
</IonHeader>
|
||||||
|
</IonContent>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
@ -108,7 +191,7 @@ export class HeaderExample {
|
|||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>,
|
</ion-header>,
|
||||||
|
|
||||||
// Header without a border
|
{/*-- Header without a border --*/}
|
||||||
<ion-header class="ion-no-border">
|
<ion-header class="ion-no-border">
|
||||||
<ion-toolbar>
|
<ion-toolbar>
|
||||||
<ion-title>Header - No Border</ion-title>
|
<ion-title>Header - No Border</ion-title>
|
||||||
@ -121,6 +204,21 @@ export class HeaderExample {
|
|||||||
<ion-title size="large">My Navigation Bar</ion-title>
|
<ion-title size="large">My Navigation Bar</ion-title>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
</ion-content>,
|
||||||
|
|
||||||
|
{/*-- Fade Header with collapse header --*/}
|
||||||
|
<ion-header collapse="fade" translucent={true}>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Header</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content fullscreen={true}>
|
||||||
|
<ion-header collapse="condense">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title size="large">Header</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -159,6 +257,21 @@ export class HeaderExample {
|
|||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|
||||||
|
<!-- Fade Header with collapse header -->
|
||||||
|
<ion-header collapse="fade" :translucent="true">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Header</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content :fullscreen="true">
|
||||||
|
<ion-header collapse="condense">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title size="large">Header</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
</ion-content>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@ -190,8 +303,8 @@ export default defineComponent({
|
|||||||
## Properties
|
## Properties
|
||||||
|
|
||||||
| Property | Attribute | Description | Type | Default |
|
| Property | Attribute | Description | Type | Default |
|
||||||
| ------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | ----------- |
|
| ------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- | ----------- |
|
||||||
| `collapse` | `collapse` | Describes the scroll effect that will be applied to the header `condense` only applies in iOS mode. Typically used for [Collapsible Large Titles](https://ionicframework.com/docs/api/title#collapsible-large-titles) | `"condense" \| undefined` | `undefined` |
|
| `collapse` | `collapse` | Describes the scroll effect that will be applied to the header. Only applies in iOS mode. Typically used for [Collapsible Large Titles](https://ionicframework.com/docs/api/title#collapsible-large-titles) | `"condense" \| "fade" \| undefined` | `undefined` |
|
||||||
| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` |
|
| `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` |
|
| `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` |
|
||||||
|
|
||||||
|
10
core/src/components/header/test/fade/e2e.ts
Normal file
10
core/src/components/header/test/fade/e2e.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { newE2EPage } from '@stencil/core/testing';
|
||||||
|
|
||||||
|
test('header: fade', async () => {
|
||||||
|
const page = await newE2EPage({
|
||||||
|
url: '/src/components/header/test/fade?ionic:_testing=true'
|
||||||
|
});
|
||||||
|
|
||||||
|
const compare = await page.compareScreenshot();
|
||||||
|
expect(compare).toMatchScreenshot();
|
||||||
|
});
|
89
core/src/components/header/test/fade/index.html
Normal file
89
core/src/components/header/test/fade/index.html
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" dir="ltr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Header - Fade</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||||
|
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet">
|
||||||
|
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet">
|
||||||
|
<script src="../../../../../scripts/testing/scripts.js"></script>
|
||||||
|
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
|
||||||
|
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||||
|
<style>
|
||||||
|
.red {
|
||||||
|
background-color: #ea445a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.green {
|
||||||
|
background-color: #76d672;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blue {
|
||||||
|
background-color: #3478f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yellow {
|
||||||
|
background-color: #ffff80;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pink {
|
||||||
|
background-color: #ff6b86;
|
||||||
|
}
|
||||||
|
|
||||||
|
.purple {
|
||||||
|
background-color: #7e34f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.black {
|
||||||
|
background-color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orange {
|
||||||
|
background-color: #f69234;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
grid-gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-item {
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<ion-app>
|
||||||
|
<div class="ion-page">
|
||||||
|
<ion-header collapse="fade" translucent="true">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Mailboxes</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content fullscreen="true">
|
||||||
|
<ion-header collapse="condense">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title size="large">Mailboxes</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
<div class="grid ion-padding">
|
||||||
|
<div class="grid-item red"></div>
|
||||||
|
<div class="grid-item green"></div>
|
||||||
|
<div class="grid-item blue"></div>
|
||||||
|
<div class="grid-item yellow"></div>
|
||||||
|
<div class="grid-item pink"></div>
|
||||||
|
<div class="grid-item purple"></div>
|
||||||
|
<div class="grid-item black"></div>
|
||||||
|
<div class="grid-item orange"></div>
|
||||||
|
</div>
|
||||||
|
</ion-content>
|
||||||
|
<ion-footer translucent="true">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Updated Just Now</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-footer>
|
||||||
|
</div>
|
||||||
|
</ion-app>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -26,4 +26,19 @@
|
|||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|
||||||
|
<!-- Fade Header with collapse header -->
|
||||||
|
<ion-header collapse="fade" [translucent]="true">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Header</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content [fullscreen]="true">
|
||||||
|
<ion-header collapse="condense">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title size="large">Header</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
</ion-content>
|
||||||
```
|
```
|
||||||
|
@ -26,4 +26,19 @@
|
|||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|
||||||
|
<!-- Fade Header with collapse header -->
|
||||||
|
<ion-header collapse="fade" translucent="true">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Header</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content fullscreen="true">
|
||||||
|
<ion-header collapse="condense">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title size="large">Header</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
</ion-content>
|
||||||
```
|
```
|
||||||
|
@ -31,6 +31,21 @@ export const HeaderExample: React.FC = () => (
|
|||||||
</IonToolbar>
|
</IonToolbar>
|
||||||
</IonHeader>
|
</IonHeader>
|
||||||
</IonContent>
|
</IonContent>
|
||||||
|
|
||||||
|
{/*-- Fade Header with collapse header --*/}
|
||||||
|
<IonHeader collapse="fade" translucent={true}>
|
||||||
|
<IonToolbar>
|
||||||
|
<IonTitle>Header</IonTitle>
|
||||||
|
</IonToolbar>
|
||||||
|
</IonHeader>
|
||||||
|
|
||||||
|
<IonContent fullscreen={true}>
|
||||||
|
<IonHeader collapse="condense">
|
||||||
|
<IonToolbar>
|
||||||
|
<IonTitle size="large">Header</IonTitle>
|
||||||
|
</IonToolbar>
|
||||||
|
</IonHeader>
|
||||||
|
</IonContent>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
```
|
```
|
@ -21,7 +21,7 @@ export class HeaderExample {
|
|||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>,
|
</ion-header>,
|
||||||
|
|
||||||
// Header without a border
|
{/*-- Header without a border --*/}
|
||||||
<ion-header class="ion-no-border">
|
<ion-header class="ion-no-border">
|
||||||
<ion-toolbar>
|
<ion-toolbar>
|
||||||
<ion-title>Header - No Border</ion-title>
|
<ion-title>Header - No Border</ion-title>
|
||||||
@ -34,6 +34,21 @@ export class HeaderExample {
|
|||||||
<ion-title size="large">My Navigation Bar</ion-title>
|
<ion-title size="large">My Navigation Bar</ion-title>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
</ion-content>,
|
||||||
|
|
||||||
|
{/*-- Fade Header with collapse header --*/}
|
||||||
|
<ion-header collapse="fade" translucent={true}>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Header</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content fullscreen={true}>
|
||||||
|
<ion-header collapse="condense">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title size="large">Header</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,21 @@
|
|||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|
||||||
|
<!-- Fade Header with collapse header -->
|
||||||
|
<ion-header collapse="fade" :translucent="true">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Header</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content :fullscreen="true">
|
||||||
|
<ion-header collapse="condense">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title size="large">Header</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
</ion-content>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
--border-width: 0;
|
--border-width: 0;
|
||||||
--border-style: solid;
|
--border-style: solid;
|
||||||
--opacity: 1;
|
--opacity: 1;
|
||||||
|
--opacity-scale: 1;
|
||||||
|
|
||||||
@include font-smoothing();
|
@include font-smoothing();
|
||||||
@include padding-horizontal(var(--ion-safe-area-left), var(--ion-safe-area-right));
|
@include padding-horizontal(var(--ion-safe-area-left), var(--ion-safe-area-right));
|
||||||
@ -91,7 +92,7 @@
|
|||||||
background: var(--background);
|
background: var(--background);
|
||||||
|
|
||||||
contain: strict;
|
contain: strict;
|
||||||
opacity: var(--opacity);
|
opacity: calc(var(--opacity) * var(--opacity-scale));
|
||||||
z-index: $z-index-toolbar-background;
|
z-index: $z-index-toolbar-background;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
@ -334,6 +334,7 @@ export const IonFabList = /*@__PURE__*/ defineContainer<JSX.IonFabList>('ion-fab
|
|||||||
|
|
||||||
|
|
||||||
export const IonFooter = /*@__PURE__*/ defineContainer<JSX.IonFooter>('ion-footer', IonFooterCmp, [
|
export const IonFooter = /*@__PURE__*/ defineContainer<JSX.IonFooter>('ion-footer', IonFooterCmp, [
|
||||||
|
'collapse',
|
||||||
'translucent'
|
'translucent'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user