Skip to content

Commit

Permalink
docs(content, fab): fixed slot placement (#3563)
Browse files Browse the repository at this point in the history
Co-authored-by: Liam DeBeasi <[email protected]>
  • Loading branch information
mapsandapps and liamdebeasi authored Apr 2, 2024
1 parent 680fb37 commit 9d20cab
Show file tree
Hide file tree
Showing 9 changed files with 342 additions and 0 deletions.
2 changes: 2 additions & 0 deletions docs/api/content.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ import Fullscreen from '@site/static/usage/v8/content/fullscreen/index.md';

To place elements outside of the scrollable area, assign them to the `fixed` slot. Doing so will [absolutely position](https://developer.mozilla.org/en-US/docs/Web/CSS/position#absolute_positioning) the element to the top left of the content. In order to change the position of the element, it can be styled using the [top, right, bottom, and left](https://developer.mozilla.org/en-US/docs/Web/CSS/position) CSS properties.

The `fixedSlotPlacement` property is used to determine if content in the `fixed` slot is placed before or after the main content in the DOM. When set to `before`, fixed slot content will be placed before the main content and will therefore receive keyboard focus before the main content receives keyboard focus. This can be useful when the main content contains an infinitely-scrolling list, preventing a [FAB](./fab) or other fixed content from being reachable by pressing the tab key.

import Fixed from '@site/static/usage/v8/content/fixed/index.md';

<Fixed />
Expand Down
10 changes: 10 additions & 0 deletions docs/api/fab.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,16 @@ import SafeArea from '@site/static/usage/v8/fab/safe-area/index.md';

<SafeArea />

### Relative to Infinite List

In scenarios where a view contains many interactive elements, such as an infinitely-scrolling list, it may be challenging for users to navigate to the Floating Action Button (FAB) if it is placed below all the items in the DOM.

By setting the `fixedSlotPlacement` property on [Content](./content) to `before`, the FAB will be placed before the main content in the DOM. This ensures that the FAB receives keyboard focus before other interactive elements receive focus, making it easier for users to access the FAB.

import BeforeContent from '@site/static/usage/v8/fab/before-content/index.md';

<BeforeContent />

## Button Sizing

Setting the `size` property of the main fab button to `"small"` will render it at a mini size. Note that this property will not have an effect when used with the inner fab buttons.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
```html
<ion-content fixed-slot-placement="before">
<ion-fab horizontal="end" vertical="bottom" slot="fixed">
<ion-fab-button>
<ion-icon name="add"></ion-icon>
</ion-fab-button>
</ion-fab>
<ion-list>
<ion-item [button]="true" *ngFor="let item of items; let index">
<ion-avatar slot="start">
<img [src]="'https://picsum.photos/80/80?random=' + index" alt="avatar" />
</ion-avatar>
<ion-label>{{ item }}</ion-label>
</ion-item>
</ion-list>
<ion-infinite-scroll (ionInfinite)="onIonInfinite($event)">
<ion-infinite-scroll-content></ion-infinite-scroll-content>
</ion-infinite-scroll>
</ion-content>
```
31 changes: 31 additions & 0 deletions static/usage/v8/fab/before-content/angular/example_component_ts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
```tsx
import { Component, OnInit } from '@angular/core';

import { InfiniteScrollCustomEvent } from '@ionic/angular';

@Component({
selector: 'app-example',
templateUrl: 'example.component.html',
})
export class ExampleComponent implements OnInit {
items = [];

ngOnInit() {
this.generateItems();
}

private generateItems() {
const count = this.items.length + 1;
for (let i = 0; i < 50; i++) {
this.items.push(`Item ${count + i}`);
}
}

onIonInfinite(ev) {
this.generateItems();
setTimeout(() => {
(ev as InfiniteScrollCustomEvent).target.complete();
}, 500);
}
}
```
66 changes: 66 additions & 0 deletions static/usage/v8/fab/before-content/demo.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Fab</title>
<link rel="stylesheet" href="../../common.css" />
<script src="../../common.js"></script>
<script type="module" src="https://cdn.jsdelivr.net/npm/@ionic/core@next/dist/ionic/ionic.esm.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ionic/core@next/css/ionic.bundle.css" />
</head>

<body>
<ion-app>
<ion-content fixed-slot-placement="before">
<ion-fab horizontal="end" vertical="bottom" slot="fixed">
<ion-fab-button>
<ion-icon name="add"></ion-icon>
</ion-fab-button>
</ion-fab>
<ion-list></ion-list>
<ion-infinite-scroll>
<ion-infinite-scroll-content></ion-infinite-scroll-content>
</ion-infinite-scroll>
</ion-content>
</ion-app>
<script>
const infiniteScroll = document.querySelector('ion-infinite-scroll');
infiniteScroll.addEventListener('ionInfinite', (event) => {
setTimeout(() => {
generateItems();
event.target.complete();
}, 500);
});

const list = document.querySelector('ion-list');

function generateItems() {
const count = list.childElementCount + 1;
const total = count + 50;
for (let i = count; i < total; i++) {
const el = document.createElement('ion-item');
el.button = true;

const avatar = document.createElement('ion-avatar');
avatar.slot = 'start';
const img = document.createElement('img');
img.src = `https://picsum.photos/80/80?random=${i}`;
img.alt = 'Avatar';

avatar.appendChild(img);
el.appendChild(avatar);

const text = document.createElement('ion-label');
text.innerText = `Item ${i}`;

el.appendChild(text);

list.appendChild(el);
}
}

generateItems();
</script>
</body>
</html>
25 changes: 25 additions & 0 deletions static/usage/v8/fab/before-content/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import Playground from '@site/src/components/global/Playground';

import javascript from './javascript.md';
import react from './react.md';
import vue from './vue.md';

import angular_example_component_html from './angular/example_component_html.md';
import angular_example_component_ts from './angular/example_component_ts.md';

<Playground
version="8"
code={{
javascript,
react,
vue,
angular: {
files: {
'src/app/example.component.html': angular_example_component_html,
'src/app/example.component.ts': angular_example_component_ts,
},
},
}}
src="usage/v8/fab/before-content/demo.html"
size="medium"
/>
52 changes: 52 additions & 0 deletions static/usage/v8/fab/before-content/javascript.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
```html
<ion-content fixed-slot-placement="before">
<ion-fab horizontal="end" vertical="bottom" slot="fixed">
<ion-fab-button>
<ion-icon name="add"></ion-icon>
</ion-fab-button>
</ion-fab>
<ion-list></ion-list>
<ion-infinite-scroll>
<ion-infinite-scroll-content></ion-infinite-scroll-content>
</ion-infinite-scroll>
</ion-content>

<script>
const infiniteScroll = document.querySelector('ion-infinite-scroll');
infiniteScroll.addEventListener('ionInfinite', (event) => {
setTimeout(() => {
generateItems();
event.target.complete();
}, 500);
});
const list = document.querySelector('ion-list');
function generateItems() {
const count = list.childElementCount + 1;
const total = count + 50;
for (let i = count; i < total; i++) {
const el = document.createElement('ion-item');
el.setAttribute('button', '');
const avatar = document.createElement('ion-avatar');
avatar.slot = 'start';
const img = document.createElement('img');
img.src = `https://picsum.photos/80/80?random=${i}`;
img.alt = 'Avatar';
avatar.appendChild(img);
el.appendChild(avatar);
const text = document.createElement('ion-label');
text.innerText = `Item ${i}`;
el.appendChild(text);
list.appendChild(el);
}
}
generateItems();
</script>
```
61 changes: 61 additions & 0 deletions static/usage/v8/fab/before-content/react.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
```tsx
import React, { useState, useEffect } from 'react';
import {
IonAvatar,
IonContent,
IonFab,
IonFabButton,
IonIcon,
IonInfiniteScroll,
IonInfiniteScrollContent,
IonItem,
IonLabel,
IonList,
} from '@ionic/react';

