@bitforgehq/ui 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +3665 -0
- package/LICENSE +21 -0
- package/README.md +175 -0
- package/ai/MANIFEST.index.json +383 -0
- package/ai/MANIFEST.json +7528 -0
- package/ai/REFERENCE.index.json +1461 -0
- package/ai/REFERENCE.json +7213 -0
- package/ai/UI_GUIDE.md +1159 -0
- package/ai/components/action-bar.md +209 -0
- package/ai/components/action-sheet.md +526 -0
- package/ai/components/alert-dialog.md +509 -0
- package/ai/components/alert.md +376 -0
- package/ai/components/avatar.md +146 -0
- package/ai/components/back-button.md +286 -0
- package/ai/components/badge.md +171 -0
- package/ai/components/button.md +431 -0
- package/ai/components/card.md +303 -0
- package/ai/components/checkbox.md +418 -0
- package/ai/components/chip-group.md +205 -0
- package/ai/components/chip.md +300 -0
- package/ai/components/content.md +474 -0
- package/ai/components/desktop-toast.md +420 -0
- package/ai/components/dialog.md +962 -0
- package/ai/components/empty-state.md +146 -0
- package/ai/components/field.md +319 -0
- package/ai/components/header.md +809 -0
- package/ai/components/icon-button.md +221 -0
- package/ai/components/icon.md +176 -0
- package/ai/components/input.md +455 -0
- package/ai/components/list-item.md +326 -0
- package/ai/components/list.md +178 -0
- package/ai/components/menu-button.md +377 -0
- package/ai/components/menu.md +661 -0
- package/ai/components/modal.md +613 -0
- package/ai/components/number-input.md +423 -0
- package/ai/components/page.md +299 -0
- package/ai/components/progress.md +215 -0
- package/ai/components/radio-group.md +432 -0
- package/ai/components/refresher.md +377 -0
- package/ai/components/segmented.md +517 -0
- package/ai/components/select.md +510 -0
- package/ai/components/sheet.md +845 -0
- package/ai/components/spinner.md +171 -0
- package/ai/components/stack-outlet.md +661 -0
- package/ai/components/tab-bar.md +427 -0
- package/ai/components/tab.md +295 -0
- package/ai/components/tabs.md +408 -0
- package/ai/components/textarea.md +371 -0
- package/ai/components/toast.md +522 -0
- package/ai/components/toggle.md +387 -0
- package/ai/reference/bf-action-directive.md +21 -0
- package/ai/reference/bf-action-sheet-action-role.md +27 -0
- package/ai/reference/bf-action-sheet-action.md +28 -0
- package/ai/reference/bf-action-sheet-options.md +28 -0
- package/ai/reference/bf-action-sheet-service.md +33 -0
- package/ai/reference/bf-alert-dialog-button-role.md +27 -0
- package/ai/reference/bf-alert-dialog-button.md +27 -0
- package/ai/reference/bf-alert-dialog-options.md +29 -0
- package/ai/reference/bf-alert-dialog-service.md +34 -0
- package/ai/reference/bf-alert-variant.md +28 -0
- package/ai/reference/bf-autocapitalize.md +30 -0
- package/ai/reference/bf-avatar-size.md +27 -0
- package/ai/reference/bf-avatar-variant.md +28 -0
- package/ai/reference/bf-back-at-root.md +28 -0
- package/ai/reference/bf-back-button-service.md +40 -0
- package/ai/reference/bf-badge-variant.md +29 -0
- package/ai/reference/bf-button-size.md +29 -0
- package/ai/reference/bf-button-type.md +28 -0
- package/ai/reference/bf-button-variant.md +29 -0
- package/ai/reference/bf-card-padding.md +26 -0
- package/ai/reference/bf-card-variant.md +28 -0
- package/ai/reference/bf-chip-group-mode.md +27 -0
- package/ai/reference/bf-chip-variant.md +26 -0
- package/ai/reference/bf-compare-with.md +24 -0
- package/ai/reference/bf-component-overlay-ref.md +33 -0
- package/ai/reference/bf-confirm-button-role.md +26 -0
- package/ai/reference/bf-confirm-button.md +27 -0
- package/ai/reference/bf-confirm-options.md +31 -0
- package/ai/reference/bf-confirm-size.md +26 -0
- package/ai/reference/bf-content-scroll-event.md +21 -0
- package/ai/reference/bf-defaults-options.md +32 -0
- package/ai/reference/bf-defaults.md +28 -0
- package/ai/reference/bf-desktop-toast-config.md +28 -0
- package/ai/reference/bf-desktop-toast-harness.md +33 -0
- package/ai/reference/bf-desktop-toast-options.md +28 -0
- package/ai/reference/bf-desktop-toast-position.md +28 -0
- package/ai/reference/bf-desktop-toast-service.md +42 -0
- package/ai/reference/bf-dialog-config.md +44 -0
- package/ai/reference/bf-dialog-harness.md +52 -0
- package/ai/reference/bf-dialog-ref.md +44 -0
- package/ai/reference/bf-dialog-role.md +26 -0
- package/ai/reference/bf-dialog-service.md +55 -0
- package/ai/reference/bf-dialog-size.md +31 -0
- package/ai/reference/bf-dialog-template-context.md +32 -0
- package/ai/reference/bf-empty-state-heading-level.md +26 -0
- package/ai/reference/bf-enter-key-hint.md +31 -0
- package/ai/reference/bf-field-control-class.md +32 -0
- package/ai/reference/bf-field-control-state.md +28 -0
- package/ai/reference/bf-field-size.md +29 -0
- package/ai/reference/bf-haptics-impact-style.md +27 -0
- package/ai/reference/bf-haptics-notification-type.md +27 -0
- package/ai/reference/bf-haptics-service.md +41 -0
- package/ai/reference/bf-harness-filters.md +29 -0
- package/ai/reference/bf-horizontal-scroll-directive.md +129 -0
- package/ai/reference/bf-icon-button-variant.md +28 -0
- package/ai/reference/bf-icon-size.md +27 -0
- package/ai/reference/bf-input-autocapitalize.md +36 -0
- package/ai/reference/bf-input-enter-key-hint.md +38 -0
- package/ai/reference/bf-input-input-mode.md +38 -0
- package/ai/reference/bf-input-mode.md +32 -0
- package/ai/reference/bf-input-type.md +35 -0
- package/ai/reference/bf-jsdom-shim-options.md +22 -0
- package/ai/reference/bf-keyboard-assist-behavior.md +26 -0
- package/ai/reference/bf-keyboard-assist-directive.md +185 -0
- package/ai/reference/bf-keyboard-phase.md +30 -0
- package/ai/reference/bf-keyboard-service.md +50 -0
- package/ai/reference/bf-labels-overrides.md +26 -0
- package/ai/reference/bf-labels.md +44 -0
- package/ai/reference/bf-menu-button-harness.md +40 -0
- package/ai/reference/bf-menu-button-variant.md +27 -0
- package/ai/reference/bf-menu-divider-directive.md +13 -0
- package/ai/reference/bf-menu-group-directive.md +14 -0
- package/ai/reference/bf-menu-harness.md +55 -0
- package/ai/reference/bf-menu-item-checkbox-directive.md +31 -0
- package/ai/reference/bf-menu-item-directive.md +31 -0
- package/ai/reference/bf-menu-item-harness.md +52 -0
- package/ai/reference/bf-menu-item-radio-directive.md +29 -0
- package/ai/reference/bf-menu-placement.md +29 -0
- package/ai/reference/bf-menu-trigger-directive.md +46 -0
- package/ai/reference/bf-modal-options.md +30 -0
- package/ai/reference/bf-modal-presentation.md +26 -0
- package/ai/reference/bf-modal-service.md +38 -0
- package/ai/reference/bf-nav-controller.md +59 -0
- package/ai/reference/bf-nav-direction.md +23 -0
- package/ai/reference/bf-nav-target.md +21 -0
- package/ai/reference/bf-number-input-mode.md +28 -0
- package/ai/reference/bf-number-input-step.md +25 -0
- package/ai/reference/bf-overlay-auto-focus.md +29 -0
- package/ai/reference/bf-overlay-controller.md +22 -0
- package/ai/reference/bf-overlay-host.md +36 -0
- package/ai/reference/bf-overlay-options.md +53 -0
- package/ai/reference/bf-overlay-position.md +30 -0
- package/ai/reference/bf-overlay-ref.md +53 -0
- package/ai/reference/bf-overlay-role.md +27 -0
- package/ai/reference/bf-overlay-service.md +53 -0
- package/ai/reference/bf-overlay-stack-options.md +24 -0
- package/ai/reference/bf-overlay-stack.md +81 -0
- package/ai/reference/bf-overlay-template-context.md +24 -0
- package/ai/reference/bf-overscroll-stretch-directive.md +163 -0
- package/ai/reference/bf-page-context.md +24 -0
- package/ai/reference/bf-platform-service.md +53 -0
- package/ai/reference/bf-platform.md +28 -0
- package/ai/reference/bf-progress-size.md +26 -0
- package/ai/reference/bf-progress-tone.md +28 -0
- package/ai/reference/bf-radio-group-harness.md +49 -0
- package/ai/reference/bf-radio-group-orientation.md +26 -0
- package/ai/reference/bf-radio-option.md +30 -0
- package/ai/reference/bf-refresher-refresh-event.md +22 -0
- package/ai/reference/bf-segment-option.md +29 -0
- package/ai/reference/bf-segmented-harness.md +48 -0
- package/ai/reference/bf-segmented-size.md +29 -0
- package/ai/reference/bf-select-harness.md +70 -0
- package/ai/reference/bf-select-option.md +29 -0
- package/ai/reference/bf-sheet-auto-focus.md +29 -0
- package/ai/reference/bf-sheet-options.md +32 -0
- package/ai/reference/bf-sheet-ref.md +45 -0
- package/ai/reference/bf-sheet-service.md +34 -0
- package/ai/reference/bf-shell-config-mode.md +28 -0
- package/ai/reference/bf-shell-config.md +38 -0
- package/ai/reference/bf-shell-dynamic-type-limit.md +21 -0
- package/ai/reference/bf-shell-dynamic-type.md +25 -0
- package/ai/reference/bf-shell-mode.md +28 -0
- package/ai/reference/bf-shell-testing-options.md +26 -0
- package/ai/reference/bf-size.md +45 -0
- package/ai/reference/bf-spinner-size.md +27 -0
- package/ai/reference/bf-stack-change.md +22 -0
- package/ai/reference/bf-stack-reuse-strategy.md +32 -0
- package/ai/reference/bf-stack-transition-event.md +26 -0
- package/ai/reference/bf-stack-transition.md +26 -0
- package/ai/reference/bf-stacked-overlay.md +23 -0
- package/ai/reference/bf-system-bars-style.md +38 -0
- package/ai/reference/bf-tab-bar-controller.md +51 -0
- package/ai/reference/bf-tab-bar-host.md +52 -0
- package/ai/reference/bf-tab-context.md +32 -0
- package/ai/reference/bf-tab-links.md +37 -0
- package/ai/reference/bf-testing-options.md +27 -0
- package/ai/reference/bf-text-scale-service.md +35 -0
- package/ai/reference/bf-theme.md +27 -0
- package/ai/reference/bf-toast-action.md +25 -0
- package/ai/reference/bf-toast-controller.md +17 -0
- package/ai/reference/bf-toast-dismiss-reason.md +29 -0
- package/ai/reference/bf-toast-options.md +29 -0
- package/ai/reference/bf-toast-position.md +26 -0
- package/ai/reference/bf-toast-ref.md +41 -0
- package/ai/reference/bf-toast-service.md +39 -0
- package/ai/reference/bf-toggle-label-position.md +26 -0
- package/ai/reference/bf-transition-kind.md +28 -0
- package/ai/reference/bf-validation-message.md +31 -0
- package/ai/reference/bf-validation-messages.md +33 -0
- package/ai/reference/bf-view-event.md +28 -0
- package/ai/reference/bf-view-lifecycle.md +41 -0
- package/ai/reference/bf-view-state.md +28 -0
- package/ai/reference/bf_default_labels.md +21 -0
- package/ai/reference/bf_default_values.md +21 -0
- package/ai/reference/bf_defaults.md +21 -0
- package/ai/reference/bf_desktop_toast_defaults.md +21 -0
- package/ai/reference/bf_dialog_data.md +17 -0
- package/ai/reference/bf_labels.md +21 -0
- package/ai/reference/bf_overlay_data.md +19 -0
- package/ai/reference/bf_overlay_host.md +21 -0
- package/ai/reference/bf_sheet_default_snap_points.md +17 -0
- package/ai/reference/bf_shell_config.md +21 -0
- package/ai/reference/bf_shell_defaults.md +21 -0
- package/ai/reference/bf_tab_bar.md +21 -0
- package/ai/reference/bf_tab_bar_controller.md +21 -0
- package/ai/reference/bf_tab_context.md +21 -0
- package/ai/reference/bf_tab_links.md +21 -0
- package/ai/reference/bf_theme_storage.md +19 -0
- package/ai/reference/create-transition.md +36 -0
- package/ai/reference/duration_fast.md +17 -0
- package/ai/reference/duration_md.md +17 -0
- package/ai/reference/duration_overlay.md +17 -0
- package/ai/reference/duration_push.md +17 -0
- package/ai/reference/fade-down.md +26 -0
- package/ai/reference/fade-in.md +25 -0
- package/ai/reference/fade-out.md +25 -0
- package/ai/reference/fade-up.md +26 -0
- package/ai/reference/inject-shell-mode.md +23 -0
- package/ai/reference/install-bf-jsdom-shims.md +31 -0
- package/ai/reference/ios_duration.md +17 -0
- package/ai/reference/ios_easing.md +17 -0
- package/ai/reference/is-text-entry.md +25 -0
- package/ai/reference/max_complete_ms.md +17 -0
- package/ai/reference/md-transition.md +31 -0
- package/ai/reference/md_duration.md +17 -0
- package/ai/reference/md_easing.md +17 -0
- package/ai/reference/overlay_duration.md +21 -0
- package/ai/reference/overlay_easing.md +19 -0
- package/ai/reference/overlay_reduced_duration.md +17 -0
- package/ai/reference/pop-frames-at.md +25 -0
- package/ai/reference/prefers-reduced-motion.md +25 -0
- package/ai/reference/provide-bf-defaults.md +33 -0
- package/ai/reference/provide-bf-desktop-toast.md +28 -0
- package/ai/reference/provide-bf-labels.md +38 -0
- package/ai/reference/provide-bf-shell-testing.md +34 -0
- package/ai/reference/provide-bf-shell.md +41 -0
- package/ai/reference/provide-bf-testing.md +33 -0
- package/ai/reference/reduced-transition.md +28 -0
- package/ai/reference/reduced_duration.md +17 -0
- package/ai/reference/resolve-shell-mode.md +33 -0
- package/ai/reference/scale-in.md +25 -0
- package/ai/reference/settle-duration.md +25 -0
- package/ai/reference/settle-pop.md +33 -0
- package/ai/reference/settle_easing.md +17 -0
- package/ai/reference/should-complete-pop.md +26 -0
- package/ai/reference/slide-down.md +26 -0
- package/ai/reference/slide-up.md +26 -0
- package/ai/reference/with-bf-shell-routing.md +19 -0
- package/bin/bf-ui.js +81 -0
- package/fesm2022/bitforgehq-ui-icons.mjs +230 -0
- package/fesm2022/bitforgehq-ui-icons.mjs.map +1 -0
- package/fesm2022/bitforgehq-ui-mobile.mjs +5530 -0
- package/fesm2022/bitforgehq-ui-mobile.mjs.map +1 -0
- package/fesm2022/bitforgehq-ui-number-input.mjs +680 -0
- package/fesm2022/bitforgehq-ui-number-input.mjs.map +1 -0
- package/fesm2022/bitforgehq-ui-overlay.mjs +2675 -0
- package/fesm2022/bitforgehq-ui-overlay.mjs.map +1 -0
- package/fesm2022/bitforgehq-ui-testing.mjs +402 -0
- package/fesm2022/bitforgehq-ui-testing.mjs.map +1 -0
- package/fesm2022/bitforgehq-ui.mjs +7765 -0
- package/fesm2022/bitforgehq-ui.mjs.map +1 -0
- package/llms-full.txt +25394 -0
- package/llms.txt +329 -0
- package/package.json +94 -0
- package/theme.css +253 -0
- package/types/bitforgehq-ui-icons.d.ts +50 -0
- package/types/bitforgehq-ui-mobile.d.ts +2914 -0
- package/types/bitforgehq-ui-number-input.d.ts +282 -0
- package/types/bitforgehq-ui-overlay.d.ts +1514 -0
- package/types/bitforgehq-ui-testing.d.ts +495 -0
- package/types/bitforgehq-ui.d.ts +3499 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
# bf-tabs
|
|
2
|
+
|
|
3
|
+
Tabs page with one navigation stack per tab, wiring the tab bar to the router
|
|
4
|
+
|
|
5
|
+
`BfTabsComponent` from `@bitforgehq/ui/mobile` · Navigation · since 0.2.0
|
|
6
|
+
|
|
7
|
+
## Example
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { Component, computed, inject } from '@angular/core';
|
|
11
|
+
import { BfTabBarComponent, BfTabComponent } from '@bitforgehq/ui';
|
|
12
|
+
import { BfIconComponent } from '@bitforgehq/ui/icons';
|
|
13
|
+
import { BfStackOutletComponent, BfTabsComponent } from '@bitforgehq/ui/mobile';
|
|
14
|
+
import { Folder, Inbox, Sun, User } from 'lucide';
|
|
15
|
+
import { InboxStateService } from '../../services/state/inbox.state.service';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The tabbed app: one navigation stack per tab under `/tabs/<tab>`, and the floating tab bar with
|
|
19
|
+
* the Inbox unread badge.
|
|
20
|
+
*/
|
|
21
|
+
@Component({
|
|
22
|
+
selector: 'app-tabs',
|
|
23
|
+
host: { class: 'block h-full' },
|
|
24
|
+
imports: [
|
|
25
|
+
BfTabsComponent,
|
|
26
|
+
BfStackOutletComponent,
|
|
27
|
+
BfTabBarComponent,
|
|
28
|
+
BfTabComponent,
|
|
29
|
+
BfIconComponent,
|
|
30
|
+
],
|
|
31
|
+
template: `
|
|
32
|
+
<bf-tabs tabsPath="tabs">
|
|
33
|
+
<bf-stack-outlet />
|
|
34
|
+
<bf-tab-bar label="Main">
|
|
35
|
+
<bf-tab id="today" label="Today"><bf-icon [icon]="sun" /></bf-tab>
|
|
36
|
+
<bf-tab id="projects" label="Projects"><bf-icon [icon]="folder" /></bf-tab>
|
|
37
|
+
<bf-tab id="inbox" label="Inbox" [badge]="inboxBadge()"><bf-icon [icon]="inbox" /></bf-tab>
|
|
38
|
+
<bf-tab id="me" label="Me"><bf-icon [icon]="user" /></bf-tab>
|
|
39
|
+
</bf-tab-bar>
|
|
40
|
+
</bf-tabs>
|
|
41
|
+
`,
|
|
42
|
+
})
|
|
43
|
+
export class TabsPage {
|
|
44
|
+
private readonly inboxState = inject(InboxStateService);
|
|
45
|
+
|
|
46
|
+
/** Unread messages on the Inbox tab; no badge at zero. */
|
|
47
|
+
protected readonly inboxBadge = computed(() => this.inboxState.unread() || null);
|
|
48
|
+
protected readonly sun = Sun;
|
|
49
|
+
protected readonly folder = Folder;
|
|
50
|
+
protected readonly inbox = Inbox;
|
|
51
|
+
protected readonly user = User;
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Overview
|
|
56
|
+
|
|
57
|
+
The tabs page of a mobile app: one navigation stack per tab. It wraps a `bf-stack-outlet`, which
|
|
58
|
+
keeps a separate stack for every tab (keyed by the URL segment after `tabsPath`), and a
|
|
59
|
+
`bf-tab-bar`, which it drives through `BF_TAB_BAR_CONTROLLER`: the bar shows the tab of the
|
|
60
|
+
in-flight navigation's target, else of the current URL, and hides while the keyboard is open,
|
|
61
|
+
without anything writing into it. Selecting another tab brings
|
|
62
|
+
that tab back as it was left; re-selecting the active tab pops its stack to the root, or scrolls
|
|
63
|
+
its content to the top when it is already there. The bar hides while the soft keyboard is open
|
|
64
|
+
or about to open (a focused text control), and `--bf-tab-bar-inset` on the host tells
|
|
65
|
+
`bf-content` how much to pad its bottom (the bar's measured height, a `ResizeObserver` on its
|
|
66
|
+
`nav`, plus its bottom offset and 8 px; 64 px for the height until it is measured); the same value is mirrored on `<html>` as
|
|
67
|
+
`--bf-tab-bar-inset-global` for overlays (the toast), which render outside the host. It is removed when the tabs are destroyed.
|
|
68
|
+
It also provides `BF_TAB_LINKS`, so the projected bar is a `nav` of links to each tab's root URL
|
|
69
|
+
(`aria-current` on the active one: `page` at the tab's root, `true` on a child page of it)
|
|
70
|
+
instead of a `tablist`.
|
|
71
|
+
|
|
72
|
+
The tabs page of a mobile app, with one navigation stack per tab. `bf-tabs` wraps a
|
|
73
|
+
`bf-stack-outlet` and a `bf-tab-bar`: the outlet keeps a separate stack for every tab, keyed by the
|
|
74
|
+
URL segment after the tabs route, so switching tabs parks the current tab's whole stack (depth,
|
|
75
|
+
scroll and state kept) and brings the other one back exactly as it was left. The bar derives its
|
|
76
|
+
selection from the router (through `BF_TAB_BAR_CONTROLLER`: the target of a navigation in flight,
|
|
77
|
+
else the current tab route), the bar hides while the soft keyboard is open, and
|
|
78
|
+
`--bf-tab-bar-inset` tells `bf-content` how far to pad its bottom.
|
|
79
|
+
|
|
80
|
+
## When to use
|
|
81
|
+
|
|
82
|
+
Use `bf-tabs` for the tab root of a Capacitor app: one navigation stack per tab behind a floating [bf-tab-bar](https://bitforge-ui.web.app/components/tab-bar). Use [bf-stack-outlet](https://bitforge-ui.web.app/components/stack-outlet) instead for an app with one stack and no tabs, and a sidebar of links on desktop.
|
|
83
|
+
|
|
84
|
+
## Basic usage
|
|
85
|
+
|
|
86
|
+
The tabs page is a route with one child route per tab page; deeper pages of a tab are more child
|
|
87
|
+
routes that start with the tab's segment. Every `bf-tab`'s `id` is that segment.
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import { Component } from '@angular/core';
|
|
91
|
+
import { type Routes } from '@angular/router';
|
|
92
|
+
import { BfTabBarComponent, BfTabComponent } from '@bitforgehq/ui';
|
|
93
|
+
import { BfIconComponent } from '@bitforgehq/ui/icons';
|
|
94
|
+
import {
|
|
95
|
+
BfContentComponent,
|
|
96
|
+
BfHeaderComponent,
|
|
97
|
+
BfPageComponent,
|
|
98
|
+
BfStackOutletComponent,
|
|
99
|
+
BfTabsComponent,
|
|
100
|
+
} from '@bitforgehq/ui/mobile';
|
|
101
|
+
import { Folder, Sun, User } from 'lucide';
|
|
102
|
+
|
|
103
|
+
/** A tab's root page (the three tabs share it here). */
|
|
104
|
+
@Component({
|
|
105
|
+
selector: 'app-tab-root-page',
|
|
106
|
+
imports: [BfContentComponent, BfHeaderComponent, BfPageComponent],
|
|
107
|
+
template: `
|
|
108
|
+
<bf-page>
|
|
109
|
+
<bf-header title="Tab" />
|
|
110
|
+
<bf-content><p class="p-4">A tab's first page.</p></bf-content>
|
|
111
|
+
</bf-page>
|
|
112
|
+
`,
|
|
113
|
+
})
|
|
114
|
+
export class TabRootPage {}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Tabs bound to the router.
|
|
118
|
+
*
|
|
119
|
+
* Each tab is a child route of `tabs` with a stack of its own; the tab bar inside `bf-tabs`
|
|
120
|
+
* becomes links to the tabs' roots.
|
|
121
|
+
*
|
|
122
|
+
* @preview none
|
|
123
|
+
*/
|
|
124
|
+
@Component({
|
|
125
|
+
selector: 'app-example-tabs-basic',
|
|
126
|
+
imports: [
|
|
127
|
+
BfIconComponent,
|
|
128
|
+
BfStackOutletComponent,
|
|
129
|
+
BfTabBarComponent,
|
|
130
|
+
BfTabComponent,
|
|
131
|
+
BfTabsComponent,
|
|
132
|
+
],
|
|
133
|
+
template: `
|
|
134
|
+
<bf-tabs tabsPath="tabs">
|
|
135
|
+
<bf-stack-outlet />
|
|
136
|
+
<bf-tab-bar>
|
|
137
|
+
<bf-tab id="today" label="Today"><bf-icon [icon]="sun" /></bf-tab>
|
|
138
|
+
<bf-tab id="projects" label="Projects"><bf-icon [icon]="folder" /></bf-tab>
|
|
139
|
+
<bf-tab id="me" label="Me"><bf-icon [icon]="user" /></bf-tab>
|
|
140
|
+
</bf-tab-bar>
|
|
141
|
+
</bf-tabs>
|
|
142
|
+
`,
|
|
143
|
+
})
|
|
144
|
+
export class TabsBasicExampleComponent {
|
|
145
|
+
protected readonly sun = Sun;
|
|
146
|
+
protected readonly folder = Folder;
|
|
147
|
+
protected readonly user = User;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** The tabs route: one child path per tab, plus the pages pushed inside a tab. */
|
|
151
|
+
export const routes: Routes = [
|
|
152
|
+
{
|
|
153
|
+
path: 'tabs',
|
|
154
|
+
component: TabsBasicExampleComponent,
|
|
155
|
+
children: [
|
|
156
|
+
{ path: 'today', component: TabRootPage },
|
|
157
|
+
{ path: 'today/task/:id', component: TabRootPage },
|
|
158
|
+
{ path: 'projects', component: TabRootPage },
|
|
159
|
+
{ path: 'me', component: TabRootPage },
|
|
160
|
+
{ path: '', redirectTo: 'today', pathMatch: 'full' },
|
|
161
|
+
],
|
|
162
|
+
},
|
|
163
|
+
];
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
`tabsPath` is the route path of the tabs page (`'tabs'` above, or `'app/tabs'` for a nested one).
|
|
167
|
+
Navigate inside a tab with `BfNavController.forward()` as anywhere else: `/tabs/today/task/4`
|
|
168
|
+
pushes onto the Today stack. An empty `tabsPath` (`tabsPath=""`) mounts the tabs page at the root
|
|
169
|
+
URL — the tab key is the first URL segment (`/today`, `/me`) with no `tabs` prefix; give the tabs
|
|
170
|
+
route `path: ''` with the same shape of children.
|
|
171
|
+
|
|
172
|
+
## Switching and re-selecting tabs
|
|
173
|
+
|
|
174
|
+
Selecting another tab (a plain click on its link) navigates to where that tab was left (the URL
|
|
175
|
+
of the top page of its stack, or `/tabs/<id>` the first time) and, once that navigation has succeeded, plays
|
|
176
|
+
`BfHapticsService.selection()` when `hapticsOnTabChange` is on in `provideBfShell()` (a switch a
|
|
177
|
+
guard refuses plays nothing). A tab switch has no transition and no swipe-back;
|
|
178
|
+
`BfNavController.depth`, `canGoBack` and `back()` refer to the active tab's stack while the tabs
|
|
179
|
+
page is shown, so the back button pops within the tab. A page pushed on an outer outlet above the
|
|
180
|
+
tabs page (`/gallery`) parks the tabs page with every tab stack intact until it is popped. Selecting the tab that is already active pops its stack back to
|
|
181
|
+
its existing root page (one history step back, so the root keeps its scroll and state) when it is
|
|
182
|
+
deeper than one page, or scrolls its `bf-content` back to the top when it is already at the root.
|
|
183
|
+
|
|
184
|
+
## Soft keyboard
|
|
185
|
+
|
|
186
|
+
While `BfKeyboardService.visible()` or `BfKeyboardService.imminent()` is true the bar slides down
|
|
187
|
+
out of view (the projected `bf-tab-bar` reads `hidden()` from `BF_TAB_BAR_CONTROLLER`): a 200 ms `translate`/`opacity`
|
|
188
|
+
transition on iOS, instantly on Android (where the WebView resizes under the keyboard) and on the
|
|
189
|
+
web. `imminent()` turns true on `focusin` of a text control, before any keyboard event: on Android
|
|
190
|
+
the WebView shrinks before the plugin's will-show, and a bar hidden only then would first be lifted
|
|
191
|
+
above the keyboard. It comes back when the keyboard hides or the control loses focus, and 2.5 s
|
|
192
|
+
after the focus when no keyboard shows at all (a hardware keyboard, such as an iPad Magic Keyboard,
|
|
193
|
+
fires no keyboard event).
|
|
194
|
+
|
|
195
|
+
## Desktop
|
|
196
|
+
|
|
197
|
+
`bf-tabs` is part of the Capacitor shell (`provideBfShell()`). On a desktop web app put the top-level destinations in a sidebar or a top `nav` of links with `routerLinkActive`, and a plain `router-outlet`. Inside the shell under a mouse, the tab links take the `bf-tab` hover and focus styles.
|
|
198
|
+
|
|
199
|
+
## Theming
|
|
200
|
+
|
|
201
|
+
- `--bf-tab-bar-inset` on the `bf-tabs` host:
|
|
202
|
+
`calc(<height>px + 8px + max(var(--spacing-safe-bottom), 12px) + 8px)` (the bar's measured
|
|
203
|
+
height, its bottom offset of 8 px above the safe area with a 12 px floor, and 8 px of room above
|
|
204
|
+
the bar: 114 px on an iPhone with a home indicator at the default text size, 104 px with Android
|
|
205
|
+
gesture navigation, 92 px without an inset) while a bar is present and visible, `0px` while it is
|
|
206
|
+
hidden or absent. The height is measured with a `ResizeObserver` on the projected bar's `nav`, so
|
|
207
|
+
the inset follows a bar that grows with large text (a two-line label at Android font scale 2.0,
|
|
208
|
+
iOS Dynamic Type) or slims down at compact heights (phone landscape); until the first
|
|
209
|
+
measurement it is the bar's 64 px minimum. The observer is disconnected with the tabs. `bf-content`
|
|
210
|
+
pads its scroller's bottom by it; use it for anything else that must clear the bar.
|
|
211
|
+
- `--bf-tab-bar-inset-global` on `<html>`: the same value, for overlays (the toast), which render in
|
|
212
|
+
the CDK overlay container outside `bf-tabs`; removed when the tabs are destroyed. Only overlays
|
|
213
|
+
read it — a page pushed outside `bf-tabs` keeps no bottom padding.
|
|
214
|
+
- The bar itself keeps the `bf-tab-bar` tokens (`surface`, `shadow-floating`, `primary-soft`).
|
|
215
|
+
- The host is `display: block; height: 100%` around a `relative flex h-full flex-col` column; give
|
|
216
|
+
the tabs page a height (it fills the outlet frame it is routed into).
|
|
217
|
+
|
|
218
|
+
## Accessibility
|
|
219
|
+
|
|
220
|
+
`bf-tabs` provides `BF_TAB_LINKS`, so its bar is a navigation landmark of links, not a `tablist`:
|
|
221
|
+
the tabs switch routes, not in-page panels, which is what the
|
|
222
|
+
[WAI-ARIA APG link pattern](https://www.w3.org/WAI/ARIA/apg/patterns/link/) is for (the
|
|
223
|
+
[tabs pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tabs/) assumes tab panels in the same
|
|
224
|
+
page). The bar is `nav` > a plain group > one `<a href>` per tab, pointing at the tab's root URL
|
|
225
|
+
(`/tabs/<id>`, through the app's `LocationStrategy`, so the base href is included), with
|
|
226
|
+
`aria-current` on the active tab: `page` while its root page is shown, `true` on a child page of it
|
|
227
|
+
(the tab is then the current section, not the current page). Tab and Shift+Tab move between the
|
|
228
|
+
tabs, Enter follows one, and the arrow keys do nothing; Ctrl/Cmd-click or a middle click opens the tab's root in a new
|
|
229
|
+
browser tab. A plain click is not followed as a link: the bar turns it into a selection, so
|
|
230
|
+
`bf-tabs` still brings the tab back where it was left, plays its haptic and pops to the root on
|
|
231
|
+
reselect. A standalone `bf-tab-bar` outside `bf-tabs` keeps its `tablist` semantics. While the
|
|
232
|
+
keyboard hides it, the bar's `nav` is `aria-hidden="true"` and `inert`, so it can be neither
|
|
233
|
+
focused nor read. Each tab's
|
|
234
|
+
stack follows `bf-stack-outlet`'s rules: parked stacks are `visibility: hidden`, `inert` and
|
|
235
|
+
`aria-hidden`, and after a switch focus moves to the restored page's title (`data-bf-page-title`).
|
|
236
|
+
|
|
237
|
+
**Safari and iOS WKWebView (the Capacitor iOS app) do not move Tab to links by default.** With a
|
|
238
|
+
hardware keyboard (an iPad with a Magic Keyboard, a Mac), Tab skips the tab bar's links unless the
|
|
239
|
+
user turns on "Press Tab to highlight each item" (Safari settings on macOS; on iPadOS the
|
|
240
|
+
keyboard's Full Keyboard Access); Option+Tab reaches them otherwise. VoiceOver and Full Keyboard
|
|
241
|
+
Access navigation are unaffected, and Chromium-based browsers and Android WebView tab to links as
|
|
242
|
+
usual. The old `button role="tab"` bar was reachable with plain Tab in WebKit; the link bar trades
|
|
243
|
+
that for real URLs, opening a tab in a new browser tab and `aria-current`.
|
|
244
|
+
|
|
245
|
+
Only the `bf-tab-bar` placed directly inside `<bf-tabs>` in the same template becomes links. An
|
|
246
|
+
in-page `bf-tab-bar` inside a tab's page (a routed page) keeps its `tablist`.
|
|
247
|
+
|
|
248
|
+
### Keyboard
|
|
249
|
+
|
|
250
|
+
In Safari and iOS WKWebView, Tab reaches the links only with "Press Tab to highlight each item" on (Option+Tab otherwise).
|
|
251
|
+
|
|
252
|
+
| Key | Action |
|
|
253
|
+
| ------------------------------------------------ | --------------------------------------------------------- |
|
|
254
|
+
| <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> | Moves between the tab links; a disabled tab is skipped |
|
|
255
|
+
| <kbd>Enter</kbd> | Follows the focused tab's link; the arrow keys do nothing |
|
|
256
|
+
|
|
257
|
+
## Inputs
|
|
258
|
+
|
|
259
|
+
| Name | Type | Default | Description |
|
|
260
|
+
| ---------- | -------- | -------- | ------------------------------------------------------------------------------------- |
|
|
261
|
+
| `tabsPath` | `string` | required | Route path of the tabs page (e.g. `'tabs'`); the URL segment after it is the tab key. |
|
|
262
|
+
|
|
263
|
+
## Slots
|
|
264
|
+
|
|
265
|
+
- `default`: The tabs page body: a `bf-stack-outlet` and a `bf-tab-bar`.
|
|
266
|
+
|
|
267
|
+
## Methods
|
|
268
|
+
|
|
269
|
+
### `keyOf(url: string): string | null`
|
|
270
|
+
|
|
271
|
+
The tab key of `url`: the primary segment after `tabsPath`. Query, fragment, matrix params and
|
|
272
|
+
auxiliary routes (`(popup:…)`) are ignored.
|
|
273
|
+
|
|
274
|
+
| Parameter | Type | Description |
|
|
275
|
+
| --------- | -------- | ---------------- |
|
|
276
|
+
| `url` | `string` | The URL to read. |
|
|
277
|
+
|
|
278
|
+
### `href(id: string): string`
|
|
279
|
+
|
|
280
|
+
The URL of tab `id`'s root page (`/tabs/<id>`, with the app's base href), the `href` of its
|
|
281
|
+
link in the bar: a new browser tab opens the tab's root. A plain click still restores the page
|
|
282
|
+
the tab was left on.
|
|
283
|
+
|
|
284
|
+
| Parameter | Type | Description |
|
|
285
|
+
| --------- | -------- | -------------------- |
|
|
286
|
+
| `id` | `string` | The `id` of the tab. |
|
|
287
|
+
|
|
288
|
+
### `isRoot(id: string): boolean`
|
|
289
|
+
|
|
290
|
+
Whether the current URL is tab `id`'s root page (`…/<tabsPath>/<id>` with nothing after it,
|
|
291
|
+
ignoring query, fragment and auxiliary routes); reactive through the router's signals.
|
|
292
|
+
|
|
293
|
+
| Parameter | Type | Description |
|
|
294
|
+
| --------- | -------- | -------------------- |
|
|
295
|
+
| `id` | `string` | The `id` of the tab. |
|
|
296
|
+
|
|
297
|
+
### `select(id: string): void`
|
|
298
|
+
|
|
299
|
+
Another tab, selected by the user in the bar: go back to where that tab was left (its stack's
|
|
300
|
+
top page), or its root. The bar shows the target while the navigation is in flight and drops
|
|
301
|
+
back by itself when a guard refuses it.
|
|
302
|
+
|
|
303
|
+
| Parameter | Type | Description |
|
|
304
|
+
| --------- | -------- | ----------------------------- |
|
|
305
|
+
| `id` | `string` | The `id` of the selected tab. |
|
|
306
|
+
|
|
307
|
+
### `reselect(id: string): void`
|
|
308
|
+
|
|
309
|
+
The active tab again: pop its stack back to its existing root page (a history step), or
|
|
310
|
+
replace the stack with the root when the history position is unknown; at the root, scroll its
|
|
311
|
+
content to the top.
|
|
312
|
+
|
|
313
|
+
| Parameter | Type | Description |
|
|
314
|
+
| --------- | -------- | --------------------------- |
|
|
315
|
+
| `id` | `string` | The `id` of the active tab. |
|
|
316
|
+
|
|
317
|
+
## Notes
|
|
318
|
+
|
|
319
|
+
### Do
|
|
320
|
+
|
|
321
|
+
- Give every `bf-tab` the `id` of its route segment, and start every page of a tab with it.
|
|
322
|
+
- Put `bf-content` in every tab page so it pads for the bar.
|
|
323
|
+
|
|
324
|
+
### Don't
|
|
325
|
+
|
|
326
|
+
- Don't bind `[(active)]` or `(changed)` on the bar inside `bf-tabs`; `bf-tabs` owns both.
|
|
327
|
+
- Don't look for `role="tab"` inside `bf-tabs` in tests or scripts; query the tabs as links
|
|
328
|
+
(`getByRole('link', { name: 'Projects' })`) and the active one by `aria-current` (`page` at the
|
|
329
|
+
tab's root, `true` below it).
|
|
330
|
+
- Don't nest a second `bf-tabs` inside a tab.
|
|
331
|
+
|
|
332
|
+
## Migration notes
|
|
333
|
+
|
|
334
|
+
### 0.3.0
|
|
335
|
+
|
|
336
|
+
**Breaking:** (ARIA contract) `bf-tabs` provides `BF_TAB_LINKS`, so the `bf-tab-bar` projected
|
|
337
|
+
directly into it is a `nav` of links with `aria-current` (`page` at the tab's root, `true` on a
|
|
338
|
+
child page) instead of a `tablist` (the tabs switch routes, not in-page panels); an in-page bar on
|
|
339
|
+
a tab's page keeps its `tablist`. New `href(id)`: the tab's root URL, `/<tabsPath>/<id>` through
|
|
340
|
+
`LocationStrategy` (base href included), used as each tab's `href`, so Ctrl/Cmd-click opens a tab
|
|
341
|
+
in a new browser tab; a plain click still restores the tab's last page. New `isRoot(id)`: whether
|
|
342
|
+
the current URL is that root. Tab moves between tabs (in Safari and iOS WKWebView only with "Press
|
|
343
|
+
Tab to highlight each item", else Option+Tab); the arrow keys no longer do (they stopped after one
|
|
344
|
+
step before). See Migration 11.
|
|
345
|
+
|
|
346
|
+
JSDoc of `tab-context.ts` for the API reference: `@summary`/`@since` on the exported symbols;
|
|
347
|
+
`@param` docs. No behaviour change.
|
|
348
|
+
|
|
349
|
+
JSDoc of `tabs.component.ts` for the API reference: `@slot` docs for `default`; `@param` docs. No
|
|
350
|
+
behaviour change.
|
|
351
|
+
|
|
352
|
+
**Behaviour:** the bar is driven through `BF_TAB_BAR_CONTROLLER` instead of written into (F-021).
|
|
353
|
+
`current()` (read-only) is the tab of the in-flight navigation's target after redirects once they
|
|
354
|
+
are applied (`router.currentNavigation()`), else of
|
|
355
|
+
the settled URL (`router.lastSuccessfulNavigation()`), keeping the last tab while neither is under
|
|
356
|
+
`tabsPath`: a clicked tab shows at once and a navigation a guard refuses drops back by itself.
|
|
357
|
+
`hidden()` is `keyboard.visible() || keyboard.imminent()`, `hideTransition()` is true on iOS, and
|
|
358
|
+
`select(id)`/`reselect(id)` are the former `changed`/`reselected` handlers. `tabKey()` and
|
|
359
|
+
`isRoot(id)` read the router's navigation signals; the `router.events` subscription and the three
|
|
360
|
+
effects that wrote into the bar are gone. `--bf-tab-bar-inset` (and `--bf-tab-bar-inset-global` on
|
|
361
|
+
`<html>`) is written in the render phase (`afterRenderEffect`). See Migration 24.
|
|
362
|
+
|
|
363
|
+
**Behaviour:** `--bf-tab-bar-inset` (and `--bf-tab-bar-inset-global`) use the bar's measured height
|
|
364
|
+
instead of the fixed 4 rem (F-051, F-054): an `afterRenderEffect` observes the projected bar's
|
|
365
|
+
`nav` with a `ResizeObserver` and writes
|
|
366
|
+
`calc(<height>px + 8px + max(var(--spacing-safe-bottom), 12px) + 8px)`, or `0px` while the bar is
|
|
367
|
+
hidden; until the first measurement it uses the bar's 64 px minimum. The observer is disconnected on
|
|
368
|
+
destroy. So the page's bottom padding follows a bar grown by large text or slimmed at compact
|
|
369
|
+
heights.
|
|
370
|
+
|
|
371
|
+
### 0.2.0
|
|
372
|
+
|
|
373
|
+
New `bf-tabs` (`tabsPath` required input): the tabs page with one navigation stack per tab. It
|
|
374
|
+
provides `BF_TAB_CONTEXT` (`tabKey` signal from `NavigationEnd.urlAfterRedirects`, `keyOf(url)` =
|
|
375
|
+
the primary segment after `tabsPath`, parsed with `Router.parseUrl`, so query, fragment, matrix
|
|
376
|
+
params and auxiliary routes are ignored) to the projected `bf-stack-outlet`, sets the
|
|
377
|
+
projected `bf-tab-bar`'s `active` to the tab key, and handles its selections: another tab navigates
|
|
378
|
+
with `nav.forward()` to where that tab was left (`outlet.lastUrlOf(key)`, else `/<tabsPath>/<key>`)
|
|
379
|
+
and, once it has succeeded, `BfHapticsService.selection()` when `hapticsOnTabChange`, putting the
|
|
380
|
+
bar back (without a haptic) if a guard refuses; the active tab again (`tabReselect`) at depth > 1 pops the stack to its existing root page
|
|
381
|
+
(`outlet.popToRoot()`, one `Location.historyGo(-n)` step computed from the outlet's history
|
|
382
|
+
positions, so the root keeps its DOM, scroll and state), falling back to
|
|
383
|
+
`nav.root(['/', tabsPath, key])` when the position is unknown; at depth 1 it scrolls the active
|
|
384
|
+
page's content to the top. It hides the bar while
|
|
385
|
+
`BfKeyboardService.visible()` (`setHidden()`, animated on iOS only, instant on Android and the web) and writes
|
|
386
|
+
`--bf-tab-bar-inset` on its host: `calc(64px + 24px + var(--spacing-safe-bottom))` while a bar is
|
|
387
|
+
present and visible, `0px` otherwise. The same value is mirrored on `<html>` as
|
|
388
|
+
`--bf-tab-bar-inset-global` for overlays, which render outside the host (the toast reads it), and
|
|
389
|
+
removed on destroy; `bf-content` keeps reading `--bf-tab-bar-inset`, so a page outside `bf-tabs`
|
|
390
|
+
gets no bottom padding.
|
|
391
|
+
|
|
392
|
+
Fixed: on Android the bar flashed above the keyboard, because the WebView shrinks (lifting the fixed
|
|
393
|
+
bar) before `keyboardWillShow`. The bar now hides on `BfKeyboardService.visible() ||
|
|
394
|
+
BfKeyboardService.imminent()`, i.e. already on `focusin` of a text control.
|
|
395
|
+
|
|
396
|
+
Fixed: an empty `tabsPath` (the tabs mounted at the root URL) built `['/', '', key]`, a doubled
|
|
397
|
+
slash the router turned into a broken navigation. `onTabChange` and `onTabReselect` now build the
|
|
398
|
+
command array from the non-empty segments of `tabsPath`, so `['/', key]` results when it is empty.
|
|
399
|
+
|
|
400
|
+
Changed: `--bf-tab-bar-inset` (and `--bf-tab-bar-inset-global`) follows the bar's new position:
|
|
401
|
+
`calc(64px + 8px + max(var(--spacing-safe-bottom), 12px) + 8px)` (the bar's height, its offset of
|
|
402
|
+
8 px above the safe area with a 12 px floor, and 8 px of room above the bar; 114 px on an iPhone
|
|
403
|
+
with a home indicator, was 122 px), replacing `calc(64px + 24px + var(--spacing-safe-bottom))`.
|
|
404
|
+
|
|
405
|
+
Fixed: the inset counts the bar's height in `rem` (`calc(4rem + 8px + max(var(--spacing-safe-bottom),
|
|
406
|
+
12px) + 8px)`), like the bar's own `h-16`. With iOS Dynamic Type (`BfTextScaleService`) the bar
|
|
407
|
+
grows with the root font size; a fixed 64 px left the end of every page under the bar at the
|
|
408
|
+
accessibility sizes (at AX3 the bar is 150 px tall). Unchanged at the default text size.
|