@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,427 @@
|
|
|
1
|
+
# bf-tab-bar
|
|
2
|
+
|
|
3
|
+
Floating pill tab bar for mobile apps with arrow-key selection and count badges
|
|
4
|
+
|
|
5
|
+
`BfTabBarComponent` from `@bitforgehq/ui` · Navigation · since 0.1.0
|
|
6
|
+
|
|
7
|
+
## Example
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { Component, computed, signal } from '@angular/core';
|
|
11
|
+
import { BfTabBarComponent, BfTabComponent } from '@bitforgehq/ui';
|
|
12
|
+
import { BfIconComponent } from '@bitforgehq/ui/icons';
|
|
13
|
+
import { Folder, Inbox, Sun, User } from 'lucide';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The bf-tab-bar overview.
|
|
17
|
+
*
|
|
18
|
+
* Live demo of `bf-tab-bar`: four destinations with a badge on Projects.
|
|
19
|
+
*/
|
|
20
|
+
@Component({
|
|
21
|
+
selector: 'app-example-tab-bar-overview',
|
|
22
|
+
imports: [BfTabBarComponent, BfTabComponent, BfIconComponent],
|
|
23
|
+
template: `
|
|
24
|
+
<bf-tab-bar [(active)]="active" [floating]="false">
|
|
25
|
+
<bf-tab id="today" label="Today"><bf-icon [icon]="sun" /></bf-tab>
|
|
26
|
+
<bf-tab id="projects" label="Projects" [badge]="3"><bf-icon [icon]="folder" /></bf-tab>
|
|
27
|
+
<bf-tab id="inbox" label="Inbox"><bf-icon [icon]="inbox" /></bf-tab>
|
|
28
|
+
<bf-tab id="me" label="Me"><bf-icon [icon]="user" /></bf-tab>
|
|
29
|
+
</bf-tab-bar>
|
|
30
|
+
<p data-status aria-live="polite" class="text-caption text-text-muted mt-3">{{ status() }}</p>
|
|
31
|
+
`,
|
|
32
|
+
})
|
|
33
|
+
export class TabBarOverviewExampleComponent {
|
|
34
|
+
readonly active = signal('today');
|
|
35
|
+
readonly sun = Sun;
|
|
36
|
+
readonly folder = Folder;
|
|
37
|
+
readonly inbox = Inbox;
|
|
38
|
+
readonly user = User;
|
|
39
|
+
|
|
40
|
+
readonly status = computed(() => `Active tab: ${this.active()}`);
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Overview
|
|
45
|
+
|
|
46
|
+
Floating pill tab bar for mobile apps (Meadow Sage): at least 64 px tall (it grows with a label
|
|
47
|
+
wrapped to two lines at large text sizes; at least 48 px, with icon and label side by side, at
|
|
48
|
+
compact heights such as phone landscape), inset 14 px from the sides and
|
|
49
|
+
8 px above the bottom safe area (at least 12 px, so 20 px from the edge without an inset), one
|
|
50
|
+
`bf-tab` per destination. On its own it is a pure control with an `active` model and a `hidden`
|
|
51
|
+
input: a `tablist` whose selection follows focus with the arrow keys. Inside `bf-tabs`, which
|
|
52
|
+
provides `BF_TAB_LINKS` and `BF_TAB_BAR_CONTROLLER`, it is a `nav` of links with `aria-current`
|
|
53
|
+
on the active tab (`page` on its root page, `true` on a child page of it) whose selection and
|
|
54
|
+
hiding derive from the router and the keyboard; `active` is not written there.
|
|
55
|
+
|
|
56
|
+
The floating pill tab bar (Meadow Sage) for a mobile app's three-to-five top-level destinations —
|
|
57
|
+
Today, Projects, Inbox, Me. It is a mobile pattern only: use it for the phone shell, not a desktop
|
|
58
|
+
web layout. `bf-tab-bar` is the container; each destination inside it is a
|
|
59
|
+
[`bf-tab`](https://bitforge-ui.web.app/components/tab), icon in the default slot with a `label`
|
|
60
|
+
underneath.
|
|
61
|
+
|
|
62
|
+
## When to use
|
|
63
|
+
|
|
64
|
+
Use `bf-tab-bar` for the three to five top-level destinations of a phone app, at the bottom of the screen; inside [bf-tabs](https://bitforge-ui.web.app/components/tabs) it switches routes. Use [bf-segmented](https://bitforge-ui.web.app/components/segmented) instead to switch a view in place, and a sidebar or a top `nav` of links for a desktop web layout.
|
|
65
|
+
|
|
66
|
+
## Basic usage
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { Component, signal } from '@angular/core';
|
|
70
|
+
import { BfTabBarComponent, BfTabComponent } from '@bitforgehq/ui';
|
|
71
|
+
import { BfIconComponent } from '@bitforgehq/ui/icons';
|
|
72
|
+
import { Folder, Inbox, Sun, User } from 'lucide';
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The floating tab bar of a phone app.
|
|
76
|
+
*
|
|
77
|
+
* Four destinations; `active` is two-way bound and `changed` reports each switch. The bar is
|
|
78
|
+
* fixed above the bottom safe area, so the docs show its code only (the inline bar is live below).
|
|
79
|
+
*
|
|
80
|
+
* @preview none
|
|
81
|
+
*/
|
|
82
|
+
@Component({
|
|
83
|
+
selector: 'app-example-tab-bar-basic',
|
|
84
|
+
imports: [BfTabBarComponent, BfTabComponent, BfIconComponent],
|
|
85
|
+
template: `
|
|
86
|
+
<bf-tab-bar [(active)]="tab" (changed)="go($event)">
|
|
87
|
+
<bf-tab id="today" label="Today"><bf-icon [icon]="sun" /></bf-tab>
|
|
88
|
+
<bf-tab id="projects" label="Projects"><bf-icon [icon]="folder" /></bf-tab>
|
|
89
|
+
<bf-tab id="inbox" label="Inbox"><bf-icon [icon]="inbox" /></bf-tab>
|
|
90
|
+
<bf-tab id="me" label="Me"><bf-icon [icon]="user" /></bf-tab>
|
|
91
|
+
</bf-tab-bar>
|
|
92
|
+
`,
|
|
93
|
+
})
|
|
94
|
+
export class TabBarBasicExampleComponent {
|
|
95
|
+
protected readonly sun = Sun;
|
|
96
|
+
protected readonly folder = Folder;
|
|
97
|
+
protected readonly inbox = Inbox;
|
|
98
|
+
protected readonly user = User;
|
|
99
|
+
protected readonly tab = signal('today');
|
|
100
|
+
readonly visited = signal<string[]>([]);
|
|
101
|
+
|
|
102
|
+
protected go(id: string): void {
|
|
103
|
+
this.visited.update((ids) => [...ids, id]);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Tabs
|
|
109
|
+
|
|
110
|
+
Each destination is a `bf-tab`: an icon in its default slot, a `label` under it, an `id` as the selection key, and optionally a `badge` or `disabled`. Tabs have [their own page](https://bitforge-ui.web.app/components/tab), with badges and disabled tabs.
|
|
111
|
+
|
|
112
|
+
## Inline in a layout
|
|
113
|
+
|
|
114
|
+
`floating=false` drops the fixed positioning and floating shadow so the bar sits inline, e.g. in a
|
|
115
|
+
docs demo or a layout that already reserves the space itself.
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import { Component, signal } from '@angular/core';
|
|
119
|
+
import { BfTabBarComponent, BfTabComponent } from '@bitforgehq/ui';
|
|
120
|
+
import { BfIconComponent } from '@bitforgehq/ui/icons';
|
|
121
|
+
import { Home, Search } from 'lucide';
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* A tab bar placed inline in the layout instead of floating.
|
|
125
|
+
*
|
|
126
|
+
* A page with more than one tab bar names each (`label`), so their landmarks stay apart.
|
|
127
|
+
*/
|
|
128
|
+
@Component({
|
|
129
|
+
selector: 'app-example-tab-bar-inline',
|
|
130
|
+
imports: [BfTabBarComponent, BfTabComponent, BfIconComponent],
|
|
131
|
+
template: `
|
|
132
|
+
<bf-tab-bar label="Sections" [(active)]="tab" [floating]="false">
|
|
133
|
+
<bf-tab id="home" label="Home"><bf-icon [icon]="home" /></bf-tab>
|
|
134
|
+
<bf-tab id="search" label="Search"><bf-icon [icon]="search" /></bf-tab>
|
|
135
|
+
</bf-tab-bar>
|
|
136
|
+
`,
|
|
137
|
+
})
|
|
138
|
+
export class TabBarInlineExampleComponent {
|
|
139
|
+
protected readonly home = Home;
|
|
140
|
+
protected readonly search = Search;
|
|
141
|
+
protected readonly tab = signal('home');
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Hiding
|
|
146
|
+
|
|
147
|
+
`hidden` slides the bar down out of view (`translate-y` plus `opacity-0`, no pointer events) and
|
|
148
|
+
makes it `aria-hidden` and `inert`; `hideTransition` (default `true`) animates that over 200 ms,
|
|
149
|
+
`false` switches instantly. `setHidden(hidden, transition?)` is the imperative counterpart for a
|
|
150
|
+
parent that projects the bar and so cannot bind the input. The bar is hidden while either is true,
|
|
151
|
+
or while the `hidden()` of a `BF_TAB_BAR_CONTROLLER` is (below).
|
|
152
|
+
|
|
153
|
+
Both inputs take boolean attributes, so a static `<bf-tab-bar hidden hideTransition="false">` works
|
|
154
|
+
too. `hidden` deliberately shadows the global HTML attribute: on `bf-tab-bar` it is this input, and
|
|
155
|
+
the attribute itself is removed from the host, so the bar slides out instead of becoming
|
|
156
|
+
`display: none`.
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
import { Component, signal } from '@angular/core';
|
|
160
|
+
import { BfButtonComponent, BfTabBarComponent, BfTabComponent } from '@bitforgehq/ui';
|
|
161
|
+
import { BfIconComponent } from '@bitforgehq/ui/icons';
|
|
162
|
+
import { Newspaper, User } from 'lucide';
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* A tab bar that slides out of view.
|
|
166
|
+
*
|
|
167
|
+
* `hidden` stands in for "the keyboard is open" here; the hidden bar is `inert` and
|
|
168
|
+
* `aria-hidden`.
|
|
169
|
+
*/
|
|
170
|
+
@Component({
|
|
171
|
+
selector: 'app-example-tab-bar-hiding',
|
|
172
|
+
imports: [BfButtonComponent, BfTabBarComponent, BfTabComponent, BfIconComponent],
|
|
173
|
+
template: `
|
|
174
|
+
<div class="flex flex-col items-center gap-4 overflow-hidden pb-2">
|
|
175
|
+
<bf-button variant="outline" size="sm" (clicked)="keyboardOpen.set(!keyboardOpen())">
|
|
176
|
+
{{ keyboardOpen() ? 'Close the keyboard' : 'Open the keyboard' }}
|
|
177
|
+
</bf-button>
|
|
178
|
+
<bf-tab-bar label="Feed" [(active)]="tab" [floating]="false" [hidden]="keyboardOpen()">
|
|
179
|
+
<bf-tab id="feed" label="Feed"><bf-icon [icon]="newspaper" /></bf-tab>
|
|
180
|
+
<bf-tab id="profile" label="Profile"><bf-icon [icon]="user" /></bf-tab>
|
|
181
|
+
</bf-tab-bar>
|
|
182
|
+
</div>
|
|
183
|
+
`,
|
|
184
|
+
})
|
|
185
|
+
export class TabBarHidingExampleComponent {
|
|
186
|
+
protected readonly newspaper = Newspaper;
|
|
187
|
+
protected readonly user = User;
|
|
188
|
+
protected readonly tab = signal('feed');
|
|
189
|
+
protected readonly keyboardOpen = signal(false);
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Re-selecting a tab
|
|
194
|
+
|
|
195
|
+
Clicking the tab that is already active emits `reselected` with its id instead of `changed`,
|
|
196
|
+
e.g. to scroll the tab's page back to the top. The 0.2 names `tabChange` and `tabReselect` still
|
|
197
|
+
emit alongside them until 0.4.0.
|
|
198
|
+
|
|
199
|
+
## Driven by a parent
|
|
200
|
+
|
|
201
|
+
A parent that projects the bar and drives it provides `BF_TAB_BAR_CONTROLLER`, a
|
|
202
|
+
[BfTabBarController](https://bitforge-ui.web.app/api/bf-tab-bar-controller) (`bf-tabs` does): the bar then shows the
|
|
203
|
+
controller's `current()` as its selection (none while it is `null`), hides while its `hidden()` is
|
|
204
|
+
true with the transition `hideTransition()` gives, and hands a user selection to its `select(id)`
|
|
205
|
+
or `reselect(id)` while still emitting `changed` or `reselected`. It never writes `active` then, so
|
|
206
|
+
nothing has to put the bar back after a navigation a guard refuses: the controller's `current()`
|
|
207
|
+
drops back by itself. Only a bar declared directly inside the provider's template is driven.
|
|
208
|
+
|
|
209
|
+
## Desktop
|
|
210
|
+
|
|
211
|
+
`bf-tab-bar` is a phone pattern. On a desktop web layout, put the top-level destinations in a sidebar or a top `nav` of links, and switch views in place with a [bf-segmented](https://bitforge-ui.web.app/components/segmented). Under a mouse (a tablet with a trackpad, the docs preview) an inactive tab takes a `surface-muted` fill and the active capsule `primary-soft-hover` on hover, with the pointer cursor, and the focus ring shows on keyboard focus only.
|
|
212
|
+
|
|
213
|
+
## Theming
|
|
214
|
+
|
|
215
|
+
- Bar: `surface` background, `shadow-floating` when floating (`shadow-raised` inline); it floats
|
|
216
|
+
8 px above the bottom safe area, with a 12 px floor where there is none:
|
|
217
|
+
`bottom: calc(max(var(--spacing-safe-bottom), 12px) + 8px)` (42 px on an iPhone with a home
|
|
218
|
+
indicator, 32 px with Android gesture navigation, 20 px without an inset), 14 px from the sides.
|
|
219
|
+
Hidden, it slides down by its own height plus 64 px, clear of the screen edge and its shadow.
|
|
220
|
+
- Height: at least 64 px (`min-h-[64px]`, in px, so iOS Dynamic Type, which scales the root and
|
|
221
|
+
every rem, does not grow the bar itself, as UITabBar does not). It grows instead of clipping
|
|
222
|
+
when a label wraps to two lines at a large text size (the label is capped at 22 px), so it tops
|
|
223
|
+
out near 98 px. At compact heights (`(max-height: 500px)`, phone landscape) it is at least
|
|
224
|
+
48 px (`min-h-[48px]`) with each
|
|
225
|
+
tab's icon and label side by side; the bottom offset stays. `bf-tabs` measures the bar, so the
|
|
226
|
+
page's bottom inset follows either way.
|
|
227
|
+
- Shape: `rounded-control` (`--radius-control`, `9999px` by default).
|
|
228
|
+
- The tabs' capsule, badge and disabled tokens are on the [`bf-tab` page](https://bitforge-ui.web.app/components/tab#theming).
|
|
229
|
+
|
|
230
|
+
## Accessibility
|
|
231
|
+
|
|
232
|
+
The bar is a `<nav>` named by `label`, else `provideBfLabels()`'s `tabBar` (`"Main"`). What it
|
|
233
|
+
holds depends on where it is used.
|
|
234
|
+
|
|
235
|
+
**Standalone (in-page selection): a tablist.** The `nav` contains a `role="tablist"`; each `bf-tab`
|
|
236
|
+
is a native `<button role="tab">` with `aria-selected` mirroring selection, following the
|
|
237
|
+
[WAI-ARIA APG tabs pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tabs/) with automatic
|
|
238
|
+
activation. One tab sits in the tab order (roving `tabindex`): the active tab when it is enabled,
|
|
239
|
+
otherwise the first enabled tab — so the bar is never removed from the tab order, even with an
|
|
240
|
+
empty `active`, before a binding resolves, or when the active tab is disabled. ArrowLeft/ArrowRight
|
|
241
|
+
move the selection and focus together between enabled tabs, wrapping at the ends, and Home/End jump
|
|
242
|
+
to the first/last enabled tab. `disabled` tabs are native-disabled, skipped by keyboard navigation
|
|
243
|
+
and cannot be clicked.
|
|
244
|
+
|
|
245
|
+
**Inside `bf-tabs` (router-bound): a `nav` of links.** `bf-tabs` provides `BF_TAB_LINKS`, and the bar
|
|
246
|
+
switches pages rather than in-page panels, so it follows the
|
|
247
|
+
[APG link pattern](https://www.w3.org/WAI/ARIA/apg/patterns/link/) instead of the tabs pattern
|
|
248
|
+
(there are no tab panels to control): the `nav` holds a plain group (no role) of `<a href>` links,
|
|
249
|
+
one per tab, each pointing at the tab's root URL, and the active one carries `aria-current`
|
|
250
|
+
(`page` on the tab's root page, `true` on a child page of it). There is no `tablist`, `tab` role,
|
|
251
|
+
`aria-selected` or roving tab stop: Tab and Shift+Tab move between the tabs like any links, Enter
|
|
252
|
+
follows one, arrow keys do nothing, and Ctrl/Cmd-click or a middle click opens the tab's URL in a
|
|
253
|
+
new browser tab. A plain click is routed through the bar, so `bf-tabs` still restores the tab's
|
|
254
|
+
last page and pops to its root on reselect. A `disabled` tab is `<a role="link"
|
|
255
|
+
aria-disabled="true">` without an `href`, so it is skipped by Tab and inert. In Safari and iOS
|
|
256
|
+
WKWebView (the Capacitor iOS app) Tab reaches links only when the user turns on "Press Tab to
|
|
257
|
+
highlight each item" (Option+Tab otherwise); VoiceOver and Full Keyboard Access are unaffected (see
|
|
258
|
+
`bf-tabs`). Only a bar placed directly inside `<bf-tabs>` in the same template becomes links; a bar
|
|
259
|
+
inside a routed page under `bf-tabs` stays a standalone `tablist`.
|
|
260
|
+
|
|
261
|
+
In both forms `bf-tab`'s `id` is a selection key, not a host DOM id: the host suppresses its own
|
|
262
|
+
`id` attribute, so the id lands once, on the inner `<button>` or `<a>`. While `hidden`, the `nav` is
|
|
263
|
+
`aria-hidden="true"` and `inert`, so a hidden bar can be neither focused nor read. The active
|
|
264
|
+
capsule stays visible under `forced-colors` (`Highlight`/`HighlightText`). The `badge` count/text is
|
|
265
|
+
`aria-hidden`; a badged tab is named by `aria-label` = its label, a comma and the announcement —
|
|
266
|
+
`provideBfLabels()`'s `tabBadge(n)` (`"{n} new"`) for a numeric badge, the string as-is for a text
|
|
267
|
+
badge — so the name carries meaning ("Inbox, 3 new", one string, with no pause before the comma)
|
|
268
|
+
instead of the bare number, and still starts with the visible label. Without a badge the visible
|
|
269
|
+
label is the name.
|
|
270
|
+
|
|
271
|
+
### Keyboard
|
|
272
|
+
|
|
273
|
+
| Key | Where | Action |
|
|
274
|
+
| ------------------------------------------------ | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
|
275
|
+
| <kbd>Tab</kbd> | Standalone | Moves focus into the bar, to the active tab (else the first enabled one), and out again |
|
|
276
|
+
| <kbd>ArrowRight</kbd> / <kbd>ArrowLeft</kbd> | Standalone | Selects and focuses the next / previous enabled tab in DOM order, wrapping at the ends; not mirrored under `dir="rtl"` yet (a known gap) |
|
|
277
|
+
| <kbd>Home</kbd> / <kbd>End</kbd> | Standalone | Selects and focuses the first / last enabled tab |
|
|
278
|
+
| <kbd>Enter</kbd> / <kbd>Space</kbd> | Standalone | On the active tab: emits `reselected` |
|
|
279
|
+
| <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> | Inside `bf-tabs` | Moves between the tab links; a disabled tab is skipped |
|
|
280
|
+
| <kbd>Enter</kbd> | Inside `bf-tabs` | Follows the tab's link; the arrow keys do nothing |
|
|
281
|
+
|
|
282
|
+
## Inputs
|
|
283
|
+
|
|
284
|
+
| Name | Type | Default | Description |
|
|
285
|
+
| ---------------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
286
|
+
| `label` | `string` | `''` | Accessible name of the navigation; empty falls back to `provideBfLabels()`'s `tabBar` ("Main"). |
|
|
287
|
+
| `floating` | `boolean` | `true` | Floats fixed above the safe area with a shadow; false renders it inline in the layout flow. |
|
|
288
|
+
| `hidden` | `boolean` | `false` | Slides the bar down out of view (e.g. while the soft keyboard is open); `aria-hidden` and inert while hidden. Shadows the global `hidden` attribute on purpose: a static `<bf-tab-bar hidden>` sets this input (the slide) and the attribute itself is removed from the host, so it never becomes `display: none`. |
|
|
289
|
+
| `hideTransition` | `boolean` | `true` | Animates hiding and showing (200 ms `translate-y`/`opacity`); false switches instantly. |
|
|
290
|
+
| `active` | `string` | `''` | The active tab's id; two-way bindable. Two-way: `[(active)]`. |
|
|
291
|
+
|
|
292
|
+
## Outputs
|
|
293
|
+
|
|
294
|
+
| Name | Payload | Description |
|
|
295
|
+
| ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
296
|
+
| `changed` | `string` | Emitted with the newly selected tab's id after a user change. |
|
|
297
|
+
| `reselected` | `string` | Emitted with the active tab's id when the user selects the tab that is already active. |
|
|
298
|
+
| `tabChange` | `string` | Emitted with the newly selected tab's id after a user change, together with `changed`. **Deprecated** (0.3.0 use changed; removed in 0.4.0) |
|
|
299
|
+
| `tabReselect` | `string` | Emitted with the active tab's id on a reselect, together with `reselected`. **Deprecated** (0.3.0 use reselected; removed in 0.4.0) |
|
|
300
|
+
|
|
301
|
+
## Slots
|
|
302
|
+
|
|
303
|
+
- `default`: The tabs, each a `bf-tab`.
|
|
304
|
+
|
|
305
|
+
## Methods
|
|
306
|
+
|
|
307
|
+
### `setHidden(hidden: boolean, transition?: boolean): void`
|
|
308
|
+
|
|
309
|
+
The imperative counterpart of the `hidden` input, for a parent that projects the bar and so
|
|
310
|
+
cannot bind it (`bf-tabs` provides `BF_TAB_BAR_CONTROLLER` instead). The bar is hidden while
|
|
311
|
+
the input, this or the controller's `hidden()` is true; `transition` overrides
|
|
312
|
+
`hideTransition` when given.
|
|
313
|
+
|
|
314
|
+
| Parameter | Type | Description |
|
|
315
|
+
| ------------- | --------- | -------------------------------------------------------- |
|
|
316
|
+
| `hidden` | `boolean` | Whether to hide the bar. |
|
|
317
|
+
| `transition?` | `boolean` | Whether the change animates (default: `hideTransition`). |
|
|
318
|
+
|
|
319
|
+
### `select(id: string): void`
|
|
320
|
+
|
|
321
|
+
Selects `id` and emits `changed`, or `reselected` when `id` is already selected; ignores
|
|
322
|
+
disabled or unknown tab ids. Inside `bf-tabs` it hands the selection to the controller
|
|
323
|
+
(`select`/`reselect`) instead of writing `active`.
|
|
324
|
+
|
|
325
|
+
| Parameter | Type | Description |
|
|
326
|
+
| --------- | -------- | ------------------------------ |
|
|
327
|
+
| `id` | `string` | The `id` of the tab to select. |
|
|
328
|
+
|
|
329
|
+
### `linkHref(id: string): string | null`
|
|
330
|
+
|
|
331
|
+
The URL of tab `id` in link mode (`BF_TAB_LINKS`); `null` for a standalone bar.
|
|
332
|
+
|
|
333
|
+
| Parameter | Type | Description |
|
|
334
|
+
| --------- | -------- | -------------------- |
|
|
335
|
+
| `id` | `string` | The `id` of the tab. |
|
|
336
|
+
|
|
337
|
+
### `linkIsRoot(id: string): boolean`
|
|
338
|
+
|
|
339
|
+
Whether the current page is tab `id`'s root page (link mode); `true` for a standalone bar.
|
|
340
|
+
|
|
341
|
+
| Parameter | Type | Description |
|
|
342
|
+
| --------- | -------- | -------------------- |
|
|
343
|
+
| `id` | `string` | The `id` of the tab. |
|
|
344
|
+
|
|
345
|
+
## Notes
|
|
346
|
+
|
|
347
|
+
### Do
|
|
348
|
+
|
|
349
|
+
- Keep it to three to five destinations; give each a short, one-word label ("Benachrichtigungen" wraps in a quarter-width tab).
|
|
350
|
+
- Bind `active` to the current route so the bar always reflects where the user is.
|
|
351
|
+
|
|
352
|
+
### Don't
|
|
353
|
+
|
|
354
|
+
- Don't put more than five tabs in the bar; move overflow destinations into a "More" screen.
|
|
355
|
+
- Don't use `bf-tab-bar` on desktop web; it's a mobile shell pattern.
|
|
356
|
+
- Don't query `role="tab"` for the bar inside `bf-tabs` in tests or scripts; its tabs are links
|
|
357
|
+
(`getByRole('link', { name: 'Projects' })`).
|
|
358
|
+
|
|
359
|
+
## Migration notes
|
|
360
|
+
|
|
361
|
+
### 0.3.0
|
|
362
|
+
|
|
363
|
+
**Deprecated:** `tabChange` → `changed` and `tabReselect` → `reselected`. The new outputs emit the
|
|
364
|
+
tab id at the same moments; the old names still emit the same id right after them until 0.4.0 (see
|
|
365
|
+
Migration 23). `bf-tabs` listens to the new names.
|
|
366
|
+
|
|
367
|
+
**Breaking:** (ARIA contract) with the new `BF_TAB_LINKS` token provided by a router-aware parent
|
|
368
|
+
(`bf-tabs`) in the same template, the bar renders a `nav` holding a plain group of links instead
|
|
369
|
+
of a `tablist`: no roving tab stop and no arrow-key, Home or End handling. The token is looked up
|
|
370
|
+
with `host: true`, so a bar inside a routed page under `bf-tabs` stays a standalone `tablist`.
|
|
371
|
+
`BfTabBarHost` gains `linkMode`, `linkHref(id)` and `linkIsRoot(id)`. See Migration 11 and 14.
|
|
372
|
+
|
|
373
|
+
**Behaviour:** `label` defaults to `''` and falls back to `provideBfLabels()`'s `tabBar` ("Main");
|
|
374
|
+
the rendered English default is unchanged.
|
|
375
|
+
|
|
376
|
+
**Behaviour:** shaped with the new `rounded-control` (`--radius-control`, `9999px` by default, so it renders as before); override the token to move off the pill.
|
|
377
|
+
|
|
378
|
+
JSDoc of `tab-bar.component.ts` for the API reference: `@slot` docs for `default`; `@param` docs;
|
|
379
|
+
template handlers and framework hooks marked `@internal`, so the reference leaves them out. No
|
|
380
|
+
behaviour change.
|
|
381
|
+
|
|
382
|
+
JSDoc of `tab-bar.token.ts` for the API reference: `@summary`/`@since` on the exported symbols;
|
|
383
|
+
`@param` docs. No behaviour change.
|
|
384
|
+
|
|
385
|
+
**Behaviour:** new `BF_TAB_BAR_CONTROLLER` token and `BfTabBarController` interface (additive,
|
|
386
|
+
extends `BfTabLinks`): a parent that projects the bar and drives it provides `current()`,
|
|
387
|
+
`hidden()`, `hideTransition()`, `select(id)` and `reselect(id)`. With a controller (looked up with
|
|
388
|
+
`host: true`, like `BF_TAB_LINKS`) the bar shows `current()` as its selection (`''` while it is
|
|
389
|
+
`null`), hides while `hidden()` is true with the transition `hideTransition()` gives, and hands a
|
|
390
|
+
user selection to `select(id)`/`reselect(id)`, still emitting `changed` or `reselected` (and the
|
|
391
|
+
deprecated `tabChange`/`tabReselect` right after them); it never writes `active` then. New
|
|
392
|
+
`selected` on the bar: the id shown as selected, `active` without a controller; `BfTabBarHost`
|
|
393
|
+
gains it as an optional member (`bf-tab` falls back to `active`), so a custom `BF_TAB_BAR`
|
|
394
|
+
provider keeps working. Inside `bf-tabs` `activeChange` no longer emits. Without a controller
|
|
395
|
+
nothing changes; `setHidden()` stays for other parents. See Migration 24.
|
|
396
|
+
|
|
397
|
+
**Behaviour:** large text and compact heights (F-051, F-054): the tabs' row has a minimum height
|
|
398
|
+
in px (`min-h-[64px]`, was the rem `h-16`) and grows with a label wrapped to two lines, which
|
|
399
|
+
Android's font scale (a text zoom that does not scale the bar) needs at 2.0. Being px, the bar no
|
|
400
|
+
longer grows with iOS Dynamic Type itself, as UITabBar does not: at AX5 it was 154.7 px (the rem
|
|
401
|
+
`h-16` at a 235 % root) and is now 98 px (a capsule and two capped label lines). At compact
|
|
402
|
+
heights (`(max-height: 500px)`, phone landscape) it is at least 48 px (`min-h-[48px]`). The bottom
|
|
403
|
+
offset stays.
|
|
404
|
+
|
|
405
|
+
### 0.2.0
|
|
406
|
+
|
|
407
|
+
New inputs `hidden` (default `false`) and `hideTransition` (default `true`), both with
|
|
408
|
+
`booleanAttribute` (`hidden` intentionally shadows the global attribute, which is removed from the
|
|
409
|
+
host so a static `<bf-tab-bar hidden>` slides out instead of becoming `display: none`): a hidden bar gets
|
|
410
|
+
`translate-y-[calc(100%+40px)] opacity-0 pointer-events-none` and `aria-hidden="true"` plus
|
|
411
|
+
`inert` on its `nav`, with `transition-[translate,opacity] duration-200` only while
|
|
412
|
+
`hideTransition` (and never under `prefers-reduced-motion`: `motion-reduce:transition-none`). The
|
|
413
|
+
floating bar sits on `z-(--bf-z-tabbar)`, `bottom-[calc(var(--spacing)*6+var(--spacing-safe-bottom))]`. New method `setHidden(hidden, transition?)`, the imperative counterpart for a
|
|
414
|
+
parent that projects the bar (`bf-tabs`); the bar is hidden while the input or it is true, and
|
|
415
|
+
`isHidden` exposes the result. New output `tabReselect`, emitted with the id when the already
|
|
416
|
+
active tab is clicked (`tabChange` still fires only on a change; arrow keys never reselect).
|
|
417
|
+
|
|
418
|
+
Changed: the floating bar sits 8 px above the bottom safe area, with a 12 px floor where there is
|
|
419
|
+
none, like other apps' floating bars: `bottom-[calc(max(var(--spacing-safe-bottom),12px)+8px)]`
|
|
420
|
+
(42 px from the screen edge on an iPhone, 32 px with Android gesture navigation, 20 px without an
|
|
421
|
+
inset), replacing `bottom-[calc(var(--spacing)*6+var(--spacing-safe-bottom))]` (58 px on an
|
|
422
|
+
iPhone). Hidden, it slides down by `translate-y-[calc(100%+64px)]` (was `100% + 40px`), so it still
|
|
423
|
+
leaves the screen with its shadow.
|
|
424
|
+
|
|
425
|
+
### 0.1.0
|
|
426
|
+
|
|
427
|
+
New `bf-tab-bar`: the Meadow Sage floating pill tab bar for mobile navigation, 64 px tall, holding a `role="tablist"` of `bf-tab` children. `active` (model) tracks the selected tab id; `label` (default `Main`) names the tablist; `floating` (default `true`) fixes the bar above the safe area with `shadow-floating`, or renders it inline with `shadow-raised` when false. ArrowLeft/ArrowRight/Home/End navigation moves both selection and focus together across the enabled tabs, wrapping at the ends and skipping disabled ones; selecting a tab emits `tabChange`. The bar and `bf-tab` communicate through the `BF_TAB_BAR` injection token so neither file imports the other. Added a `tabStop` computed, exposed through `BF_TAB_BAR`: the roving tab stop now falls back to the first enabled tab when `active` matches no enabled tab (empty, unresolved, or the active tab is disabled), instead of leaving every tab at `tabIndex -1` and removing the bar from the tab order.
|