function Example() {
const [items, setItems] = useState<string[]>([]);

const generateItems = () => {
const newItems = [];
for (let i = 0; i < 50; i++) {
newItems.push(`Item ${1 + items.length + i}`);
}
setItems([...items, ...newItems]);
};

useEffect(() => {
generateItems();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

return (
<IonContent fixedSlotPlacement="before">
<IonFab horizontal="end" vertical="bottom" slot="fixed">
<IonFabButton>
<IonIcon name="add"></IonIcon>
</IonFabButton>
</IonFab>
<IonList>
{items.map((item, index) => (
<IonItem key={item} button>
<IonAvatar slot="start">
<img src={'https://picsum.photos/80/80?random=' + index} alt="avatar" />
</IonAvatar>
<IonLabel>{item}</IonLabel>
</IonItem>
))}
</IonList>
<IonInfiniteScroll
onIonInfinite={(ev) => {
generateItems();
setTimeout(() => ev.target.complete(), 500);
}}
>
<IonInfiniteScrollContent></IonInfiniteScrollContent>
</IonInfiniteScroll>
</IonContent>
);
}
export default Example;
```
75 changes: 75 additions & 0 deletions static/usage/v8/fab/before-content/vue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
```html
<template>
<ion-content fixed-slot-placement="before">
<ion-fab horizontal="end" vertical="bottom" slot="fixed">
<ion-fab-button>
<ion-icon name="add"></ion-icon>
</ion-fab-button>
</ion-fab>
<ion-list>
<ion-item v-for="(item, index) in items" button>
<ion-avatar slot="start">
<img :src="'https://picsum.photos/80/80?random=' + index" alt="avatar" />
</ion-avatar>
<ion-label>{{ item }}</ion-label>
</ion-item>
</ion-list>
<ion-infinite-scroll @ionInfinite="ionInfinite">
<ion-infinite-scroll-content></ion-infinite-scroll-content>
</ion-infinite-scroll>
</ion-content>
</template>

<script lang="ts">
import {
InfiniteScrollCustomEvent,
IonAvatar,
IonContent,
IonFab,
IonFabButton,
IonIcon,
IonImg,
IonInfiniteScroll,
IonInfiniteScrollContent,
IonItem,
IonLabel,
IonList,
} from '@ionic/vue';
import { defineComponent, reactive } from 'vue';
export default defineComponent({
components: {
IonAvatar,
IonContent,
IonFab,
IonFabButton,
IonIcon,
IonImg,
IonInfiniteScroll,
IonInfiniteScrollContent,
IonItem,
IonLabel,
IonList,
},
setup() {
const items = reactive([]);
const generateItems = () => {
const count = items.length + 1;
for (let i = 0; i < 50; i++) {
items.push(`Item ${count + i}`);
}
};
const ionInfinite = (ev: InfiniteScrollCustomEvent) => {
generateItems();
setTimeout(() => ev.target.complete(), 500);
};
generateItems();
return { ionInfinite, items };
},
});
</script>
```

0 comments on commit 9d20cab

Please sign in to comment.