@bitforgehq/ui 0.3.0 → 0.3.2

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 (158) hide show
  1. package/CHANGELOG.md +598 -0
  2. package/README.md +18 -11
  3. package/ai/MANIFEST.index.json +306 -0
  4. package/ai/MANIFEST.json +8915 -3788
  5. package/ai/REFERENCE.index.json +476 -0
  6. package/ai/REFERENCE.json +5810 -2905
  7. package/ai/UI_GUIDE.md +212 -58
  8. package/ai/components/app-frame.md +328 -0
  9. package/ai/components/back-button.md +42 -2
  10. package/ai/components/badge.md +17 -7
  11. package/ai/components/bar-chart.md +188 -0
  12. package/ai/components/breadcrumb-item.md +121 -0
  13. package/ai/components/breadcrumb.md +277 -0
  14. package/ai/components/button.md +82 -15
  15. package/ai/components/calendar.md +300 -0
  16. package/ai/components/card.md +119 -4
  17. package/ai/components/chart-card.md +283 -0
  18. package/ai/components/combobox.md +411 -0
  19. package/ai/components/date-input.md +396 -0
  20. package/ai/components/datetime-input.md +215 -0
  21. package/ai/components/description-item.md +111 -0
  22. package/ai/components/description-list.md +160 -0
  23. package/ai/components/donut-chart.md +169 -0
  24. package/ai/components/empty-state.md +99 -6
  25. package/ai/components/header.md +71 -2
  26. package/ai/components/icon-button.md +74 -16
  27. package/ai/components/icon.md +63 -7
  28. package/ai/components/line-chart.md +234 -0
  29. package/ai/components/list.md +70 -6
  30. package/ai/components/menu.md +5 -0
  31. package/ai/components/multi-combobox.md +257 -0
  32. package/ai/components/nav-group.md +178 -0
  33. package/ai/components/nav-item.md +210 -0
  34. package/ai/components/page-header.md +237 -0
  35. package/ai/components/pagination.md +173 -0
  36. package/ai/components/popover.md +250 -0
  37. package/ai/components/side-panel.md +391 -0
  38. package/ai/components/sidebar-toggle.md +287 -0
  39. package/ai/components/sidebar.md +541 -0
  40. package/ai/components/skeleton.md +215 -0
  41. package/ai/components/sparkline.md +188 -0
  42. package/ai/components/stat.md +297 -0
  43. package/ai/components/tab-group.md +226 -0
  44. package/ai/components/tab-panel.md +163 -0
  45. package/ai/components/table.md +697 -0
  46. package/ai/components/tabs.md +46 -2
  47. package/ai/components/theme-menu-button.md +182 -0
  48. package/ai/components/theme-switch.md +170 -0
  49. package/ai/components/time-input.md +240 -0
  50. package/ai/components/toolbar.md +354 -0
  51. package/ai/components/tooltip.md +230 -0
  52. package/ai/components/top-nav.md +159 -0
  53. package/ai/reference/bf-app-frame-breakpoint.md +27 -0
  54. package/ai/reference/bf-app-frame-content-width.md +29 -0
  55. package/ai/reference/bf-badge-appearance.md +36 -0
  56. package/ai/reference/bf-badge-variant.md +1 -1
  57. package/ai/reference/bf-bar-chart-orientation.md +27 -0
  58. package/ai/reference/bf-button-appearance.md +36 -0
  59. package/ai/reference/bf-button-variant.md +1 -1
  60. package/ai/reference/bf-calendar-disabled-date.md +23 -0
  61. package/ai/reference/bf-calendar-harness.md +51 -0
  62. package/ai/reference/bf-calendar-weekday.md +31 -0
  63. package/ai/reference/bf-chart-card-heading-level.md +26 -0
  64. package/ai/reference/bf-chart-curve.md +28 -0
  65. package/ai/reference/bf-chart-harness.md +51 -0
  66. package/ai/reference/bf-chart-legend-position.md +30 -0
  67. package/ai/reference/bf-chart-series.md +23 -0
  68. package/ai/reference/bf-chart-value-format.md +23 -0
  69. package/ai/reference/bf-color-scheme-service.md +30 -0
  70. package/ai/reference/bf-color-scheme.md +31 -0
  71. package/ai/reference/bf-combobox-filter.md +27 -0
  72. package/ai/reference/bf-combobox-harness.md +89 -0
  73. package/ai/reference/bf-combobox-option.md +33 -0
  74. package/ai/reference/bf-compare-with.md +2 -0
  75. package/ai/reference/bf-date-input-harness.md +67 -0
  76. package/ai/reference/bf-date-input-picker.md +27 -0
  77. package/ai/reference/bf-date-time-input-harness.md +76 -0
  78. package/ai/reference/bf-date-time-input-picker.md +27 -0
  79. package/ai/reference/bf-description-list-layout.md +26 -0
  80. package/ai/reference/bf-donut-slice.md +22 -0
  81. package/ai/reference/bf-empty-state-heading-level.md +1 -1
  82. package/ai/reference/bf-empty-state-heading.md +33 -0
  83. package/ai/reference/bf-labels.md +73 -20
  84. package/ai/reference/bf-menu-button-variant.md +1 -0
  85. package/ai/reference/bf-menu-placement.md +1 -0
  86. package/ai/reference/bf-overlay-options.md +28 -27
  87. package/ai/reference/bf-overlay-placement.md +34 -0
  88. package/ai/reference/bf-overlay-position.md +1 -1
  89. package/ai/reference/bf-overlay-service.md +5 -3
  90. package/ai/reference/bf-page-header-heading-level.md +26 -0
  91. package/ai/reference/bf-pagination-harness.md +61 -0
  92. package/ai/reference/bf-platform-service.md +17 -15
  93. package/ai/reference/bf-popover-harness.md +42 -0
  94. package/ai/reference/bf-popover-placement.md +28 -0
  95. package/ai/reference/bf-popover-trigger-directive.md +56 -0
  96. package/ai/reference/bf-side-panel-config.md +35 -0
  97. package/ai/reference/bf-side-panel-harness.md +34 -0
  98. package/ai/reference/bf-side-panel-ref.md +44 -0
  99. package/ai/reference/bf-side-panel-service.md +43 -0
  100. package/ai/reference/bf-side-panel-template-context.md +32 -0
  101. package/ai/reference/bf-side-panel-width.md +28 -0
  102. package/ai/reference/bf-sidebar-state-service.md +21 -0
  103. package/ai/reference/bf-sidebar-toggle-harness.md +32 -0
  104. package/ai/reference/bf-size.md +13 -0
  105. package/ai/reference/bf-skeleton-shape.md +28 -0
  106. package/ai/reference/bf-stat-appearance.md +33 -0
  107. package/ai/reference/bf-stat-trend-intent.md +27 -0
  108. package/ai/reference/bf-stat-trend.md +27 -0
  109. package/ai/reference/bf-stat-variant.md +26 -0
  110. package/ai/reference/bf-tab-group-harness.md +44 -0
  111. package/ai/reference/bf-table-align.md +26 -0
  112. package/ai/reference/bf-table-cell-context.md +24 -0
  113. package/ai/reference/bf-table-cell-directive.md +22 -0
  114. package/ai/reference/bf-table-column.md +28 -0
  115. package/ai/reference/bf-table-harness.md +77 -0
  116. package/ai/reference/bf-table-row-key.md +21 -0
  117. package/ai/reference/bf-table-sort-direction.md +26 -0
  118. package/ai/reference/bf-table-sort.md +27 -0
  119. package/ai/reference/bf-table-stack.md +29 -0
  120. package/ai/reference/bf-theme.md +5 -4
  121. package/ai/reference/bf-time-input-harness.md +67 -0
  122. package/ai/reference/bf-time-input-hour-cycle.md +27 -0
  123. package/ai/reference/bf-time-input-picker.md +27 -0
  124. package/ai/reference/bf-tooltip-directive.md +43 -0
  125. package/ai/reference/bf-tooltip-harness.md +43 -0
  126. package/ai/reference/bf-tooltip-placement.md +28 -0
  127. package/ai/reference/bf_color_scheme_storage.md +20 -0
  128. package/ai/reference/bf_side_panel_data.md +17 -0
  129. package/ai/reference/bf_sidebar_state_storage.md +20 -0
  130. package/ai/reference/bf_theme_storage.md +3 -1
  131. package/ai/reference/provide-bf-color-scheme.md +20 -0
  132. package/ai/reference/provide-bf-labels.md +13 -4
  133. package/ai/reference/provide-bf-sidebar-state.md +20 -0
  134. package/ai/reference/slide-in-from-end.md +24 -0
  135. package/ai/reference/slide-out-to-end.md +24 -0
  136. package/bin/bf-ui.js +433 -3
  137. package/fesm2022/bitforgehq-ui-charts.mjs +3045 -0
  138. package/fesm2022/bitforgehq-ui-charts.mjs.map +1 -0
  139. package/fesm2022/bitforgehq-ui-icons.mjs +107 -3
  140. package/fesm2022/bitforgehq-ui-icons.mjs.map +1 -1
  141. package/fesm2022/bitforgehq-ui-mobile.mjs +14 -59
  142. package/fesm2022/bitforgehq-ui-mobile.mjs.map +1 -1
  143. package/fesm2022/bitforgehq-ui-overlay.mjs +6566 -38
  144. package/fesm2022/bitforgehq-ui-overlay.mjs.map +1 -1
  145. package/fesm2022/bitforgehq-ui-testing.mjs +634 -1
  146. package/fesm2022/bitforgehq-ui-testing.mjs.map +1 -1
  147. package/fesm2022/bitforgehq-ui.mjs +5708 -137
  148. package/fesm2022/bitforgehq-ui.mjs.map +1 -1
  149. package/llms-full.txt +26766 -14470
  150. package/llms.txt +106 -1
  151. package/package.json +7 -3
  152. package/theme.css +101 -0
  153. package/types/bitforgehq-ui-charts.d.ts +967 -0
  154. package/types/bitforgehq-ui-icons.d.ts +13 -1
  155. package/types/bitforgehq-ui-mobile.d.ts +25 -18
  156. package/types/bitforgehq-ui-overlay.d.ts +1895 -7
  157. package/types/bitforgehq-ui-testing.d.ts +782 -1
  158. package/types/bitforgehq-ui.d.ts +2393 -45
