diff --git a/docs/api/content.md b/docs/api/content.md index 832e22cd9d1..c8e9568530f 100644 --- a/docs/api/content.md +++ b/docs/api/content.md @@ -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'; diff --git a/docs/api/fab.md b/docs/api/fab.md index 3e665ad8159..d3f5e77ced6 100644 --- a/docs/api/fab.md +++ b/docs/api/fab.md @@ -67,6 +67,16 @@ import SafeArea from '@site/static/usage/v8/fab/safe-area/index.md'; +### 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'; + + + ## 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. diff --git a/static/usage/v8/fab/before-content/angular/example_component_html.md b/static/usage/v8/fab/before-content/angular/example_component_html.md new file mode 100644 index 00000000000..494d73158e6 --- /dev/null +++ b/static/usage/v8/fab/before-content/angular/example_component_html.md @@ -0,0 +1,20 @@ +```html + + + + + + + + + + avatar + + {{ item }} + + + + + + +``` diff --git a/static/usage/v8/fab/before-content/angular/example_component_ts.md b/static/usage/v8/fab/before-content/angular/example_component_ts.md new file mode 100644 index 00000000000..7a4ee72cdd0 --- /dev/null +++ b/static/usage/v8/fab/before-content/angular/example_component_ts.md @@ -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); + } +} +``` diff --git a/static/usage/v8/fab/before-content/demo.html b/static/usage/v8/fab/before-content/demo.html new file mode 100644 index 00000000000..1c7e04ef66b --- /dev/null +++ b/static/usage/v8/fab/before-content/demo.html @@ -0,0 +1,66 @@ + + + + + + Fab + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/usage/v8/fab/before-content/index.md b/static/usage/v8/fab/before-content/index.md new file mode 100644 index 00000000000..79e37a29118 --- /dev/null +++ b/static/usage/v8/fab/before-content/index.md @@ -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'; + + diff --git a/static/usage/v8/fab/before-content/javascript.md b/static/usage/v8/fab/before-content/javascript.md new file mode 100644 index 00000000000..dd3581091eb --- /dev/null +++ b/static/usage/v8/fab/before-content/javascript.md @@ -0,0 +1,52 @@ +```html + + + + + + + + + + + + + +``` diff --git a/static/usage/v8/fab/before-content/react.md b/static/usage/v8/fab/before-content/react.md new file mode 100644 index 00000000000..b3eef14cc40 --- /dev/null +++ b/static/usage/v8/fab/before-content/react.md @@ -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([]); + + 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 ( + + + + + + + + {items.map((item, index) => ( + + + avatar + + {item} + + ))} + + { + generateItems(); + setTimeout(() => ev.target.complete(), 500); + }} + > + + + + ); +} +export default Example; +``` diff --git a/static/usage/v8/fab/before-content/vue.md b/static/usage/v8/fab/before-content/vue.md new file mode 100644 index 00000000000..f8ff0c5ae4f --- /dev/null +++ b/static/usage/v8/fab/before-content/vue.md @@ -0,0 +1,75 @@ +```html + + + +```