@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.
Files changed (281) hide show
  1. package/CHANGELOG.md +3665 -0
  2. package/LICENSE +21 -0
  3. package/README.md +175 -0
  4. package/ai/MANIFEST.index.json +383 -0
  5. package/ai/MANIFEST.json +7528 -0
  6. package/ai/REFERENCE.index.json +1461 -0
  7. package/ai/REFERENCE.json +7213 -0
  8. package/ai/UI_GUIDE.md +1159 -0
  9. package/ai/components/action-bar.md +209 -0
  10. package/ai/components/action-sheet.md +526 -0
  11. package/ai/components/alert-dialog.md +509 -0
  12. package/ai/components/alert.md +376 -0
  13. package/ai/components/avatar.md +146 -0
  14. package/ai/components/back-button.md +286 -0
  15. package/ai/components/badge.md +171 -0
  16. package/ai/components/button.md +431 -0
  17. package/ai/components/card.md +303 -0
  18. package/ai/components/checkbox.md +418 -0
  19. package/ai/components/chip-group.md +205 -0
  20. package/ai/components/chip.md +300 -0
  21. package/ai/components/content.md +474 -0
  22. package/ai/components/desktop-toast.md +420 -0
  23. package/ai/components/dialog.md +962 -0
  24. package/ai/components/empty-state.md +146 -0
  25. package/ai/components/field.md +319 -0
  26. package/ai/components/header.md +809 -0
  27. package/ai/components/icon-button.md +221 -0
  28. package/ai/components/icon.md +176 -0
  29. package/ai/components/input.md +455 -0
  30. package/ai/components/list-item.md +326 -0
  31. package/ai/components/list.md +178 -0
  32. package/ai/components/menu-button.md +377 -0
  33. package/ai/components/menu.md +661 -0
  34. package/ai/components/modal.md +613 -0
  35. package/ai/components/number-input.md +423 -0
  36. package/ai/components/page.md +299 -0
  37. package/ai/components/progress.md +215 -0
  38. package/ai/components/radio-group.md +432 -0
  39. package/ai/components/refresher.md +377 -0
  40. package/ai/components/segmented.md +517 -0
  41. package/ai/components/select.md +510 -0
  42. package/ai/components/sheet.md +845 -0
  43. package/ai/components/spinner.md +171 -0
  44. package/ai/components/stack-outlet.md +661 -0
  45. package/ai/components/tab-bar.md +427 -0
  46. package/ai/components/tab.md +295 -0
  47. package/ai/components/tabs.md +408 -0
  48. package/ai/components/textarea.md +371 -0
  49. package/ai/components/toast.md +522 -0
  50. package/ai/components/toggle.md +387 -0
  51. package/ai/reference/bf-action-directive.md +21 -0
  52. package/ai/reference/bf-action-sheet-action-role.md +27 -0
  53. package/ai/reference/bf-action-sheet-action.md +28 -0
  54. package/ai/reference/bf-action-sheet-options.md +28 -0
  55. package/ai/reference/bf-action-sheet-service.md +33 -0
  56. package/ai/reference/bf-alert-dialog-button-role.md +27 -0
  57. package/ai/reference/bf-alert-dialog-button.md +27 -0
  58. package/ai/reference/bf-alert-dialog-options.md +29 -0
  59. package/ai/reference/bf-alert-dialog-service.md +34 -0
  60. package/ai/reference/bf-alert-variant.md +28 -0
  61. package/ai/reference/bf-autocapitalize.md +30 -0
  62. package/ai/reference/bf-avatar-size.md +27 -0
  63. package/ai/reference/bf-avatar-variant.md +28 -0
  64. package/ai/reference/bf-back-at-root.md +28 -0
  65. package/ai/reference/bf-back-button-service.md +40 -0
  66. package/ai/reference/bf-badge-variant.md +29 -0
  67. package/ai/reference/bf-button-size.md +29 -0
  68. package/ai/reference/bf-button-type.md +28 -0
  69. package/ai/reference/bf-button-variant.md +29 -0
  70. package/ai/reference/bf-card-padding.md +26 -0
  71. package/ai/reference/bf-card-variant.md +28 -0
  72. package/ai/reference/bf-chip-group-mode.md +27 -0
  73. package/ai/reference/bf-chip-variant.md +26 -0
  74. package/ai/reference/bf-compare-with.md +24 -0
  75. package/ai/reference/bf-component-overlay-ref.md +33 -0
  76. package/ai/reference/bf-confirm-button-role.md +26 -0
  77. package/ai/reference/bf-confirm-button.md +27 -0
  78. package/ai/reference/bf-confirm-options.md +31 -0
  79. package/ai/reference/bf-confirm-size.md +26 -0
  80. package/ai/reference/bf-content-scroll-event.md +21 -0
  81. package/ai/reference/bf-defaults-options.md +32 -0
  82. package/ai/reference/bf-defaults.md +28 -0
  83. package/ai/reference/bf-desktop-toast-config.md +28 -0
  84. package/ai/reference/bf-desktop-toast-harness.md +33 -0
  85. package/ai/reference/bf-desktop-toast-options.md +28 -0
  86. package/ai/reference/bf-desktop-toast-position.md +28 -0
  87. package/ai/reference/bf-desktop-toast-service.md +42 -0
  88. package/ai/reference/bf-dialog-config.md +44 -0
  89. package/ai/reference/bf-dialog-harness.md +52 -0
  90. package/ai/reference/bf-dialog-ref.md +44 -0
  91. package/ai/reference/bf-dialog-role.md +26 -0
  92. package/ai/reference/bf-dialog-service.md +55 -0
  93. package/ai/reference/bf-dialog-size.md +31 -0
  94. package/ai/reference/bf-dialog-template-context.md +32 -0
  95. package/ai/reference/bf-empty-state-heading-level.md +26 -0
  96. package/ai/reference/bf-enter-key-hint.md +31 -0
  97. package/ai/reference/bf-field-control-class.md +32 -0
  98. package/ai/reference/bf-field-control-state.md +28 -0
  99. package/ai/reference/bf-field-size.md +29 -0
  100. package/ai/reference/bf-haptics-impact-style.md +27 -0
  101. package/ai/reference/bf-haptics-notification-type.md +27 -0
  102. package/ai/reference/bf-haptics-service.md +41 -0
  103. package/ai/reference/bf-harness-filters.md +29 -0
  104. package/ai/reference/bf-horizontal-scroll-directive.md +129 -0
  105. package/ai/reference/bf-icon-button-variant.md +28 -0
  106. package/ai/reference/bf-icon-size.md +27 -0
  107. package/ai/reference/bf-input-autocapitalize.md +36 -0
  108. package/ai/reference/bf-input-enter-key-hint.md +38 -0
  109. package/ai/reference/bf-input-input-mode.md +38 -0
  110. package/ai/reference/bf-input-mode.md +32 -0
  111. package/ai/reference/bf-input-type.md +35 -0
  112. package/ai/reference/bf-jsdom-shim-options.md +22 -0
  113. package/ai/reference/bf-keyboard-assist-behavior.md +26 -0
  114. package/ai/reference/bf-keyboard-assist-directive.md +185 -0
  115. package/ai/reference/bf-keyboard-phase.md +30 -0
  116. package/ai/reference/bf-keyboard-service.md +50 -0
  117. package/ai/reference/bf-labels-overrides.md +26 -0
  118. package/ai/reference/bf-labels.md +44 -0
  119. package/ai/reference/bf-menu-button-harness.md +40 -0
  120. package/ai/reference/bf-menu-button-variant.md +27 -0
  121. package/ai/reference/bf-menu-divider-directive.md +13 -0
  122. package/ai/reference/bf-menu-group-directive.md +14 -0
  123. package/ai/reference/bf-menu-harness.md +55 -0
  124. package/ai/reference/bf-menu-item-checkbox-directive.md +31 -0
  125. package/ai/reference/bf-menu-item-directive.md +31 -0
  126. package/ai/reference/bf-menu-item-harness.md +52 -0
  127. package/ai/reference/bf-menu-item-radio-directive.md +29 -0
  128. package/ai/reference/bf-menu-placement.md +29 -0
  129. package/ai/reference/bf-menu-trigger-directive.md +46 -0
  130. package/ai/reference/bf-modal-options.md +30 -0
  131. package/ai/reference/bf-modal-presentation.md +26 -0
  132. package/ai/reference/bf-modal-service.md +38 -0
  133. package/ai/reference/bf-nav-controller.md +59 -0
  134. package/ai/reference/bf-nav-direction.md +23 -0
  135. package/ai/reference/bf-nav-target.md +21 -0
  136. package/ai/reference/bf-number-input-mode.md +28 -0
  137. package/ai/reference/bf-number-input-step.md +25 -0
  138. package/ai/reference/bf-overlay-auto-focus.md +29 -0
  139. package/ai/reference/bf-overlay-controller.md +22 -0
  140. package/ai/reference/bf-overlay-host.md +36 -0
  141. package/ai/reference/bf-overlay-options.md +53 -0
  142. package/ai/reference/bf-overlay-position.md +30 -0
  143. package/ai/reference/bf-overlay-ref.md +53 -0
  144. package/ai/reference/bf-overlay-role.md +27 -0
  145. package/ai/reference/bf-overlay-service.md +53 -0
  146. package/ai/reference/bf-overlay-stack-options.md +24 -0
  147. package/ai/reference/bf-overlay-stack.md +81 -0
  148. package/ai/reference/bf-overlay-template-context.md +24 -0
  149. package/ai/reference/bf-overscroll-stretch-directive.md +163 -0
  150. package/ai/reference/bf-page-context.md +24 -0
  151. package/ai/reference/bf-platform-service.md +53 -0
  152. package/ai/reference/bf-platform.md +28 -0
  153. package/ai/reference/bf-progress-size.md +26 -0
  154. package/ai/reference/bf-progress-tone.md +28 -0
  155. package/ai/reference/bf-radio-group-harness.md +49 -0
  156. package/ai/reference/bf-radio-group-orientation.md +26 -0
  157. package/ai/reference/bf-radio-option.md +30 -0
  158. package/ai/reference/bf-refresher-refresh-event.md +22 -0
  159. package/ai/reference/bf-segment-option.md +29 -0
  160. package/ai/reference/bf-segmented-harness.md +48 -0
  161. package/ai/reference/bf-segmented-size.md +29 -0
  162. package/ai/reference/bf-select-harness.md +70 -0
  163. package/ai/reference/bf-select-option.md +29 -0
  164. package/ai/reference/bf-sheet-auto-focus.md +29 -0
  165. package/ai/reference/bf-sheet-options.md +32 -0
  166. package/ai/reference/bf-sheet-ref.md +45 -0
  167. package/ai/reference/bf-sheet-service.md +34 -0
  168. package/ai/reference/bf-shell-config-mode.md +28 -0
  169. package/ai/reference/bf-shell-config.md +38 -0
  170. package/ai/reference/bf-shell-dynamic-type-limit.md +21 -0
  171. package/ai/reference/bf-shell-dynamic-type.md +25 -0
  172. package/ai/reference/bf-shell-mode.md +28 -0
  173. package/ai/reference/bf-shell-testing-options.md +26 -0
  174. package/ai/reference/bf-size.md +45 -0
  175. package/ai/reference/bf-spinner-size.md +27 -0
  176. package/ai/reference/bf-stack-change.md +22 -0
  177. package/ai/reference/bf-stack-reuse-strategy.md +32 -0
  178. package/ai/reference/bf-stack-transition-event.md +26 -0
  179. package/ai/reference/bf-stack-transition.md +26 -0
  180. package/ai/reference/bf-stacked-overlay.md +23 -0
  181. package/ai/reference/bf-system-bars-style.md +38 -0
  182. package/ai/reference/bf-tab-bar-controller.md +51 -0
  183. package/ai/reference/bf-tab-bar-host.md +52 -0
  184. package/ai/reference/bf-tab-context.md +32 -0
  185. package/ai/reference/bf-tab-links.md +37 -0
  186. package/ai/reference/bf-testing-options.md +27 -0
  187. package/ai/reference/bf-text-scale-service.md +35 -0
  188. package/ai/reference/bf-theme.md +27 -0
  189. package/ai/reference/bf-toast-action.md +25 -0
  190. package/ai/reference/bf-toast-controller.md +17 -0
  191. package/ai/reference/bf-toast-dismiss-reason.md +29 -0
  192. package/ai/reference/bf-toast-options.md +29 -0
  193. package/ai/reference/bf-toast-position.md +26 -0
  194. package/ai/reference/bf-toast-ref.md +41 -0
  195. package/ai/reference/bf-toast-service.md +39 -0
  196. package/ai/reference/bf-toggle-label-position.md +26 -0
  197. package/ai/reference/bf-transition-kind.md +28 -0
  198. package/ai/reference/bf-validation-message.md +31 -0
  199. package/ai/reference/bf-validation-messages.md +33 -0
  200. package/ai/reference/bf-view-event.md +28 -0
  201. package/ai/reference/bf-view-lifecycle.md +41 -0
  202. package/ai/reference/bf-view-state.md +28 -0
  203. package/ai/reference/bf_default_labels.md +21 -0
  204. package/ai/reference/bf_default_values.md +21 -0
  205. package/ai/reference/bf_defaults.md +21 -0
  206. package/ai/reference/bf_desktop_toast_defaults.md +21 -0
  207. package/ai/reference/bf_dialog_data.md +17 -0
  208. package/ai/reference/bf_labels.md +21 -0
  209. package/ai/reference/bf_overlay_data.md +19 -0
  210. package/ai/reference/bf_overlay_host.md +21 -0
  211. package/ai/reference/bf_sheet_default_snap_points.md +17 -0
  212. package/ai/reference/bf_shell_config.md +21 -0
  213. package/ai/reference/bf_shell_defaults.md +21 -0
  214. package/ai/reference/bf_tab_bar.md +21 -0
  215. package/ai/reference/bf_tab_bar_controller.md +21 -0
  216. package/ai/reference/bf_tab_context.md +21 -0
  217. package/ai/reference/bf_tab_links.md +21 -0
  218. package/ai/reference/bf_theme_storage.md +19 -0
  219. package/ai/reference/create-transition.md +36 -0
  220. package/ai/reference/duration_fast.md +17 -0
  221. package/ai/reference/duration_md.md +17 -0
  222. package/ai/reference/duration_overlay.md +17 -0
  223. package/ai/reference/duration_push.md +17 -0
  224. package/ai/reference/fade-down.md +26 -0
  225. package/ai/reference/fade-in.md +25 -0
  226. package/ai/reference/fade-out.md +25 -0
  227. package/ai/reference/fade-up.md +26 -0
  228. package/ai/reference/inject-shell-mode.md +23 -0
  229. package/ai/reference/install-bf-jsdom-shims.md +31 -0
  230. package/ai/reference/ios_duration.md +17 -0
  231. package/ai/reference/ios_easing.md +17 -0
  232. package/ai/reference/is-text-entry.md +25 -0
  233. package/ai/reference/max_complete_ms.md +17 -0
  234. package/ai/reference/md-transition.md +31 -0
  235. package/ai/reference/md_duration.md +17 -0
  236. package/ai/reference/md_easing.md +17 -0
  237. package/ai/reference/overlay_duration.md +21 -0
  238. package/ai/reference/overlay_easing.md +19 -0
  239. package/ai/reference/overlay_reduced_duration.md +17 -0
  240. package/ai/reference/pop-frames-at.md +25 -0
  241. package/ai/reference/prefers-reduced-motion.md +25 -0
  242. package/ai/reference/provide-bf-defaults.md +33 -0
  243. package/ai/reference/provide-bf-desktop-toast.md +28 -0
  244. package/ai/reference/provide-bf-labels.md +38 -0
  245. package/ai/reference/provide-bf-shell-testing.md +34 -0
  246. package/ai/reference/provide-bf-shell.md +41 -0
  247. package/ai/reference/provide-bf-testing.md +33 -0
  248. package/ai/reference/reduced-transition.md +28 -0
  249. package/ai/reference/reduced_duration.md +17 -0
  250. package/ai/reference/resolve-shell-mode.md +33 -0
  251. package/ai/reference/scale-in.md +25 -0
  252. package/ai/reference/settle-duration.md +25 -0
  253. package/ai/reference/settle-pop.md +33 -0
  254. package/ai/reference/settle_easing.md +17 -0
  255. package/ai/reference/should-complete-pop.md +26 -0
  256. package/ai/reference/slide-down.md +26 -0
  257. package/ai/reference/slide-up.md +26 -0
  258. package/ai/reference/with-bf-shell-routing.md +19 -0
  259. package/bin/bf-ui.js +81 -0
  260. package/fesm2022/bitforgehq-ui-icons.mjs +230 -0
  261. package/fesm2022/bitforgehq-ui-icons.mjs.map +1 -0
  262. package/fesm2022/bitforgehq-ui-mobile.mjs +5530 -0
  263. package/fesm2022/bitforgehq-ui-mobile.mjs.map +1 -0
  264. package/fesm2022/bitforgehq-ui-number-input.mjs +680 -0
  265. package/fesm2022/bitforgehq-ui-number-input.mjs.map +1 -0
  266. package/fesm2022/bitforgehq-ui-overlay.mjs +2675 -0
  267. package/fesm2022/bitforgehq-ui-overlay.mjs.map +1 -0
  268. package/fesm2022/bitforgehq-ui-testing.mjs +402 -0
  269. package/fesm2022/bitforgehq-ui-testing.mjs.map +1 -0
  270. package/fesm2022/bitforgehq-ui.mjs +7765 -0
  271. package/fesm2022/bitforgehq-ui.mjs.map +1 -0
  272. package/llms-full.txt +25394 -0
  273. package/llms.txt +329 -0
  274. package/package.json +94 -0
  275. package/theme.css +253 -0
  276. package/types/bitforgehq-ui-icons.d.ts +50 -0
  277. package/types/bitforgehq-ui-mobile.d.ts +2914 -0
  278. package/types/bitforgehq-ui-number-input.d.ts +282 -0
  279. package/types/bitforgehq-ui-overlay.d.ts +1514 -0
  280. package/types/bitforgehq-ui-testing.d.ts +495 -0
  281. 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.