@@ -0,0 +1,541 @@
1
+ # bf-sidebar
2
+
3
+ Vertical app navigation with brand, grouped links, footer and an icon rail mode
4
+
5
+ `BfSidebarComponent` from `@bitforgehq/ui` · Navigation · since 0.3.1
6
+
7
+ ## Example
8
+
9
+ ```ts
10
+ import { Component, computed, signal } from '@angular/core';
11
+ import { RouterLink, RouterLinkActive } from '@angular/router';
12
+ import {
13
+ BfIconButtonComponent,
14
+ BfNavGroupComponent,
15
+ BfNavItemComponent,
16
+ BfSidebarComponent,
17
+ } from '@bitforgehq/ui';
18
+ import { BfIconComponent } from '@bitforgehq/ui/icons';
19
+ import {
20
+ BookOpen,
21
+ Layers,
22
+ type IconNode,
23
+ PanelLeftClose,
24
+ PanelLeftOpen,
25
+ PanelTop,
26
+ Rows3,
27
+ Settings,
28
+ Smartphone,
29
+ } from 'lucide';
30
+
31
+ interface NavLink {
32
+ readonly path: string;
33
+ readonly label: string;
34
+ readonly icon: IconNode;
35
+ readonly badge?: number;
36
+ }
37
+
38
+ /**
39
+ * The bf-sidebar overview.
40
+ *
41
+ * Live demo of `bf-sidebar`: a brand and a collapse toggle in the header, two `bf-nav-group`s of
42
+ * `a[bfNavItem]` router links (this page is the current one, one item has a count badge) and a
43
+ * footer link. The toggle binds `[(collapsed)]`; collapsed, the items show their icon only and
44
+ * name themselves in a tooltip. The result line reports the state.
45
+ */
46
+ @Component({
47
+ selector: 'app-example-sidebar-overview',
48
+ imports: [
49
+ RouterLink,
50
+ RouterLinkActive,
51
+ BfIconComponent,
52
+ BfIconButtonComponent,
53
+ BfNavGroupComponent,
54
+ BfNavItemComponent,
55
+ BfSidebarComponent,
56
+ ],
57
+ template: `
58
+ <div class="border-border bg-bg rounded-card flex h-96 overflow-hidden border">
59
+ <bf-sidebar label="Docs" [(collapsed)]="collapsed">
60
+ <div bfSlot="header" class="flex items-center justify-between gap-2">
61
+ @if (!collapsed()) {
62
+ <span class="text-heading text-text ps-3">Atlas</span>
63
+ }
64
+ <bf-icon-button
65
+ variant="text"
66
+ [label]="collapsed() ? 'Expand sidebar' : 'Collapse sidebar'"
67
+ (clicked)="collapsed.set(!collapsed())"
68
+ >
69
+ <bf-icon [icon]="collapsed() ? panelOpen : panelClose" />
70
+ </bf-icon-button>
71
+ </div>
72
+ <bf-nav-group label="Desktop">
73
+ @for (link of desktop; track link.path) {
74
+ <a
75
+ bfNavItem
76
+ [routerLink]="link.path"
77
+ routerLinkActive
78
+ ariaCurrentWhenActive="page"
79
+ [badge]="link.badge ?? null"
80
+ >
81
+ <bf-icon bfSlot="icon" [icon]="link.icon" />
82
+ {{ link.label }}
83
+ </a>
84
+ }
85
+ </bf-nav-group>
86
+ <bf-nav-group label="Shell">
87
+ @for (link of shell; track link.path) {
88
+ <a
89
+ bfNavItem
90
+ [routerLink]="link.path"
91
+ routerLinkActive
92
+ ariaCurrentWhenActive="page"
93
+ [badge]="link.badge ?? null"
94
+ >
95
+ <bf-icon bfSlot="icon" [icon]="link.icon" />
96
+ {{ link.label }}
97
+ </a>
98
+ }
99
+ </bf-nav-group>
100
+ <a
101
+ bfSlot="footer"
102
+ bfNavItem
103
+ routerLink="/theming"
104
+ routerLinkActive
105
+ ariaCurrentWhenActive="page"
106
+ >
107
+ <bf-icon bfSlot="icon" [icon]="settings" />
108
+ Theming
109
+ </a>
110
+ </bf-sidebar>
111
+ <div class="min-w-0 flex-1 p-4">
112
+ <p data-status aria-live="polite" class="text-caption text-text-muted wrap-anywhere">
113
+ {{ status() }}
114
+ </p>
115
+ </div>
116
+ </div>
117
+ `,
118
+ })
119
+ export class SidebarOverviewExampleComponent {
120
+ readonly collapsed = signal(false);
121
+ readonly status = computed(() =>
122
+ this.collapsed() ? 'Sidebar collapsed to an icon rail.' : 'Sidebar expanded.',
123
+ );
124
+
125
+ protected readonly desktop: readonly NavLink[] = [
126
+ { path: '/components/sidebar', label: 'Sidebar', icon: PanelTop },
127
+ { path: '/components/top-nav', label: 'Top nav', icon: Rows3 },
128
+ { path: '/components/nav-group', label: 'Nav group', icon: Layers },
129
+ ];
130
+ protected readonly shell: readonly NavLink[] = [
131
+ { path: '/components/tabs', label: 'Tabs', icon: Smartphone, badge: 3 },
132
+ { path: '/mobile', label: 'Mobile guide', icon: BookOpen },
133
+ ];
134
+ protected readonly settings = Settings;
135
+ protected readonly panelOpen = PanelLeftOpen;
136
+ protected readonly panelClose = PanelLeftClose;
137
+ }
138
+ ```
139
+
140
+ ## Overview
141
+
142
+ The vertical navigation of a desktop app: a `nav` landmark with a `header` slot (the brand),
143
+ the destinations (`a[bfNavItem]` links, optionally in `bf-nav-group`s) and a `footer` slot.
144
+ `collapsed` turns it into an icon rail: put a `bf-sidebar-toggle` (`@bitforgehq/ui/overlay`) in
145
+ it, or bind `[(collapsed)]` to your own button. Below a breakpoint hide it
146
+ (`class="hidden md:flex"`) and drive a `bf-menu-button` from the same routes.
147
+
148
+ The vertical navigation of a desktop web app: a `nav` landmark down the inline-start edge with a `header` slot for the brand, the destinations as [`a[bfNavItem]`](https://bitforge-ui.web.app/components/nav-item) links (optionally in titled [`bf-nav-group`](https://bitforge-ui.web.app/components/nav-group)s) and a `footer` slot for settings or the account. `collapsed` turns it into an icon rail. Its horizontal sibling for apps with few sections is [`bf-top-nav`](https://bitforge-ui.web.app/components/top-nav); both share the same item, so the current page, the badge and the focus ring look the same in either.
149
+
150
+ ## When to use
151
+
152
+ Use `bf-sidebar` for the top-level destinations of a desktop web app with more than four or five sections, or with sections that fall into groups. Use [bf-top-nav](https://bitforge-ui.web.app/components/top-nav) instead for an app with a handful of sections, and [bf-segmented](https://bitforge-ui.web.app/components/segmented) to switch a view in place. In the Capacitor shell use [bf-tabs](https://bitforge-ui.web.app/components/tabs) with [bf-tab-bar](https://bitforge-ui.web.app/components/tab-bar) instead.
153
+
154
+ ## Basic usage
155
+
156
+ Put `bfNavItem` on your own `<a routerLink routerLinkActive ariaCurrentWhenActive="page">`: the router sets `aria-current="page"` on the current page's link and the item shows it in the tonal fill. The widget never imports the router. Group the links with `bf-nav-group`, put the brand in `bfSlot="header"` and secondary links in `bfSlot="footer"`. Name the landmark with `label` (it falls back to `provideBfLabels()`'s `navigation`, "Main").
157
+
158
+ ## Collapsing to an icon rail
159
+
160
+ `collapsed` is a model. Put a [`bf-sidebar-toggle`](https://bitforge-ui.web.app/components/sidebar-toggle) (`@bitforgehq/ui/overlay`) in the header or footer: it flips the model, names itself by what the next press does ("Collapse sidebar", "Expand sidebar") and takes its two icons as slots, since a widget never imports one. Bind `[(collapsed)]` to keep the state in your own signal, or bind it to your own button outside the sidebar. Collapsed, every item shows only its icon, centred in a square; its label stays in the DOM as screen-reader text, so its accessible name does not change, and becomes the link's `title`, the tooltip a pointer user sees on hover. A count badge becomes a small dot that is still announced, and the group headings are hidden visually but still name their groups. The width follows the rail (`w-auto`); expanded the sidebar is `w-60` (240 px).
161
+
162
+ ## Remember the collapsed sidebar
163
+
164
+ `BfSidebarStateService` (from `@bitforgehq/ui`) holds a `collapsed` signal: bind the sidebar's `[(collapsed)]` to it (`sidebarState.collapsed`) and add `provideBfSidebarState()` to the app's providers, and every change is stored under `bf-sidebar-collapsed` (`localStorage` in the browser, `BF_SIDEBAR_STATE_STORAGE` to keep it elsewhere) and read back at the next start, so the app opens as the user left it. Without the provider the signal still holds the state but nothing is stored; on the server it starts expanded and reads nothing. The widget itself injects no service.
165
+
166
+ ```ts
167
+ import { Component, inject } from '@angular/core';
168
+ import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
169
+ import { BfNavItemComponent, BfSidebarComponent, BfSidebarStateService } from '@bitforgehq/ui';
170
+ import { BfIconComponent } from '@bitforgehq/ui/icons';
171
+ import { BfSidebarToggleComponent } from '@bitforgehq/ui/overlay';
172
+ import { Inbox, PanelLeftClose, PanelLeftOpen } from 'lucide';
173
+
174
+ /**
175
+ * A sidebar that opens collapsed or expanded, as the user left it.
176
+ *
177
+ * `[(collapsed)]` binds `BfSidebarStateService.collapsed`; with `provideBfSidebarState()` in the
178
+ * app's providers every change is stored under `bf-sidebar-collapsed` (`localStorage` in the
179
+ * browser) and read back at the next start.
180
+ *
181
+ * @preview none
182
+ */
183
+ @Component({
184
+ selector: 'app-example-sidebar-remembered',
185
+ imports: [
186
+ RouterLink,
187
+ RouterLinkActive,
188
+ RouterOutlet,
189
+ BfIconComponent,
190
+ BfNavItemComponent,
191
+ BfSidebarComponent,
192
+ BfSidebarToggleComponent,
193
+ ],
194
+ template: `
195
+ <div class="flex min-h-dvh">
196
+ <bf-sidebar [(collapsed)]="sidebarState.collapsed" class="sticky top-0 h-dvh">
197
+ <a bfNavItem routerLink="/inbox" routerLinkActive ariaCurrentWhenActive="page">
198
+ <bf-icon bfSlot="icon" [icon]="inbox" />
199
+ Inbox
200
+ </a>
201
+ <bf-sidebar-toggle bfSlot="footer">
202
+ <bf-icon bfSlot="collapse-icon" [icon]="panelClose" />
203
+ <bf-icon bfSlot="expand-icon" [icon]="panelOpen" />
204
+ </bf-sidebar-toggle>
205
+ </bf-sidebar>
206
+ <main class="min-w-0 flex-1 p-6"><router-outlet /></main>
207
+ </div>
208
+ `,
209
+ })
210
+ export class SidebarRememberedExampleComponent {
211
+ // app.config.ts: providers: [provideBfSidebarState()]
212
+ protected readonly sidebarState = inject(BfSidebarStateService);
213
+ protected readonly inbox = Inbox;
214
+ protected readonly panelOpen = PanelLeftOpen;
215
+ protected readonly panelClose = PanelLeftClose;
216
+ }
217
+ ```
218
+
219
+ ## Footer
220
+
221
+ The footer slot takes several elements, stacked with a 4 px gap: a Settings link as an `a[bfNavItem]`, a row holding an icon-only text [`bf-theme-menu-button`](https://bitforge-ui.web.app/components/theme-menu-button) and the `bf-sidebar-toggle`, and an optional version line in `text-caption text-text-muted`. Expanded, the row puts the two buttons at either end (`flex items-center justify-between`). As an icon rail every footer element is centred: switch the row to a column (`flex flex-col items-center gap-1`) so both buttons fit the rail, and hide the version line (`[class.hidden]="collapsed()"`), which has no icon to shrink to. Open the theme menu upwards (`placement="above-start"`), since the footer sits at the bottom of the screen.
222
+
223
+ ```ts
224
+ import { Component, computed, signal } from '@angular/core';
225
+ import { RouterLink, RouterLinkActive } from '@angular/router';
226
+ import { type BfColorScheme, BfNavItemComponent, BfSidebarComponent } from '@bitforgehq/ui';
227
+ import { BfIconComponent } from '@bitforgehq/ui/icons';
228
+ import { BfSidebarToggleComponent, BfThemeMenuButtonComponent } from '@bitforgehq/ui/overlay';
229
+ import {
230
+ Inbox,
231
+ type IconNode,
232
+ Moon,
233
+ PanelLeftClose,
234
+ PanelLeftOpen,
235
+ Settings,
236
+ Sun,
237
+ SunMoon,
238
+ Users,
239
+ } from 'lucide';
240
+
241
+ interface NavLink {
242
+ readonly path: string;
243
+ readonly label: string;
244
+ readonly icon: IconNode;
245
+ }
246
+
247
+ /**
248
+ * A sidebar footer: settings, theme, toggle and version.
249
+ *
250
+ * The footer slot holds a Settings link (`a[bfNavItem]`), a row with an icon-only
251
+ * `bf-theme-menu-button` and the `bf-sidebar-toggle`, and a version line. Expanded, the two
252
+ * buttons sit at either end of the row; as an icon rail the row stacks vertically and the version
253
+ * line hides. The theme binds a local signal so the docs site keeps its theme; an app binds
254
+ * `BfColorSchemeService.scheme`.
255
+ */
256
+ @Component({
257
+ selector: 'app-example-sidebar-footer',
258
+ imports: [
259
+ RouterLink,
260
+ RouterLinkActive,
261
+ BfIconComponent,
262
+ BfNavItemComponent,
263
+ BfSidebarComponent,
264
+ BfSidebarToggleComponent,
265
+ BfThemeMenuButtonComponent,
266
+ ],
267
+ template: `
268
+ <div class="border-border bg-bg rounded-card flex h-[26rem] overflow-hidden border">
269
+ <bf-sidebar label="Footer demo" [(collapsed)]="collapsed">
270
+ @for (link of links; track link.path) {
271
+ <a bfNavItem [routerLink]="link.path" routerLinkActive ariaCurrentWhenActive="page">
272
+ <bf-icon bfSlot="icon" [icon]="link.icon" />
273
+ {{ link.label }}
274
+ </a>
275
+ }
276
+ <a
277
+ bfSlot="footer"
278
+ bfNavItem
279
+ routerLink="/theming"
280
+ routerLinkActive
281
+ ariaCurrentWhenActive="page"
282
+ >
283
+ <bf-icon bfSlot="icon" [icon]="settings" />
284
+ Settings
285
+ </a>
286
+ <div bfSlot="footer" [class]="rowClasses()">
287
+ <bf-theme-menu-button iconOnly variant="text" placement="above-start" [(value)]="scheme">
288
+ <bf-icon bfSlot="icon" [icon]="themeIcon()" />
289
+ </bf-theme-menu-button>
290
+ <bf-sidebar-toggle>
291
+ <bf-icon bfSlot="collapse-icon" [icon]="panelClose" />
292
+ <bf-icon bfSlot="expand-icon" [icon]="panelOpen" />
293
+ </bf-sidebar-toggle>
294
+ </div>
295
+ <p bfSlot="footer" class="text-caption text-text-muted ps-3" [class.hidden]="collapsed()">
296
+ Version 0.3.2
297
+ </p>
298
+ </bf-sidebar>
299
+ <div class="min-w-0 flex-1 p-4">
300
+ <p data-status aria-live="polite" class="text-caption text-text-muted wrap-anywhere">
301
+ {{ collapsed() ? 'Icon rail' : 'Expanded' }}, theme {{ scheme() }}.
302
+ </p>
303
+ </div>
304
+ </div>
305
+ `,
306
+ })
307
+ export class SidebarFooterExampleComponent {
308
+ readonly collapsed = signal(false);
309
+ /** Stands in for `BfColorSchemeService.scheme`. */
310
+ readonly scheme = signal<BfColorScheme>('system');
311
+
312
+ /** Expanded: one row, the buttons at either end. Collapsed: a column in the rail. */
313
+ protected readonly rowClasses = computed(() =>
314
+ this.collapsed() ? 'flex flex-col items-center gap-1' : 'flex items-center justify-between',
315
+ );
316
+ protected readonly themeIcon = computed(
317
+ () => ({ system: SunMoon, light: Sun, dark: Moon })[this.scheme()],
318
+ );
319
+ protected readonly links: readonly NavLink[] = [
320
+ { path: '/components/nav-item', label: 'Inbox', icon: Inbox },
321
+ { path: '/components/nav-group', label: 'Team', icon: Users },
322
+ ];
323
+ protected readonly settings = Settings;
324
+ protected readonly panelOpen = PanelLeftOpen;
325
+ protected readonly panelClose = PanelLeftClose;
326
+ }
327
+ ```
328
+
329
+ ## Responsive layouts
330
+
331
+ Below a breakpoint the sidebar does not turn itself into a menu: a root widget cannot render `bf-menu`. Keep one array of destinations (ideally derived from your route config) and drive every surface from it: the sidebar from `md` up (`class="hidden md:flex"` on `bf-sidebar`; its own `flex` loses to your `hidden`), and below it a [`bf-menu-button`](https://bitforge-ui.web.app/components/menu-button) of `a[bfMenuItem]` router links (`class="md:hidden"`, from `@bitforgehq/ui/overlay`). Each surface keeps the router's `routerLinkActive`. [`bf-app-frame`](https://bitforge-ui.web.app/components/app-frame) does this layout for you: it shows the sidebar in a sticky column from the breakpoint up and the menu button in a header bar below it.
332
+
333
+ ```ts
334
+ import { Component } from '@angular/core';
335
+ import { RouterLink, RouterLinkActive } from '@angular/router';
336
+ import { BfNavItemComponent, BfSidebarComponent } from '@bitforgehq/ui';
337
+ import { BfIconComponent } from '@bitforgehq/ui/icons';
338
+ import {
339
+ BfMenuButtonComponent,
340
+ BfMenuComponent,
341
+ BfMenuItemDirective,
342
+ } from '@bitforgehq/ui/overlay';
343
+ import { type IconNode, Layers, Menu, PanelTop, Rows3 } from 'lucide';
344
+
345
+ /** One top-level destination of the app. */
346
+ interface Destination {
347
+ readonly path: string;
348
+ readonly label: string;
349
+ readonly icon: IconNode;
350
+ }
351
+
352
+ /**
353
+ * One route array, two surfaces.
354
+ *
355
+ * The same destinations drive the sidebar from the `md` breakpoint up (`hidden md:flex`) and a
356
+ * `bf-menu-button` of `a[bfMenuItem]` router links below it (`md:hidden`). Each surface keeps
357
+ * the router's `routerLinkActive`; resize the window to switch between them.
358
+ */
359
+ @Component({
360
+ selector: 'app-example-sidebar-responsive',
361
+ imports: [
362
+ RouterLink,
363
+ RouterLinkActive,
364
+ BfIconComponent,
365
+ BfMenuButtonComponent,
366
+ BfMenuComponent,
367
+ BfMenuItemDirective,
368
+ BfNavItemComponent,
369
+ BfSidebarComponent,
370
+ ],
371
+ template: `
372
+ <div class="flex flex-col gap-3 md:flex-row">
373
+ <bf-sidebar label="Desktop components" class="hidden md:flex">
374
+ @for (destination of destinations; track destination.path) {
375
+ <a
376
+ bfNavItem
377
+ [routerLink]="destination.path"
378
+ routerLinkActive
379
+ ariaCurrentWhenActive="page"
380
+ >
381
+ <bf-icon bfSlot="icon" [icon]="destination.icon" />
382
+ {{ destination.label }}
383
+ </a>
384
+ }
385
+ </bf-sidebar>
386
+ <bf-menu-button class="md:hidden" [menu]="navMenu">
387
+ <bf-icon bfSlot="icon-start" [icon]="menu" />
388
+ Sections
389
+ </bf-menu-button>
390
+ <p class="text-caption text-text-muted min-w-0 flex-1">
391
+ The page content sits here, beside the sidebar or under the menu button.
392
+ </p>
393
+ </div>
394
+
395
+ <ng-template #navMenu>
396
+ <bf-menu>
397
+ @for (destination of destinations; track destination.path) {
398
+ <a
399
+ bfMenuItem
400
+ [routerLink]="destination.path"
401
+ routerLinkActive
402
+ ariaCurrentWhenActive="page"
403
+ >{{ destination.label }}</a
404
+ >
405
+ }
406
+ </bf-menu>
407
+ </ng-template>
408
+ `,
409
+ })
410
+ export class SidebarResponsiveExampleComponent {
411
+ /** In an app, derive this from the route config, so a new route appears on every surface. */
412
+ protected readonly destinations: readonly Destination[] = [
413
+ { path: '/components/sidebar', label: 'Sidebar', icon: PanelTop },
414
+ { path: '/components/top-nav', label: 'Top nav', icon: Rows3 },
415
+ { path: '/components/nav-group', label: 'Nav group', icon: Layers },
416
+ ];
417
+ protected readonly menu = Menu;
418
+ }
419
+ ```
420
+
421
+ Inside the Capacitor shell the same array drives the `bf-tab`s of a `bf-tab-bar` in `bf-tabs`; each destination's path is a tab id and a child route of the tabs.
422
+
423
+ ```ts
424
+ import { Component } from '@angular/core';
425
+ import { BfTabBarComponent, BfTabComponent } from '@bitforgehq/ui';
426
+ import { BfIconComponent } from '@bitforgehq/ui/icons';
427
+ import { BfStackOutletComponent, BfTabsComponent } from '@bitforgehq/ui/mobile';
428
+ import { Folder, type IconNode, Inbox, Settings } from 'lucide';
429
+
430
+ /** One top-level destination: `path` is the route under the tabs, and the tab's id. */
431
+ interface Destination {
432
+ readonly path: string;
433
+ readonly label: string;
434
+ readonly icon: IconNode;
435
+ }
436
+
437
+ /**
438
+ * The same destinations inside the Capacitor shell.
439
+ *
440
+ * In the shell the destinations array that drives the desktop sidebar drives a `bf-tab-bar`
441
+ * inside `bf-tabs` instead: each destination's `path` is a child route of `app` and the tab's id.
442
+ * On desktop the same array feeds `a[bfNavItem]` links (`routerLink="/app/{path}"`).
443
+ *
444
+ * @preview none
445
+ */
446
+ @Component({
447
+ selector: 'app-example-sidebar-shell',
448
+ imports: [
449
+ BfIconComponent,
450
+ BfStackOutletComponent,
451
+ BfTabBarComponent,
452
+ BfTabComponent,
453
+ BfTabsComponent,
454
+ ],
455
+ template: `
456
+ <bf-tabs tabsPath="app">
457
+ <bf-stack-outlet />
458
+ <bf-tab-bar>
459
+ @for (destination of destinations; track destination.path) {
460
+ <bf-tab [id]="destination.path" [label]="destination.label"
461
+ ><bf-icon [icon]="destination.icon"
462
+ /></bf-tab>
463
+ }
464
+ </bf-tab-bar>
465
+ </bf-tabs>
466
+ `,
467
+ })
468
+ export class SidebarShellExampleComponent {
469
+ protected readonly destinations: readonly Destination[] = [
470
+ { path: 'inbox', label: 'Inbox', icon: Inbox },
471
+ { path: 'projects', label: 'Projects', icon: Folder },
472
+ { path: 'settings', label: 'Settings', icon: Settings },
473
+ ];
474
+ }
475
+ ```
476
+
477
+ ## Desktop
478
+
479
+ The sidebar is a desktop pattern. Its items take a `surface-muted` fill and the pointer on hover, a `surface-pressed` fill while pressed and the focus ring on keyboard focus only (`focus-visible`); the current page keeps its tonal fill with its own hover and pressed steps. The destinations scroll on their own when they outgrow the height, while the header and footer stay put; a 4 px inset keeps the focus ring inside the scroll box. Item height follows `provideBfDefaults()` (`controlSize`); under a coarse pointer (a touchscreen laptop) every item is at least 44 px tall. Collapsed, the `title` tooltip names each item on hover. A link keeps the browser's behaviour: Ctrl/Cmd-click and a middle click open it in a new tab.
480
+
481
+ ## Theming
482
+
483
+ - Surface: `surface` with a `border` line on its inline-end edge (`border-e`); 12 px padding and 24 px between the header, the groups and the footer.
484
+ - Width: `w-60` (240 px) expanded, the icon rail's own width collapsed.
485
+ - The items' and the groups' tokens are on the [nav item](https://bitforge-ui.web.app/components/nav-item#theming) and [nav group](https://bitforge-ui.web.app/components/nav-group#theming) pages.
486
+
487
+ ## Accessibility
488
+
489
+ `bf-sidebar` renders a native `<nav>` landmark named by `label`, else `provideBfLabels()`'s `navigation` ("Main"); give each landmark on a page a different name. The header, the destinations and the footer all sit inside it, in that DOM order. The current page is the link the router marks with `aria-current="page"`; the tonal fill is never the only cue under forced colours, where an outline marks it. Collapsing changes only what is visible: every item keeps its label as screen-reader text and each group its name, so the accessible names stay the same. `bf-sidebar-toggle` is a native button named by what it does ("Collapse sidebar"), with `aria-expanded` and `aria-controls` on the `nav`; your own toggle should do the same, and sit in the sidebar or right before it.
490
+
491
+ ### Keyboard
492
+
493
+ | Key | Action |
494
+ | ------------------------------------------------ | -------------------------------------------------- |
495
+ | <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> | Moves through the header, the links and the footer |
496
+ | <kbd>Enter</kbd> | Follows the focused link |
497
+
498
+ ## Inputs
499
+
500
+ | Name | Type | Default | Description |
501
+ | ----------- | --------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
502
+ | `label` | `string` | `''` | Accessible name of the navigation; empty falls back to `provideBfLabels()`'s `navigation` ("Main"). Name each landmark differently when a page has more than one. |
503
+ | `collapsed` | `boolean` | `false` | Collapses the sidebar to an icon rail: items show their icon only, their label stays for screen readers and becomes a tooltip. A `bf-sidebar-toggle` inside flips it; bind `[(collapsed)]` to keep it in your state. |
504
+
505
+ ## Slots
506
+
507
+ - `header`: The brand or app switcher above the destinations.
508
+ - `default`: The destinations: `a[bfNavItem]` links and `bf-nav-group`s.
509
+ - `footer`: Content under the destinations, such as settings or the account.
510
+
511
+ ## Notes
512
+
513
+ ### Do
514
+
515
+ - Mark the current page with `routerLinkActive` and `ariaCurrentWhenActive="page"` on every link.
516
+ - Drive the sidebar, the small-screen menu and the shell's tab bar from one array of destinations.
517
+ - Remember the collapsed state with `BfSidebarStateService` and `provideBfSidebarState()`.
518
+
519
+ ### Don't
520
+
521
+ - Don't put actions (Create, Log out) among the destinations; a sidebar is for places, so put an action in the header or footer as a button.
522
+ - Don't use `bf-sidebar` inside the Capacitor shell; use `bf-tabs` with a `bf-tab-bar` there.
523
+
524
+ ## Migration notes
525
+
526
+ ### 0.3.2
527
+
528
+ New `BfSidebarStateService` (a `collapsed` signal) and `provideBfSidebarState()` in
529
+ `@bitforgehq/ui`: bind the sidebar's `[(collapsed)]` to `sidebarState.collapsed` and the provider
530
+ stores every change under `bf-sidebar-collapsed` in `BF_SIDEBAR_STATE_STORAGE` (`localStorage` in
531
+ the browser, memory elsewhere), read back at the next start; without the provider nothing is
532
+ written, and on the server nothing is read. The sidebar's `nav` now has an id, for a toggle's
533
+ `aria-controls`. The docs gain "Remember the collapsed sidebar" and a "Footer" composition
534
+ (Settings link, theme menu, toggle, version line) that works expanded and as an icon rail.
535
+
536
+ ### 0.3.1
537
+
538
+ New `bf-sidebar`: the vertical navigation of a desktop app, a `nav` landmark with `header`,
539
+ default and `footer` slots, named by `label` (else the new `navigation` label, "Main").
540
+ `collapsed` (a model the app binds to its own toggle button) turns it into an icon rail. Below a
541
+ breakpoint, hide it with `hidden md:flex` and drive a `bf-menu-button` from the same routes.