@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,328 @@
1
+ # bf-app-frame
2
+
3
+ Desktop app frame with skip link, sticky sidebar, narrow header and main content
4
+
5
+ `BfAppFrameComponent` from `@bitforgehq/ui` · Navigation · since 0.3.2
6
+
7
+ ## Example
8
+
9
+ ```ts
10
+ import { Component, signal } from '@angular/core';
11
+ import { RouterLink, RouterLinkActive } from '@angular/router';
12
+ import {
13
+ BfAppFrameComponent,
14
+ BfIconButtonComponent,
15
+ BfNavItemComponent,
16
+ BfSidebarComponent,
17
+ } from '@bitforgehq/ui';
18
+ import { BfIconComponent } from '@bitforgehq/ui/icons';
19
+ import {
20
+ BfMenuButtonComponent,
21
+ BfMenuComponent,
22
+ BfMenuItemDirective,
23
+ } from '@bitforgehq/ui/overlay';
24
+ import { Bell, type IconNode, Layers, Menu, PanelLeft, PanelTop } from 'lucide';
25
+
26
+ /** One top-level destination of the app, shared by the sidebar and the narrow-screen menu. */
27
+ interface Destination {
28
+ readonly path: string;
29
+ readonly label: string;
30
+ readonly icon: IconNode;
31
+ }
32
+
33
+ /**
34
+ * The bf-app-frame overview.
35
+ *
36
+ * Live demo of `bf-app-frame` in a box of fixed height: one destinations array drives the
37
+ * `bf-sidebar` (from `md` up) and the narrow header's `bf-menu-button`; the header also holds the
38
+ * brand and a notifications action. Tab into the frame to reveal its skip link; Enter moves focus
39
+ * to the content. The result line reports it.
40
+ */
41
+ @Component({
42
+ selector: 'app-example-app-frame-overview',
43
+ imports: [
44
+ RouterLink,
45
+ RouterLinkActive,
46
+ BfAppFrameComponent,
47
+ BfIconButtonComponent,
48
+ BfIconComponent,
49
+ BfMenuButtonComponent,
50
+ BfMenuComponent,
51
+ BfMenuItemDirective,
52
+ BfNavItemComponent,
53
+ BfSidebarComponent,
54
+ ],
55
+ template: `
56
+ <!-- In an app the frame is the root of the page; here a fixed height stands in for the
57
+ viewport, so the sidebar and the header bar stick inside the box. -->
58
+ <div (focusin)="onFocus($event)">
59
+ <bf-app-frame
60
+ mainId="app-frame-demo-content"
61
+ class="border-border rounded-card h-96 overflow-auto border"
62
+ >
63
+ <bf-sidebar bfSlot="sidebar" label="App frame demo">
64
+ <span bfSlot="header" class="text-heading ps-3">Atlas</span>
65
+ @for (destination of destinations; track destination.path) {
66
+ <a
67
+ bfNavItem
68
+ [routerLink]="destination.path"
69
+ routerLinkActive
70
+ ariaCurrentWhenActive="page"
71
+ >
72
+ <bf-icon bfSlot="icon" [icon]="destination.icon" />
73
+ {{ destination.label }}
74
+ </a>
75
+ }
76
+ </bf-sidebar>
77
+
78
+ <span bfSlot="brand" class="text-heading">Atlas</span>
79
+ <bf-menu-button bfSlot="menu" variant="text" [menu]="navMenu">
80
+ <bf-icon bfSlot="icon-start" [icon]="menu" />
81
+ Sections
82
+ </bf-menu-button>
83
+ <bf-icon-button
84
+ bfSlot="actions"
85
+ variant="text"
86
+ label="Notifications"
87
+ (clicked)="status.set('Notifications opened.')"
88
+ >
89
+ <bf-icon [icon]="bell" />
90
+ </bf-icon-button>
91
+
92
+ <div class="flex flex-col gap-3">
93
+ <h2 class="text-title">Inbox</h2>
94
+ <p class="text-body text-text-muted max-w-prose">
95
+ The page sits in the content column, beside the sidebar from the md breakpoint up and
96
+ under the header bar below it.
97
+ </p>
98
+ <p data-status aria-live="polite" class="text-caption text-text-muted">{{ status() }}</p>
99
+ @for (row of rows; track row) {
100
+ <div class="bg-surface rounded-tile text-body p-4">{{ row }}</div>
101
+ }
102
+ </div>
103
+ </bf-app-frame>
104
+ </div>
105
+
106
+ <ng-template #navMenu>
107
+ <bf-menu>
108
+ @for (destination of destinations; track destination.path) {
109
+ <a
110
+ bfMenuItem
111
+ [routerLink]="destination.path"
112
+ routerLinkActive
113
+ ariaCurrentWhenActive="page"
114
+ >{{ destination.label }}</a
115
+ >
116
+ }
117
+ </bf-menu>
118
+ </ng-template>
119
+ `,
120
+ })
121
+ export class AppFrameOverviewExampleComponent {
122
+ /** In an app, derive this from the route config, so a new route appears on every surface. */
123
+ protected readonly destinations: readonly Destination[] = [
124
+ { path: '/components/app-frame', label: 'App frame', icon: PanelLeft },
125
+ { path: '/components/sidebar', label: 'Sidebar', icon: PanelTop },
126
+ { path: '/components/menu-button', label: 'Menu button', icon: Layers },
127
+ ];
128
+ protected readonly rows = [
129
+ 'Review the brand brief',
130
+ 'Plan the launch',
131
+ 'Write the release notes',
132
+ ];
133
+ protected readonly bell = Bell;
134
+ protected readonly menu = Menu;
135
+ readonly status = signal('Tab into the frame to reveal the skip link.');
136
+
137
+ /** Reports when focus lands on the content, as the skip link moves it there. */
138
+ protected onFocus(event: FocusEvent): void {
139
+ if (event.target instanceof HTMLElement && event.target.id === 'app-frame-demo-content') {
140
+ this.status.set('Skipped to the content.');
141
+ }
142
+ }
143
+ }
144
+ ```
145
+
146
+ ## Overview
147
+
148
+ The frame of a desktop web app: a skip link, a sticky full-height sidebar column from the
149
+ breakpoint up, a header bar with the brand, the narrow-screen menu and actions below it, and
150
+ the `main` landmark with a content column of a set maximum width.
151
+
152
+ The frame around a desktop web app's pages: a skip link, a [`bf-sidebar`](https://bitforge-ui.web.app/components/sidebar) column that sticks to the viewport at its full height from a breakpoint up, a header bar below the breakpoint with the brand, the narrow-screen [`bf-menu-button`](https://bitforge-ui.web.app/components/menu-button) and end actions, and the `main` landmark with a content column of a set maximum width. It replaces the `hidden md:flex` sidebar, the `md:hidden` menu button and the padded `main` an app would otherwise build by hand. The frame imports no overlay and never reads the router: the menu is your own `bf-menu-button`, projected into a slot.
153
+
154
+ ## When to use
155
+
156
+ Use `bf-app-frame` once, at the root of a desktop web app whose top-level destinations live in a `bf-sidebar`, with your `router-outlet` as its content. Use [bf-top-nav](https://bitforge-ui.web.app/components/top-nav) in your own header instead for an app with a handful of sections and no sidebar. In the Capacitor shell use [bf-tabs](https://bitforge-ui.web.app/components/tabs) with a [bf-tab-bar](https://bitforge-ui.web.app/components/tab-bar), and a [bf-page](https://bitforge-ui.web.app/components/page) with a [bf-header](https://bitforge-ui.web.app/components/header) per page, instead; never put the frame inside [`provideBfShell()`](https://bitforge-ui.web.app/api/provide-bf-shell).
157
+
158
+ ## Slots and layout
159
+
160
+ Put the `bf-sidebar` in `bfSlot="sidebar"`, the app's name or logo in `bfSlot="brand"`, the narrow-screen `bf-menu-button` of `a[bfMenuItem]` router links in `bfSlot="menu"` and end actions (the theme menu, the account) in `bfSlot="actions"`; everything else is the page. From the breakpoint up the sidebar column sits at the inline start, `sticky top-0` and `h-dvh` tall, and the `bf-sidebar` inside fills it and scrolls its own destinations; the header bar is hidden. Below it the sidebar column is hidden and the header bar (`surface`, a bottom border, sticky at the top) shows the brand at the start, then the menu, and the actions at the end. Keep the brand in the sidebar's own `header` slot too, for the wide layout.
161
+
162
+ ## Breakpoint and content width
163
+
164
+ `breakpoint` (`BfAppFrameBreakpoint`) is the viewport width from which the sidebar shows: `md` (768 px, the default) or `lg` (1024 px) for a wide sidebar that would crowd a tablet. `contentWidth` (`BfAppFrameContentWidth`) caps the content column, centred in the space beside the sidebar: `md` 48rem, `lg` 64rem, `xl` 80rem (the default) and `full` for no limit. The content is padded 16 px below the breakpoint and 32 px from it up.
165
+
166
+ ```ts
167
+ import { Component } from '@angular/core';
168
+ import { RouterOutlet } from '@angular/router';
169
+ import { BfAppFrameComponent, BfNavItemComponent, BfSidebarComponent } from '@bitforgehq/ui';
170
+
171
+ /**
172
+ * A narrow reading column, with the sidebar from lg up.
173
+ *
174
+ * A settings or reading app keeps its lines short: `contentWidth="md"` caps the content column at
175
+ * 48rem (`lg` 64rem, the default `xl` 80rem, `full` no limit), and `breakpoint="lg"` keeps the
176
+ * header bar up to 1024 px, for a sidebar that would crowd a tablet. `mainId` names the main
177
+ * landmark, the skip link's target.
178
+ *
179
+ * @preview none
180
+ */
181
+ @Component({
182
+ selector: 'app-example-app-frame-content-width',
183
+ imports: [RouterOutlet, BfAppFrameComponent, BfNavItemComponent, BfSidebarComponent],
184
+ template: `
185
+ <bf-app-frame contentWidth="md" breakpoint="lg" mainId="content">
186
+ <bf-sidebar bfSlot="sidebar" label="Settings">
187
+ <a bfNavItem href="/account">Account</a>
188
+ <a bfNavItem href="/billing">Billing</a>
189
+ </bf-sidebar>
190
+ <span bfSlot="brand" class="text-heading">Settings</span>
191
+ <router-outlet />
192
+ </bf-app-frame>
193
+ `,
194
+ })
195
+ export class AppFrameContentWidthExampleComponent {}
196
+ ```
197
+
198
+ ## Skip link and the main landmark
199
+
200
+ The first element in the tab order is a "Skip to main content" link (`provideBfLabels()`'s `skipToContent`), visually hidden until it has focus, then shown as a pill at the top inline-start corner. Activating it moves focus to the frame's main landmark (`tabindex="-1"`, its id from `mainId`, generated when left out) by script and cancels the link's navigation. That interception matters: under `<base href="/">`, which every Angular app has, a bare `#id` link resolves against the base, so following it would load the root URL instead of moving focus. The href stays for the link's semantics. Nested inside another `main` landmark (a docs preview, as on this page) the content drops its `main` role, so the document keeps exactly one.
201
+
202
+ ## One array of destinations
203
+
204
+ Keep one array of destinations, ideally derived from your route config, and drive every surface from it: the sidebar's `a[bfNavItem]` links, the narrow menu's `a[bfMenuItem]` links and, in the Capacitor shell, the `bf-tab`s of a `bf-tab-bar` (where each destination's path is a child route of the tabs and the tab's id). The overview at the top of this page drives the sidebar and the menu from one array; the shell keeps `bf-tabs` and has no frame:
205
+
206
+ ```ts
207
+ import { Component } from '@angular/core';
208
+ import { BfTabBarComponent, BfTabComponent } from '@bitforgehq/ui';
209
+ import { BfIconComponent } from '@bitforgehq/ui/icons';
210
+ import { BfStackOutletComponent, BfTabsComponent } from '@bitforgehq/ui/mobile';
211
+ import { Folder, type IconNode, Inbox, Settings } from 'lucide';
212
+
213
+ /** One top-level destination: `path` is the route under the tabs, and the tab's id. */
214
+ interface Destination {
215
+ readonly path: string;
216
+ readonly label: string;
217
+ readonly icon: IconNode;
218
+ }
219
+
220
+ /**
221
+ * The same destinations in the Capacitor shell.
222
+ *
223
+ * Inside `provideBfShell()` there is no `bf-app-frame`: the array that feeds the frame's sidebar
224
+ * and narrow menu drives the `bf-tab`s of a `bf-tab-bar` in `bf-tabs` instead, and each page
225
+ * brings its own `bf-page` with a `bf-header`. Each destination's `path` is a child route of
226
+ * `app` and the tab's id; on desktop the same array feeds `a[bfNavItem]` and `a[bfMenuItem]`
227
+ * links (`routerLink="/app/{path}"`).
228
+ *
229
+ * @preview none
230
+ */
231
+ @Component({
232
+ selector: 'app-example-app-frame-shell',
233
+ imports: [
234
+ BfIconComponent,
235
+ BfStackOutletComponent,
236
+ BfTabBarComponent,
237
+ BfTabComponent,
238
+ BfTabsComponent,
239
+ ],
240
+ template: `
241
+ <bf-tabs tabsPath="app">
242
+ <bf-stack-outlet />
243
+ <bf-tab-bar>
244
+ @for (destination of destinations; track destination.path) {
245
+ <bf-tab [id]="destination.path" [label]="destination.label"
246
+ ><bf-icon [icon]="destination.icon"
247
+ /></bf-tab>
248
+ }
249
+ </bf-tab-bar>
250
+ </bf-tabs>
251
+ `,
252
+ })
253
+ export class AppFrameShellExampleComponent {
254
+ protected readonly destinations: readonly Destination[] = [
255
+ { path: 'inbox', label: 'Inbox', icon: Inbox },
256
+ { path: 'projects', label: 'Projects', icon: Folder },
257
+ { path: 'settings', label: 'Settings', icon: Settings },
258
+ ];
259
+ }
260
+ ```
261
+
262
+ ## Desktop
263
+
264
+ The frame is a desktop pattern. The skip link shows its focus ring on keyboard focus only (`focus-visible`); it stays out of sight for pointer users, who never focus it. The sidebar column keeps its height while the page scrolls under the pointer, and the narrow header bar stays at the top. The breakpoint follows the viewport, not a container, so a frame inside a smaller box (as in the preview above) still switches at the viewport's width. Under a coarse pointer (a touchscreen laptop) the sidebar's items and the projected controls take their own touch heights; the frame adds none.
265
+
266
+ ## Theming
267
+
268
+ - Frame: `bg` background, `text` colour.
269
+ - Narrow header bar: `surface` with a `border` line at the bottom, at least 56 px tall.
270
+ - Skip link: `surface` background, `text`, `shadow-floating`, the `control` (pill) radius and the `ring` focus ring.
271
+ - Content: 16 px padding below the breakpoint, 32 px from it up; `max-w-3xl`, `max-w-5xl`, `max-w-7xl` or no cap by `contentWidth`.
272
+ - The sidebar's own tokens are on the [sidebar](https://bitforge-ui.web.app/components/sidebar#theming) page.
273
+
274
+ ## Accessibility
275
+
276
+ The skip link is a native `<a>` with an href to the main landmark and is the first focusable element, so a keyboard user reaches the content in one step; it moves focus by script (see above). The content is a `role="main"` landmark with `tabindex="-1"`, so it takes focus without joining the tab order, and drops the role inside another `main`. The narrow header bar is a native `header` (the banner landmark at the root of a page). Name the `bf-sidebar` by `label` and the menu button by its text; the frame names nothing else itself. Hidden regions use `display: none`, so the sidebar below the breakpoint and the header bar from it up are out of the accessibility tree too.
277
+
278
+ ### Keyboard
279
+
280
+ | Key | Action |
281
+ | ------------------------------------------------ | ------------------------------------------------------------------------- |
282
+ | <kbd>Tab</kbd> | From the top of the page, focuses the skip link first and reveals it |
283
+ | <kbd>Enter</kbd> | On the skip link, moves focus to the main content |
284
+ | <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> | Moves through the sidebar or the header bar, then the page, in that order |
285
+
286
+ ## Inputs
287
+
288
+ | Name | Type | Default | Description |
289
+ | -------------- | ----------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------- |
290
+ | `breakpoint` | `BfAppFrameBreakpoint` (`'md' \| 'lg'`) | `'md'` | The viewport width from which the sidebar column replaces the narrow header bar. |
291
+ | `contentWidth` | `BfAppFrameContentWidth` (`'md' \| 'lg' \| 'xl' \| 'full'`) | `'xl'` | The maximum width of the content column (`md` 48rem, `lg` 64rem, `xl` 80rem, `full`). |
292
+ | `mainId` | `string` | `generated` | The id of the `main` landmark, the skip link's target; generated when left out. |
293
+
294
+ ## Slots
295
+
296
+ - `sidebar`: The `bf-sidebar`, shown from the breakpoint up; it fills the column's height.
297
+ - `brand`: The app's name or logo in the narrow header bar.
298
+ - `menu`: The narrow-screen navigation: a `bf-menu-button` of `a[bfMenuItem]` links.
299
+ - `actions`: End actions of the narrow header bar, such as the theme menu.
300
+ - `default`: The page: what the router shows, inside the `main` landmark.
301
+
302
+ ## Notes
303
+
304
+ ### Do
305
+
306
+ - Put the frame at the root of the app, with the `router-outlet` as its content, so the sidebar stays put between pages.
307
+ - Drive the sidebar, the narrow menu and the shell's tab bar from one array of destinations.
308
+ - Keep one `h1` per page in the content; the frame renders no heading.
309
+
310
+ ### Don't
311
+
312
+ - Don't use `bf-app-frame` inside the Capacitor shell; use `bf-tabs` with a `bf-tab-bar` and `bf-page` there.
313
+ - Don't add your own skip link or hide the `bf-sidebar` with `hidden md:flex` inside the frame; the frame does both.
314
+
315
+ ## Migration notes
316
+
317
+ ### 0.3.2
318
+
319
+ New `bf-app-frame` in `@bitforgehq/ui`: the frame of a desktop web app. Slots `sidebar` (a
320
+ `bf-sidebar`, in a column that sticks to the viewport at full height from the breakpoint up),
321
+ `brand`, `menu` (the narrow-screen `bf-menu-button`) and `actions` (the header bar shown below
322
+ the breakpoint) and the default slot (the page, in the `main` landmark). `breakpoint`
323
+ (`BfAppFrameBreakpoint`: `'md'` default, `'lg'`), `contentWidth` (`BfAppFrameContentWidth`:
324
+ `'md'` 48rem, `'lg'` 64rem, `'xl'` 80rem default, `'full'`) and `mainId`. A skip link, first in
325
+ the tab order and hidden until focused, moves focus to the main landmark by script, so it works
326
+ under `<base href="/">`; new label key `skipToContent` ("Skip to main content"). Inside another
327
+ `main` landmark the content drops its `main` role. Desktop only: the Capacitor shell keeps
328
+ `bf-tabs`.
@@ -81,7 +81,7 @@ and its anatomy follows the shell mode (`provideBfShell({ mode })`, the platform
81
81
 
82
82
  ## When to use
83
83
 
84
- Use `bf-back-button` in the `start` slot of a pushed page's [bf-header](https://bitforge-ui.web.app/components/header) in the Capacitor shell. Use the browser's own back button and a breadcrumb instead on a desktop web page, and a [bf-button](https://bitforge-ui.web.app/components/button) link (`a[bfButton]`) for a named way back.
84
+ Use `bf-back-button` in the `start` slot of a pushed page's [bf-header](https://bitforge-ui.web.app/components/header) in the Capacitor shell. Use the browser's own back button and a [bf-breadcrumb](https://bitforge-ui.web.app/components/breadcrumb) instead on a desktop web page, and a [bf-button](https://bitforge-ui.web.app/components/button) link (`a[bfButton]`) for a named way back.
85
85
 
86
86
  ## Basic usage
87
87
 
@@ -171,7 +171,42 @@ export class BackButtonLabelExampleComponent {}
171
171
 
172
172
  ## Desktop
173
173
 
174
- `bf-back-button` is part of the Capacitor shell (`provideBfShell()`). On a desktop web page the browser's back button and history already go back; add a breadcrumb or a named link instead. Inside the shell under a mouse, the iOS form darkens to `primary-strong` on hover and the Material form is a `text` icon button; both show the pointer cursor and a focus ring on keyboard focus only.
174
+ `bf-back-button` is part of the Capacitor shell (`provideBfShell()`). On a desktop web page the browser's back button and history already go back; show where the page sits with a [bf-breadcrumb](https://bitforge-ui.web.app/components/breadcrumb) instead, or give a named way back with `a[bfButton]` (or an icon-only `a[bfIconButton]`, [bf-icon-button](https://bitforge-ui.web.app/components/icon-button)). Each level up is a `li[bfBreadcrumbItem]` holding the app's own `routerLink`; the breadcrumb draws the separators (never announced), marks the last item, the current page, with `aria-current="page"`, and collapses a long trail with `maxItems`. Put it in the `breadcrumb` slot of a [bf-page-header](https://bitforge-ui.web.app/components/page-header).
175
+
176
+ ```ts
177
+ import { Component } from '@angular/core';
178
+ import { RouterLink } from '@angular/router';
179
+ import { BfBreadcrumbComponent, BfBreadcrumbItemComponent } from '@bitforgehq/ui';
180
+
181
+ /**
182
+ * The desktop replacement for a back button: a bf-breadcrumb.
183
+ *
184
+ * Every level up is the app's own `routerLink`; the last item is the current page, which the
185
+ * breadcrumb marks with `aria-current="page"`.
186
+ */
187
+ @Component({
188
+ selector: 'app-example-back-button-breadcrumb',
189
+ imports: [RouterLink, BfBreadcrumbComponent, BfBreadcrumbItemComponent],
190
+ template: `
191
+ <bf-breadcrumb label="Back button breadcrumb">
192
+ @for (crumb of trail; track crumb.path) {
193
+ <li bfBreadcrumbItem>
194
+ <a [routerLink]="crumb.path">{{ crumb.label }}</a>
195
+ </li>
196
+ }
197
+ <li bfBreadcrumbItem>Back button</li>
198
+ </bf-breadcrumb>
199
+ `,
200
+ })
201
+ export class BackButtonBreadcrumbExampleComponent {
202
+ protected readonly trail: readonly { path: string; label: string }[] = [
203
+ { path: '/', label: 'Docs' },
204
+ { path: '/components', label: 'Components' },
205
+ ];
206
+ }
207
+ ```
208
+
209
+ Inside the shell under a mouse, the iOS form darkens to `primary-strong` on hover and the Material form is a `text` icon button; both show the pointer cursor and a focus ring on keyboard focus only.
175
210
 
176
211
  ## Theming
177
212
 
@@ -232,6 +267,11 @@ Goes back one page, or to `defaultHref` when the stack has none to go back to.
232
267
 
233
268
  ## Migration notes
234
269
 
270
+ ### 0.3.1
271
+
272
+ The `## Desktop` docs and When to use name the desktop replacement, `bf-breadcrumb`, with an
273
+ example. No code change.
274
+
235
275
  ### 0.3.0
236
276
 
237
277
  **Behaviour:** `label` defaults to `''` and falls back to `provideBfLabels()`'s `back` ("Back"),
@@ -43,13 +43,13 @@ Use `bf-badge` for a read-only status or count next to what it describes: "At ri
43
43
 
44
44
  ## Status labels
45
45
 
46
- The variant carries the meaning: `success`, `warning`, `danger`, `primary` or the default `neutral`.
46
+ The variant carries the meaning: `success`, `warning`, `danger`, `primary`, `info` or the default `neutral`. `info` is for an informational label that is not a status, such as a category ("Design", "Billing"); it is a calm blue, distinct from the brand `primary`, so a category never reads as the selected or main thing. `variant` takes a `BfBadgeAppearance` (the five `BfBadgeVariant` tones plus `info`).
47
47
 
48
48
  ```ts
49
49
  import { Component } from '@angular/core';
50
50
  import { BfBadgeComponent } from '@bitforgehq/ui';
51
51
 
52
- /** Status badges in the four tones. */
52
+ /** Status badges in the four status tones, and an informational info label. */
53
53
  @Component({
54
54
  selector: 'app-example-badge-status',
55
55
  imports: [BfBadgeComponent],
@@ -59,6 +59,7 @@ import { BfBadgeComponent } from '@bitforgehq/ui';
59
59
  <bf-badge variant="warning">At risk</bf-badge>
60
60
  <bf-badge variant="danger">Blocked</bf-badge>
61
61
  <bf-badge>Draft</bf-badge>
62
+ <bf-badge variant="info">Design</bf-badge>
62
63
  </div>
63
64
  `,
64
65
  })
@@ -125,6 +126,7 @@ Consumes tokens by variant, background then text:
125
126
  - `success`: `success-soft` / `success`
126
127
  - `warning`: `warning-soft` / `warning`
127
128
  - `danger`: `danger-soft` / `danger`
129
+ - `info`: `info-soft` / `info` (≥ 4.5:1 in both modes; it is not derived from the primary, so a custom brand colour leaves it unchanged)
128
130
  - Count pill (`count` set): `primary` / `on-primary`, ignoring `variant`
129
131
  - Shape (label and count pill): `rounded-control` (`--radius-control`, `9999px` by default).
130
132
 
@@ -134,11 +136,11 @@ A badge is a plain `<span>` with no role or ARIA attributes; its text is read as
134
136
 
135
137
  ## Inputs
136
138
 
137
- | Name | Type | Default | Description |
138
- | ------------ | --------------------------------------------------------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
139
- | `variant` | `BfBadgeVariant` (`'neutral' \| 'primary' \| 'success' \| 'warning' \| 'danger'`) | `'neutral'` | Tint for the label style. Ignored when `count` is set. |
140
- | `count` | `number \| undefined` | `undefined` | Switches to the count pill; values above 99 render as "99+". |
141
- | `countLabel` | `string` | `''` | What the count counts ("unread messages"), read by assistive tech after the number ("99+ unread messages"); visually hidden. Only used with `count`. |
139
+ | Name | Type | Default | Description |
140
+ | ------------ | ---------------------------------------------------------------------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
141
+ | `variant` | `BfBadgeAppearance` (`'neutral' \| 'primary' \| 'success' \| 'warning' \| 'danger' \| 'info'`) | `'neutral'` | Tint for the label style: neutral, primary, success, warning, danger or info. Ignored when `count` is set. |
142
+ | `count` | `number \| undefined` | `undefined` | Switches to the count pill; values above 99 render as "99+". |
143
+ | `countLabel` | `string` | `''` | What the count counts ("unread messages"), read by assistive tech after the number ("99+ unread messages"); visually hidden. Only used with `count`. |
142
144
 
143
145
  ## Slots
144
146
 
@@ -155,9 +157,17 @@ A badge is a plain `<span>` with no role or ARIA attributes; its text is read as
155
157
 
156
158
  - Don't use a badge as a button; it has no interactive states.
157
159
  - Don't invent new colors on it; pick the variant that matches the meaning.
160
+ - Don't use `primary` or `success` for a category; use `info`, so the status tones keep their meaning.
158
161
 
159
162
  ## Migration notes
160
163
 
164
+ ### 0.3.1
165
+
166
+ New `info` tone for informational labels such as a category, on the new `info` / `info-soft`
167
+ tokens (7.7:1 in light, 7.2:1 in dark; not derived from the primary). `variant` is now typed
168
+ `BfBadgeAppearance` (the five `BfBadgeVariant` tones plus `info`); `BfBadgeVariant` keeps its five
169
+ members, so code that maps over it keeps compiling.
170
+
161
171
  ### 0.3.0
162
172
 
163
173
  **Behaviour:** the label and the count pill use `rounded-control` (`--radius-control`, `9999px` by
@@ -0,0 +1,188 @@
1
+ # bf-bar-chart
2
+
3
+ Bar chart of series over categories, grouped or stacked, vertical or horizontal
4
+
5
+ `BfBarChartComponent` from `@bitforgehq/ui/charts` · Data display · since 0.3.1
6
+
7
+ ## Example
8
+
9
+ ```ts
10
+ import { Component } from '@angular/core';
11
+ import { BfBarChartComponent, type BfChartSeries } from '@bitforgehq/ui/charts';
12
+
13
+ /**
14
+ * The bf-bar-chart overview.
15
+ *
16
+ * Live demo of `bf-bar-chart`: support tickets opened and closed per weekday, side by side.
17
+ * Hover a day, or focus the plot and use the arrow keys; hide a series in the legend; open Show
18
+ * data for the table.
19
+ */
20
+ @Component({
21
+ selector: 'app-example-bar-chart-overview',
22
+ imports: [BfBarChartComponent],
23
+ template: `
24
+ <bf-bar-chart label="Support tickets this week" [series]="series" [categories]="days" />
25
+ `,
26
+ })
27
+ export class BarChartOverviewExampleComponent {
28
+ readonly days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'];
29
+ readonly series: readonly BfChartSeries[] = [
30
+ { name: 'Opened', values: [14, 9, 12, 17, 8] },
31
+ { name: 'Closed', values: [11, 12, 8, 15, 13] },
32
+ ];
33
+ }
34
+ ```
35
+
36
+ ## Overview
37
+
38
+ A bar chart of one or more series over shared categories, drawn in SVG with the chart palette:
39
+ bars side by side per category (or `stacked`), up from the baseline (or `horizontal`, along
40
+ from it), with a value axis of nice ticks, a legend that shows and hides series, a tooltip on
41
+ hover and on keyboard focus (Arrow keys move between the categories and a polite live region
42
+ reads each one) and a Show data table. Bars grow in on the first render and on every data
43
+ change, still under reduced motion; under `dir="rtl"` the horizontal axis runs from the right.
44
+
45
+ A bar chart compares amounts between categories: tickets opened and closed per weekday, revenue per region, hours per project. `bf-bar-chart` draws it in SVG from `series` (each a `name` and one value per category) and `categories`, side by side or `stacked`, as vertical columns or `horizontal` bars, with a value axis of nice ticks from zero, a legend that shows and hides series, a tooltip on hover and on keyboard focus, and a Show data table. It lives in `@bitforgehq/ui/charts` (`import { BfBarChartComponent } from '@bitforgehq/ui/charts'`) and works the same in a desktop web app and in the Capacitor shell.
46
+
47
+ ## When to use
48
+
49
+ Use `bf-bar-chart` to compare amounts between categories, or the parts of each category with `stacked`. Our charts are themed with the `--color-chart-*` tokens, follow dark mode and `dir="rtl"`, and bring their own legend, tooltip, keyboard reading and data table; bring a chart library (themed with the same tokens) only for a form they do not draw. Use [bf-line-chart](https://bitforge-ui.web.app/components/line-chart) instead for a trend over time, [bf-donut-chart](https://bitforge-ui.web.app/components/donut-chart) for the shares of one whole, and [bf-table](https://bitforge-ui.web.app/components/table) when readers look up exact values more than they compare them.
50
+
51
+ ## Stacked
52
+
53
+ `stacked` puts the series of each category into one bar, one segment per series with a 2 px gap between them, so the bar's length is the category's total. Positive values stack up from zero and negative ones down. Use it when the total matters as much as its parts; compare the parts across categories with grouped bars instead (the default), which share one baseline.
54
+
55
+ ```ts
56
+ import { Component } from '@angular/core';
57
+ import { BfBarChartComponent, type BfChartSeries } from '@bitforgehq/ui/charts';
58
+
59
+ /**
60
+ * A stacked bar chart.
61
+ *
62
+ * `stacked` puts the series of each month into one bar, so its length is the month's total:
63
+ * hours logged per month, split by kind of work.
64
+ */
65
+ @Component({
66
+ selector: 'app-example-bar-chart-stacked',
67
+ imports: [BfBarChartComponent],
68
+ template: `
69
+ <bf-bar-chart
70
+ label="Hours logged by kind of work"
71
+ stacked
72
+ legend="top"
73
+ [series]="series"
74
+ [categories]="months"
75
+ [valueFormat]="hours"
76
+ />
77
+ `,
78
+ })
79
+ export class BarChartStackedExampleComponent {
80
+ readonly months = ['Jun', 'Jul', 'Aug', 'Sep', 'Oct'];
81
+ readonly series: readonly BfChartSeries[] = [
82
+ { name: 'Design', values: [42, 38, 24, 51, 47] },
83
+ { name: 'Build', values: [88, 96, 61, 102, 110] },
84
+ { name: 'Review', values: [16, 20, 12, 24, 22] },
85
+ ];
86
+ readonly hours = (value: number): string => `${value} h`;
87
+ }
88
+ ```
89
+
90
+ ## Horizontal
91
+
92
+ `orientation="horizontal"` runs the bars along from a vertical baseline, the category names down the start side: for long names (projects, countries) or many categories. The categories run top to bottom, and <kbd>ArrowUp</kbd> and <kbd>ArrowDown</kbd> move between them too.
93
+
94
+ ```ts
95
+ import { Component } from '@angular/core';
96
+ import { BfBarChartComponent, type BfChartSeries } from '@bitforgehq/ui/charts';
97
+
98
+ /**
99
+ * A horizontal bar chart.
100
+ *
101
+ * `orientation="horizontal"` runs the bars along from the start side, with room for long
102
+ * category names: open tasks per project, sorted from most to fewest.
103
+ */
104
+ @Component({
105
+ selector: 'app-example-bar-chart-horizontal',
106
+ imports: [BfBarChartComponent],
107
+ template: `
108
+ <bf-bar-chart
109
+ label="Open tasks per project"
110
+ orientation="horizontal"
111
+ [height]="200"
112
+ [series]="series"
113
+ [categories]="projects"
114
+ />
115
+ `,
116
+ })
117
+ export class BarChartHorizontalExampleComponent {
118
+ readonly projects = ['Atlas relaunch', 'Billing', 'Mobile app', 'Docs site'];
119
+ readonly series: readonly BfChartSeries[] = [{ name: 'Open tasks', values: [23, 17, 12, 6] }];
120
+ }
121
+ ```
122
+
123
+ ## Data and formatting
124
+
125
+ The value axis always includes zero, so every bar's length is honest; values below zero draw below (or before) the baseline. A `null` value draws no bar. `valueFormat` formats the values on the axis, in the tooltip, the announcement and the table; unset, values use `Intl.NumberFormat` in the document's language. Each series keeps its colour (`--color-chart-1` for the first) when another is hidden. Bars grow from the baseline on the first render and whenever the data change. `height` sets the plot's height in px (240 by default); the width follows the container.
126
+
127
+ ## Desktop
128
+
129
+ Under a mouse, hovering a category highlights its band in `surface-muted` and shows its tooltip; leaving the plot hides them. The legend's buttons take a `surface-muted` fill on hover. The plot is one stop in the tab order, with the focus ring on keyboard focus: focusing it shows the first category. Under a coarse pointer (touch) a tap shows a category's tooltip, and the legend buttons and the Show data button are at least 44 px tall. Inside `provideBfShell()` it renders and behaves the same.
130
+
131
+ ## Theming
132
+
133
+ - Bars: `--color-chart-1` … `--color-chart-8` by series (`fill-chart-n`), 2 px corner radius, at most 40 px thick.
134
+ - Grid lines: `chart-grid`; the zero baseline: `chart-axis`; axis text: `text-micro` in `text-muted`.
135
+ - Active category band: `surface-muted`.
136
+ - Tooltip, legend and data table: as in [bf-line-chart](https://bitforge-ui.web.app/components/line-chart).
137
+ - Motion: `--animate-chart-grow-y` (columns) or `--animate-chart-grow-x` (horizontal bars); none under `prefers-reduced-motion`.
138
+
139
+ ## Accessibility
140
+
141
+ The chart is a native `figure` named by `label` (else the `bf-chart-card` title, else the `chart` label); the SVG is hidden from assistive technology. The plot is a focusable `role="group"` named by the `chartValues` label: the arrow keys move between the categories and a polite live region reads each one ("Tue: Opened 9, Closed 12"). The legend is a group of toggle buttons (`aria-pressed`); the Show data button (`aria-expanded`) shows the data as a native `table` with a caption and row headers. Every series is ≥ 3:1 against `surface` and `bg` in both modes, and the legend names each colour.
142
+
143
+ ### Keyboard
144
+
145
+ | Key | Action |
146
+ | ------------------------------------------------ | ------------------------------------------------------------------------------------- |
147
+ | <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> | Moves through the plot, the legend buttons and the Show data button |
148
+ | <kbd>ArrowRight</kbd> | Moves to the next category and reads it (the previous one under `dir="rtl"`) |
149
+ | <kbd>ArrowLeft</kbd> | Moves to the previous category and reads it (the next one under `dir="rtl"`) |
150
+ | <kbd>ArrowDown</kbd> / <kbd>ArrowUp</kbd> | With `orientation="horizontal"`, moves to the next / previous category |
151
+ | <kbd>Home</kbd> / <kbd>End</kbd> | Moves to the first / last category |
152
+ | <kbd>Escape</kbd> | Hides the tooltip |
153
+ | <kbd>Enter</kbd> / <kbd>Space</kbd> | On a legend button, shows or hides its series; on Show data, shows or hides the table |
154
+
155
+ ## Inputs
156
+
157
+ | Name | Type | Default | Description |
158
+ | ------------- | --------------------------------------------------------------------------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
159
+ | `series` | `readonly BfChartSeries[]` | required | The series to draw, each with one value per category. |
160
+ | `categories` | `readonly string[]` | required | The category names along the category axis, in order. |
161
+ | `label` | `string` | `''` | The chart's accessible name; empty falls back to the `bf-chart-card` title, then to `provideBfLabels()`'s `chart` ("Chart"). |
162
+ | `height` | `number` | `240` | The plot's height in px; the width follows the container. |
163
+ | `orientation` | `BfBarChartOrientation` (`'vertical' \| 'horizontal'`) | `'vertical'` | `vertical` (default) bars rise from a horizontal baseline; `horizontal` bars run along from a vertical one, the categories down the start side. |
164
+ | `stacked` | `boolean` | `false` | Stacks the series of a category into one bar instead of side by side. |
165
+ | `valueFormat` | `BfChartValueFormat \| undefined` | `undefined` | Formats the values on the axis, in the tooltip and in the table; unset uses `Intl.NumberFormat` in the document's language. |
166
+ | `legend` | `BfChartLegendPosition \| undefined` (`'top' \| 'bottom' \| 'none' \| undefined`) | `undefined` | Where the legend goes; unset follows the `bf-chart-card`, else `bottom`. A single series draws no legend. |
167
+
168
+ ## Notes
169
+
170
+ ### Do
171
+
172
+ - Sort the categories by value when they have no order of their own, so the bars read as a ranking.
173
+ - Use `horizontal` when the category names are long, rather than abbreviating them.
174
+
175
+ ### Don't
176
+
177
+ - Don't stack series that do not add up to a meaningful total.
178
+ - Don't draw more than eight series; fold the smallest into "Other".
179
+
180
+ ## Migration notes
181
+
182
+ ### 0.3.1
183
+
184
+ New `bf-bar-chart` in `@bitforgehq/ui/charts`: series over categories as bars from a zero
185
+ baseline, side by side or `stacked`, vertical or `orientation="horizontal"`
186
+ (`BfBarChartOrientation`), with the line chart's value axis, legend, tooltip, keyboard reading and
187
+ data table. Bars grow from the baseline on the first render and on every data change, not under
188
+ reduced motion; `dir="rtl"` mirrors the horizontal axis.