@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.
- package/CHANGELOG.md +598 -0
- package/README.md +18 -11
- package/ai/MANIFEST.index.json +306 -0
- package/ai/MANIFEST.json +8915 -3788
- package/ai/REFERENCE.index.json +476 -0
- package/ai/REFERENCE.json +5810 -2905
- package/ai/UI_GUIDE.md +212 -58
- package/ai/components/app-frame.md +328 -0
- package/ai/components/back-button.md +42 -2
- package/ai/components/badge.md +17 -7
- package/ai/components/bar-chart.md +188 -0
- package/ai/components/breadcrumb-item.md +121 -0
- package/ai/components/breadcrumb.md +277 -0
- package/ai/components/button.md +82 -15
- package/ai/components/calendar.md +300 -0
- package/ai/components/card.md +119 -4
- package/ai/components/chart-card.md +283 -0
- package/ai/components/combobox.md +411 -0
- package/ai/components/date-input.md +396 -0
- package/ai/components/datetime-input.md +215 -0
- package/ai/components/description-item.md +111 -0
- package/ai/components/description-list.md +160 -0
- package/ai/components/donut-chart.md +169 -0
- package/ai/components/empty-state.md +99 -6
- package/ai/components/header.md +71 -2
- package/ai/components/icon-button.md +74 -16
- package/ai/components/icon.md +63 -7
- package/ai/components/line-chart.md +234 -0
- package/ai/components/list.md +70 -6
- package/ai/components/menu.md +5 -0
- package/ai/components/multi-combobox.md +257 -0
- package/ai/components/nav-group.md +178 -0
- package/ai/components/nav-item.md +210 -0
- package/ai/components/page-header.md +237 -0
- package/ai/components/pagination.md +173 -0
- package/ai/components/popover.md +250 -0
- package/ai/components/side-panel.md +391 -0
- package/ai/components/sidebar-toggle.md +287 -0
- package/ai/components/sidebar.md +541 -0
- package/ai/components/skeleton.md +215 -0
- package/ai/components/sparkline.md +188 -0
- package/ai/components/stat.md +297 -0
- package/ai/components/tab-group.md +226 -0
- package/ai/components/tab-panel.md +163 -0
- package/ai/components/table.md +697 -0
- package/ai/components/tabs.md +46 -2
- package/ai/components/theme-menu-button.md +182 -0
- package/ai/components/theme-switch.md +170 -0
- package/ai/components/time-input.md +240 -0
- package/ai/components/toolbar.md +354 -0
- package/ai/components/tooltip.md +230 -0
- package/ai/components/top-nav.md +159 -0
- package/ai/reference/bf-app-frame-breakpoint.md +27 -0
- package/ai/reference/bf-app-frame-content-width.md +29 -0
- package/ai/reference/bf-badge-appearance.md +36 -0
- package/ai/reference/bf-badge-variant.md +1 -1
- package/ai/reference/bf-bar-chart-orientation.md +27 -0
- package/ai/reference/bf-button-appearance.md +36 -0
- package/ai/reference/bf-button-variant.md +1 -1
- package/ai/reference/bf-calendar-disabled-date.md +23 -0
- package/ai/reference/bf-calendar-harness.md +51 -0
- package/ai/reference/bf-calendar-weekday.md +31 -0
- package/ai/reference/bf-chart-card-heading-level.md +26 -0
- package/ai/reference/bf-chart-curve.md +28 -0
- package/ai/reference/bf-chart-harness.md +51 -0
- package/ai/reference/bf-chart-legend-position.md +30 -0
- package/ai/reference/bf-chart-series.md +23 -0
- package/ai/reference/bf-chart-value-format.md +23 -0
- package/ai/reference/bf-color-scheme-service.md +30 -0
- package/ai/reference/bf-color-scheme.md +31 -0
- package/ai/reference/bf-combobox-filter.md +27 -0
- package/ai/reference/bf-combobox-harness.md +89 -0
- package/ai/reference/bf-combobox-option.md +33 -0
- package/ai/reference/bf-compare-with.md +2 -0
- package/ai/reference/bf-date-input-harness.md +67 -0
- package/ai/reference/bf-date-input-picker.md +27 -0
- package/ai/reference/bf-date-time-input-harness.md +76 -0
- package/ai/reference/bf-date-time-input-picker.md +27 -0
- package/ai/reference/bf-description-list-layout.md +26 -0
- package/ai/reference/bf-donut-slice.md +22 -0
- package/ai/reference/bf-empty-state-heading-level.md +1 -1
- package/ai/reference/bf-empty-state-heading.md +33 -0
- package/ai/reference/bf-labels.md +73 -20
- package/ai/reference/bf-menu-button-variant.md +1 -0
- package/ai/reference/bf-menu-placement.md +1 -0
- package/ai/reference/bf-overlay-options.md +28 -27
- package/ai/reference/bf-overlay-placement.md +34 -0
- package/ai/reference/bf-overlay-position.md +1 -1
- package/ai/reference/bf-overlay-service.md +5 -3
- package/ai/reference/bf-page-header-heading-level.md +26 -0
- package/ai/reference/bf-pagination-harness.md +61 -0
- package/ai/reference/bf-platform-service.md +17 -15
- package/ai/reference/bf-popover-harness.md +42 -0
- package/ai/reference/bf-popover-placement.md +28 -0
- package/ai/reference/bf-popover-trigger-directive.md +56 -0
- package/ai/reference/bf-side-panel-config.md +35 -0
- package/ai/reference/bf-side-panel-harness.md +34 -0
- package/ai/reference/bf-side-panel-ref.md +44 -0
- package/ai/reference/bf-side-panel-service.md +43 -0
- package/ai/reference/bf-side-panel-template-context.md +32 -0
- package/ai/reference/bf-side-panel-width.md +28 -0
- package/ai/reference/bf-sidebar-state-service.md +21 -0
- package/ai/reference/bf-sidebar-toggle-harness.md +32 -0
- package/ai/reference/bf-size.md +13 -0
- package/ai/reference/bf-skeleton-shape.md +28 -0
- package/ai/reference/bf-stat-appearance.md +33 -0
- package/ai/reference/bf-stat-trend-intent.md +27 -0
- package/ai/reference/bf-stat-trend.md +27 -0
- package/ai/reference/bf-stat-variant.md +26 -0
- package/ai/reference/bf-tab-group-harness.md +44 -0
- package/ai/reference/bf-table-align.md +26 -0
- package/ai/reference/bf-table-cell-context.md +24 -0
- package/ai/reference/bf-table-cell-directive.md +22 -0
- package/ai/reference/bf-table-column.md +28 -0
- package/ai/reference/bf-table-harness.md +77 -0
- package/ai/reference/bf-table-row-key.md +21 -0
- package/ai/reference/bf-table-sort-direction.md +26 -0
- package/ai/reference/bf-table-sort.md +27 -0
- package/ai/reference/bf-table-stack.md +29 -0
- package/ai/reference/bf-theme.md +5 -4
- package/ai/reference/bf-time-input-harness.md +67 -0
- package/ai/reference/bf-time-input-hour-cycle.md +27 -0
- package/ai/reference/bf-time-input-picker.md +27 -0
- package/ai/reference/bf-tooltip-directive.md +43 -0
- package/ai/reference/bf-tooltip-harness.md +43 -0
- package/ai/reference/bf-tooltip-placement.md +28 -0
- package/ai/reference/bf_color_scheme_storage.md +20 -0
- package/ai/reference/bf_side_panel_data.md +17 -0
- package/ai/reference/bf_sidebar_state_storage.md +20 -0
- package/ai/reference/bf_theme_storage.md +3 -1
- package/ai/reference/provide-bf-color-scheme.md +20 -0
- package/ai/reference/provide-bf-labels.md +13 -4
- package/ai/reference/provide-bf-sidebar-state.md +20 -0
- package/ai/reference/slide-in-from-end.md +24 -0
- package/ai/reference/slide-out-to-end.md +24 -0
- package/bin/bf-ui.js +433 -3
- package/fesm2022/bitforgehq-ui-charts.mjs +3045 -0
- package/fesm2022/bitforgehq-ui-charts.mjs.map +1 -0
- package/fesm2022/bitforgehq-ui-icons.mjs +107 -3
- package/fesm2022/bitforgehq-ui-icons.mjs.map +1 -1
- package/fesm2022/bitforgehq-ui-mobile.mjs +14 -59
- package/fesm2022/bitforgehq-ui-mobile.mjs.map +1 -1
- package/fesm2022/bitforgehq-ui-overlay.mjs +6566 -38
- package/fesm2022/bitforgehq-ui-overlay.mjs.map +1 -1
- package/fesm2022/bitforgehq-ui-testing.mjs +634 -1
- package/fesm2022/bitforgehq-ui-testing.mjs.map +1 -1
- package/fesm2022/bitforgehq-ui.mjs +5708 -137
- package/fesm2022/bitforgehq-ui.mjs.map +1 -1
- package/llms-full.txt +26766 -14470
- package/llms.txt +106 -1
- package/package.json +7 -3
- package/theme.css +101 -0
- package/types/bitforgehq-ui-charts.d.ts +967 -0
- package/types/bitforgehq-ui-icons.d.ts +13 -1
- package/types/bitforgehq-ui-mobile.d.ts +25 -18
- package/types/bitforgehq-ui-overlay.d.ts +1895 -7
- package/types/bitforgehq-ui-testing.d.ts +782 -1
- 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;
|
|
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"),
|
package/ai/components/badge.md
CHANGED
|
@@ -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
|
|
138
|
-
| ------------ |
|
|
139
|
-
| `variant` | `
|
|
140
|
-
| `count` | `number \| undefined`
|
|
141
|
-
| `countLabel` | `string`
|
|
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.
|