@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,297 @@
|
|
|
1
|
+
# bf-stat
|
|
2
|
+
|
|
3
|
+
One labelled figure with an optional icon tile and a supporting line
|
|
4
|
+
|
|
5
|
+
`BfStatComponent` from `@bitforgehq/ui` · Data display · since 0.3.1
|
|
6
|
+
|
|
7
|
+
## Example
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { Component } from '@angular/core';
|
|
11
|
+
import { BfStatComponent } from '@bitforgehq/ui';
|
|
12
|
+
import { BfIconComponent } from '@bitforgehq/ui/icons';
|
|
13
|
+
import { Clock, CookingPot, Timer, Users } from 'lucide';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The bf-stat overview.
|
|
17
|
+
*
|
|
18
|
+
* Live demo of `bf-stat`: a recipe's prep time, cook time, total time and servings as four card
|
|
19
|
+
* stats, each with an icon tile, wrapping from four across to two.
|
|
20
|
+
*/
|
|
21
|
+
@Component({
|
|
22
|
+
selector: 'app-example-stat-overview',
|
|
23
|
+
imports: [BfStatComponent, BfIconComponent],
|
|
24
|
+
template: `
|
|
25
|
+
<div class="grid grid-cols-[repeat(auto-fit,minmax(9rem,1fr))] gap-3">
|
|
26
|
+
<bf-stat label="Prep time" value="15 min" variant="card">
|
|
27
|
+
<bf-icon bfSlot="icon" [icon]="timer" />
|
|
28
|
+
</bf-stat>
|
|
29
|
+
<bf-stat label="Cook time" value="30 min" variant="card">
|
|
30
|
+
<bf-icon bfSlot="icon" [icon]="cookingPot" />
|
|
31
|
+
</bf-stat>
|
|
32
|
+
<bf-stat label="Total time" value="45 min" variant="card">
|
|
33
|
+
<bf-icon bfSlot="icon" [icon]="clock" />
|
|
34
|
+
</bf-stat>
|
|
35
|
+
<bf-stat label="Servings" value="4" variant="card">
|
|
36
|
+
<bf-icon bfSlot="icon" [icon]="users" />
|
|
37
|
+
</bf-stat>
|
|
38
|
+
</div>
|
|
39
|
+
`,
|
|
40
|
+
})
|
|
41
|
+
export class StatOverviewExampleComponent {
|
|
42
|
+
protected readonly timer = Timer;
|
|
43
|
+
protected readonly cookingPot = CookingPot;
|
|
44
|
+
protected readonly clock = Clock;
|
|
45
|
+
protected readonly users = Users;
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Overview
|
|
50
|
+
|
|
51
|
+
One figure with its label, such as a count, a duration or a total: a native `dl` pair with the
|
|
52
|
+
value set large, an optional icon tile at the start and a supporting line under the value.
|
|
53
|
+
|
|
54
|
+
A `delta` with a `trend` adds a pill beside the value, toned by `trendIntent`.
|
|
55
|
+
|
|
56
|
+
A stat shows one figure with its label: a recipe's cook time, a project's open tasks, a month's revenue. The value is set large, an optional icon tile sits at the start, a supporting line under the value adds context such as a period or a badge, and an optional delta pill beside the value shows the change and its direction. `bf-stat` renders its own native `dl` with the label as the `dt` and the value as the `dd`. It works the same in both shells: in the Capacitor shell and in a desktop web app.
|
|
57
|
+
|
|
58
|
+
## When to use
|
|
59
|
+
|
|
60
|
+
Use `bf-stat` for a few headline figures the reader should take in at a glance, usually in a row or grid of them. Use [bf-description-list](https://bitforge-ui.web.app/components/description-list) instead for many details of one record, and [bf-card](https://bitforge-ui.web.app/components/card) instead when the tile holds more than one figure (a title, a progress bar, actions).
|
|
61
|
+
|
|
62
|
+
## Cards
|
|
63
|
+
|
|
64
|
+
`variant="card"` gives each stat its own outlined tile, the `flat` card surface, for stats that sit on the page itself; inside a raised card use `tile` instead. In a grid the cards stretch to the row's height, so a row of stats lines up even when one has a supporting line and another has none. Put an icon in the `icon` slot (`bfSlot="icon"`, a `bf-icon`) to show what the figure is about.
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
import { Component } from '@angular/core';
|
|
68
|
+
import { BfStatComponent } from '@bitforgehq/ui';
|
|
69
|
+
import { BfIconComponent } from '@bitforgehq/ui/icons';
|
|
70
|
+
import { Clock, CookingPot, Timer, Users } from 'lucide';
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The bf-stat overview.
|
|
74
|
+
*
|
|
75
|
+
* Live demo of `bf-stat`: a recipe's prep time, cook time, total time and servings as four card
|
|
76
|
+
* stats, each with an icon tile, wrapping from four across to two.
|
|
77
|
+
*/
|
|
78
|
+
@Component({
|
|
79
|
+
selector: 'app-example-stat-overview',
|
|
80
|
+
imports: [BfStatComponent, BfIconComponent],
|
|
81
|
+
template: `
|
|
82
|
+
<div class="grid grid-cols-[repeat(auto-fit,minmax(9rem,1fr))] gap-3">
|
|
83
|
+
<bf-stat label="Prep time" value="15 min" variant="card">
|
|
84
|
+
<bf-icon bfSlot="icon" [icon]="timer" />
|
|
85
|
+
</bf-stat>
|
|
86
|
+
<bf-stat label="Cook time" value="30 min" variant="card">
|
|
87
|
+
<bf-icon bfSlot="icon" [icon]="cookingPot" />
|
|
88
|
+
</bf-stat>
|
|
89
|
+
<bf-stat label="Total time" value="45 min" variant="card">
|
|
90
|
+
<bf-icon bfSlot="icon" [icon]="clock" />
|
|
91
|
+
</bf-stat>
|
|
92
|
+
<bf-stat label="Servings" value="4" variant="card">
|
|
93
|
+
<bf-icon bfSlot="icon" [icon]="users" />
|
|
94
|
+
</bf-stat>
|
|
95
|
+
</div>
|
|
96
|
+
`,
|
|
97
|
+
})
|
|
98
|
+
export class StatOverviewExampleComponent {
|
|
99
|
+
protected readonly timer = Timer;
|
|
100
|
+
protected readonly cookingPot = CookingPot;
|
|
101
|
+
protected readonly clock = Clock;
|
|
102
|
+
protected readonly users = Users;
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Tiles
|
|
107
|
+
|
|
108
|
+
`variant="tile"` is a sunken, borderless tile (`surface-sunken`, the `card` radius and padding) for a row of stats inside a raised [bf-card](https://bitforge-ui.web.app/components/card): a raised card holds sunken tiles, never outlined ones. On a tile the icon tile turns `surface`, so it still shows. Keep one surface style per level of a dashboard: raised cards on the page, tiles in each card.
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
import { Component } from '@angular/core';
|
|
112
|
+
import { BfCardComponent, BfStatComponent } from '@bitforgehq/ui';
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* A dashboard with one surface per level.
|
|
116
|
+
*
|
|
117
|
+
* The page holds raised cards, and every card holds sunken stat tiles: each level has one surface
|
|
118
|
+
* style, so the eye reads the cards as the groups and the tiles as their figures.
|
|
119
|
+
*/
|
|
120
|
+
@Component({
|
|
121
|
+
selector: 'app-example-stat-dashboard',
|
|
122
|
+
imports: [BfCardComponent, BfStatComponent],
|
|
123
|
+
template: `
|
|
124
|
+
<div class="grid gap-4 @lg:grid-cols-2">
|
|
125
|
+
<bf-card>
|
|
126
|
+
<h3 class="text-heading">Sales</h3>
|
|
127
|
+
<div class="grid grid-cols-[repeat(auto-fit,minmax(9rem,1fr))] gap-3">
|
|
128
|
+
<bf-stat label="Revenue" value="€48,200" variant="tile" delta="+12%" trend="up">
|
|
129
|
+
vs. last month
|
|
130
|
+
</bf-stat>
|
|
131
|
+
<bf-stat label="Orders" value="1,204" variant="tile" delta="+3%" trend="up">
|
|
132
|
+
vs. last month
|
|
133
|
+
</bf-stat>
|
|
134
|
+
</div>
|
|
135
|
+
</bf-card>
|
|
136
|
+
<bf-card>
|
|
137
|
+
<h3 class="text-heading">Support</h3>
|
|
138
|
+
<div class="grid grid-cols-[repeat(auto-fit,minmax(9rem,1fr))] gap-3">
|
|
139
|
+
<bf-stat
|
|
140
|
+
label="Open tickets"
|
|
141
|
+
value="18"
|
|
142
|
+
variant="tile"
|
|
143
|
+
delta="-4"
|
|
144
|
+
trend="down"
|
|
145
|
+
trendIntent="negative"
|
|
146
|
+
>
|
|
147
|
+
vs. last week
|
|
148
|
+
</bf-stat>
|
|
149
|
+
<bf-stat label="First reply" value="2 h" variant="tile" delta="0" trend="flat">
|
|
150
|
+
vs. last week
|
|
151
|
+
</bf-stat>
|
|
152
|
+
</div>
|
|
153
|
+
</bf-card>
|
|
154
|
+
</div>
|
|
155
|
+
`,
|
|
156
|
+
})
|
|
157
|
+
export class StatDashboardExampleComponent {}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Plain
|
|
161
|
+
|
|
162
|
+
`variant="plain"` (the default) draws no surface, for stats that sit on a card or a page section of their own. The default slot is the supporting line under the value: a short trend ("+12% this week") or a `bf-badge`.
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
import { Component } from '@angular/core';
|
|
166
|
+
import { BfBadgeComponent, BfCardComponent, BfStatComponent } from '@bitforgehq/ui';
|
|
167
|
+
|
|
168
|
+
/** Plain stats on a card, with a trend and a badge as their supporting lines. */
|
|
169
|
+
@Component({
|
|
170
|
+
selector: 'app-example-stat-plain',
|
|
171
|
+
imports: [BfBadgeComponent, BfCardComponent, BfStatComponent],
|
|
172
|
+
template: `
|
|
173
|
+
<bf-card>
|
|
174
|
+
<div class="grid grid-cols-[repeat(auto-fit,minmax(9rem,1fr))] gap-4">
|
|
175
|
+
<bf-stat label="Recipes saved" value="1,204">+12% this week</bf-stat>
|
|
176
|
+
<bf-stat label="Average rating" value="4.8">From 312 reviews</bf-stat>
|
|
177
|
+
<bf-stat label="Shopping list" value="9 items">
|
|
178
|
+
<bf-badge variant="warning">3 missing</bf-badge>
|
|
179
|
+
</bf-stat>
|
|
180
|
+
</div>
|
|
181
|
+
</bf-card>
|
|
182
|
+
`,
|
|
183
|
+
})
|
|
184
|
+
export class StatPlainExampleComponent {}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Trend
|
|
188
|
+
|
|
189
|
+
`delta` (the change, already formatted, such as `+12%`) and `trend` (`up`, `down` or `flat`) add a pill beside the value; on a narrow stat it wraps under it. The pill carries an arrow mark (a chevron up or down, a dash for no change) and, for screen readers, the direction as a word before the delta ("Up +12%"), so the colour is never the only signal. An empty `delta` shows no pill; a `delta` without a `trend` shows a neutral pill with no mark.
|
|
190
|
+
|
|
191
|
+
The colour says whether the change is good, which depends on the figure. `trendIntent="positive"` (the default) is for figures where up is good, such as revenue or sign-ups: up is `success`, down is `danger`. `trendIntent="negative"` is for figures where down is good, such as costs, churn or load time: down is `success`, up is `danger`. `flat` is neutral in both. Put the comparison ("vs. last month") in the supporting line.
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
import { Component } from '@angular/core';
|
|
195
|
+
import { BfStatComponent } from '@bitforgehq/ui';
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Stats with a trend.
|
|
199
|
+
*
|
|
200
|
+
* Revenue up (good), costs down (good, so `trendIntent="negative"`) and churn unchanged, each with
|
|
201
|
+
* a delta pill beside the value.
|
|
202
|
+
*/
|
|
203
|
+
@Component({
|
|
204
|
+
selector: 'app-example-stat-trend',
|
|
205
|
+
imports: [BfStatComponent],
|
|
206
|
+
template: `
|
|
207
|
+
<div class="grid grid-cols-[repeat(auto-fit,minmax(11rem,1fr))] gap-3">
|
|
208
|
+
<bf-stat label="Revenue" value="€48,200" delta="+12%" trend="up" variant="card">
|
|
209
|
+
vs. last month
|
|
210
|
+
</bf-stat>
|
|
211
|
+
<bf-stat
|
|
212
|
+
label="Costs"
|
|
213
|
+
value="€12,900"
|
|
214
|
+
delta="-8%"
|
|
215
|
+
trend="down"
|
|
216
|
+
trendIntent="negative"
|
|
217
|
+
variant="card"
|
|
218
|
+
>
|
|
219
|
+
vs. last month
|
|
220
|
+
</bf-stat>
|
|
221
|
+
<bf-stat label="Churn" value="2.1%" delta="0%" trend="flat" variant="card">
|
|
222
|
+
vs. last month
|
|
223
|
+
</bf-stat>
|
|
224
|
+
</div>
|
|
225
|
+
`,
|
|
226
|
+
})
|
|
227
|
+
export class StatTrendExampleComponent {}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Desktop
|
|
231
|
+
|
|
232
|
+
A stat is not interactive: no hover, no pointer cursor, no focus stop. Its size does not follow `provideBfDefaults()`; the figure keeps the `title` type on every density. Inside `provideBfShell()` (the Capacitor shell) it renders and behaves exactly the same. A grid of stats wraps by itself with `grid-cols-[repeat(auto-fit,minmax(9rem,1fr))]` on the grid that holds them, so the same row reads as four across on a desktop and two on a phone.
|
|
233
|
+
|
|
234
|
+
## Theming
|
|
235
|
+
|
|
236
|
+
- Label (`dt`): `text-caption` in `text-muted`. Value (`dd`): `text-title` in `text`, with tabular figures (`tabular-nums`) so numbers line up across stats.
|
|
237
|
+
- Supporting line: `text-caption` in `text-muted`.
|
|
238
|
+
- Delta pill: the badge's label shape (`control` radius, `text-micro` bold, tabular figures); good changes `success-soft` with `success` text, bad ones `danger-soft` with `danger` text, flat or no trend `neutral-soft` with `neutral` text. The arrow mark is drawn with borders in `currentColor`, so it takes the pill's text colour.
|
|
239
|
+
- Icon tile: `surface-sunken` background (`surface` on a `tile` stat), `text-muted` icon, `tile` radius, 40 px.
|
|
240
|
+
- `card`: `surface`, the `card` radius, `--spacing-card` padding (`p-card`) and a 1.5 px `border-strong` edge, as the `flat` `bf-card`.
|
|
241
|
+
- `tile`: `surface-sunken`, the `card` radius and `--spacing-card` padding, no border.
|
|
242
|
+
|
|
243
|
+
## Accessibility
|
|
244
|
+
|
|
245
|
+
`bf-stat` renders a native `dl` holding the label as the `dt`, the value as a `dd` and the supporting line as a second `dd`, so a screen reader announces the label with its value and context. The icon tile sits outside the `dl`; keep the icon decorative (a `bf-icon` without a `label` is hidden from assistive technology). The delta pill sits in the value's `dd`, so it is read with the value; its arrow mark is `aria-hidden`, and a visually hidden word (the `statTrendUp`, `statTrendDown` and `statTrendFlat` labels, "Up", "Down", "No change"; translate them with `provideBfLabels()`) names the direction before the delta. The pill uses the badge's tone pairs, so its text keeps the badge's contrast in both modes; in forced colours the mark still shows, as it is drawn with borders. The stat adds no other ARIA.
|
|
246
|
+
|
|
247
|
+
## Inputs
|
|
248
|
+
|
|
249
|
+
| Name | Type | Default | Description |
|
|
250
|
+
| ------------- | -------------------------------------------------------------------- | ------------ | ----------------------------------------------------------------------------------------------- |
|
|
251
|
+
| `label` | `string` | required | What the figure measures, shown above it. |
|
|
252
|
+
| `value` | `string` | required | The figure, already formatted (`45 min`, `1,204`, `98%`). |
|
|
253
|
+
| `variant` | `BfStatAppearance` (`'plain' \| 'card' \| 'tile'`) | `'plain'` | Surface: plain (default) on its container, card (an outlined tile) or tile (sunken, no border). |
|
|
254
|
+
| `delta` | `string` | `''` | A change shown in a pill beside the value, already formatted (`+12%`); empty shows none. |
|
|
255
|
+
| `trend` | `BfStatTrend \| undefined` (`'up' \| 'down' \| 'flat' \| undefined`) | `undefined` | The delta's direction: an arrow mark and a hidden word; left unset, the pill is neutral. |
|
|
256
|
+
| `trendIntent` | `BfStatTrendIntent` (`'positive' \| 'negative'`) | `'positive'` | Whether up is good (positive, default) or down is good (negative, such as costs). |
|
|
257
|
+
|
|
258
|
+
## Slots
|
|
259
|
+
|
|
260
|
+
- `icon`: The icon in the tile at the start.
|
|
261
|
+
- `default`: A supporting line under the value, such as a trend or a badge.
|
|
262
|
+
|
|
263
|
+
## Notes
|
|
264
|
+
|
|
265
|
+
### Do
|
|
266
|
+
|
|
267
|
+
- Format the value before passing it (`45 min`, `1,204`, `98%`); the stat prints it as given.
|
|
268
|
+
- Keep the supporting line to a few words.
|
|
269
|
+
- Set `trendIntent="negative"` for figures where a fall is good news (costs, churn, errors).
|
|
270
|
+
|
|
271
|
+
### Don't
|
|
272
|
+
|
|
273
|
+
- Don't put `card` stats (outlined) inside a raised `bf-card`; use `tile`.
|
|
274
|
+
- Don't use a stat for a value the user edits or acts on; give it its own control.
|
|
275
|
+
- Don't put long text in `value`; it is set at the `title` size for a short figure.
|
|
276
|
+
- Don't put the comparison period in `delta`; keep it to the change (`+12%`) and say "vs. last month" in the supporting line.
|
|
277
|
+
|
|
278
|
+
## Migration notes
|
|
279
|
+
|
|
280
|
+
### 0.3.2
|
|
281
|
+
|
|
282
|
+
New `variant="tile"`: a sunken, borderless tile (`surface-sunken`, the `card` radius and
|
|
283
|
+
padding) for a row of stats inside a raised `bf-card`; on it the icon tile turns `surface`.
|
|
284
|
+
`variant` now takes the new named type `BfStatAppearance` (`BfStatVariant | 'tile'`); the
|
|
285
|
+
released `BfStatVariant` keeps its two members, so `Record`s over it keep compiling. New
|
|
286
|
+
`stat/dashboard` example with one surface per level.
|
|
287
|
+
|
|
288
|
+
### 0.3.1
|
|
289
|
+
|
|
290
|
+
New: `bf-stat`, one labelled figure in its own `dl`, with the value at the `title` size in
|
|
291
|
+
tabular figures, an `icon` slot for a tile at the start and a supporting line in the default slot.
|
|
292
|
+
`variant="card"` gives it the outlined `flat` card surface; `plain` (default) draws none. An
|
|
293
|
+
opt-in trend: `delta` (the formatted change, such as `+12%`; empty shows nothing) with `trend`
|
|
294
|
+
(`up`, `down`, `flat`) renders a pill beside the value with a CSS arrow mark and the direction as a
|
|
295
|
+
hidden word (new labels `statTrendUp`, `statTrendDown`, `statTrendFlat`). `trendIntent`
|
|
296
|
+
(`positive` default, up is good; `negative` when down is good, such as costs) picks `success` or
|
|
297
|
+
`danger`; flat is neutral. New types `BfStatVariant`, `BfStatTrend` and `BfStatTrendIntent`.
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# bf-tab-group
|
|
2
|
+
|
|
3
|
+
In-page tabs that switch between content panels, with keyboard navigation and badges
|
|
4
|
+
|
|
5
|
+
`BfTabGroupComponent` from `@bitforgehq/ui` · Navigation · since 0.3.1
|
|
6
|
+
|
|
7
|
+
## Example
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { Component, signal } from '@angular/core';
|
|
11
|
+
import { BfTabGroupComponent, BfTabPanelComponent } from '@bitforgehq/ui';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The bf-tab-group overview.
|
|
15
|
+
*
|
|
16
|
+
* Live demo of `bf-tab-group`: a project's Overview, Activity and Settings sections, Activity with
|
|
17
|
+
* a count badge; click a tab, or focus one and use the arrow keys, Home and End.
|
|
18
|
+
*/
|
|
19
|
+
@Component({
|
|
20
|
+
selector: 'app-example-tab-group-overview',
|
|
21
|
+
imports: [BfTabGroupComponent, BfTabPanelComponent],
|
|
22
|
+
template: `
|
|
23
|
+
<bf-tab-group label="Project sections" [(selected)]="tab">
|
|
24
|
+
<bf-tab-panel value="overview" label="Overview">
|
|
25
|
+
<p class="text-body">Atlas ships the new onboarding flow in March.</p>
|
|
26
|
+
</bf-tab-panel>
|
|
27
|
+
<bf-tab-panel value="activity" label="Activity" [badge]="3">
|
|
28
|
+
<ng-template>
|
|
29
|
+
<p class="text-body">Mara commented on the brief, and two tasks were closed.</p>
|
|
30
|
+
</ng-template>
|
|
31
|
+
</bf-tab-panel>
|
|
32
|
+
<bf-tab-panel value="settings" label="Settings">
|
|
33
|
+
<ng-template>
|
|
34
|
+
<p class="text-body">Only owners can change the project settings.</p>
|
|
35
|
+
</ng-template>
|
|
36
|
+
</bf-tab-panel>
|
|
37
|
+
</bf-tab-group>
|
|
38
|
+
<p data-status aria-live="polite" class="text-caption text-text-muted mt-3">
|
|
39
|
+
Selected tab: {{ tab() }}
|
|
40
|
+
</p>
|
|
41
|
+
`,
|
|
42
|
+
})
|
|
43
|
+
export class TabGroupOverviewExampleComponent {
|
|
44
|
+
readonly tab = signal('overview');
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Overview
|
|
49
|
+
|
|
50
|
+
In-page tabs: a row of tabs over the content they switch, one `bf-tab-panel` per tab. The
|
|
51
|
+
`selected` model holds the shown panel's `value`; unset or unknown, the first enabled panel
|
|
52
|
+
shows. Selection follows focus: the arrow keys (mirrored under `dir="rtl"`), Home and End move
|
|
53
|
+
to a tab and show its panel. When the tabs do not fit, the row scrolls sideways.
|
|
54
|
+
|
|
55
|
+
In-page tabs: a row of tabs over the content they switch, such as Overview / Activity / Settings on a project's detail page. `bf-tab-group` holds the row and the selection; each tab and its content is a [`bf-tab-panel`](https://bitforge-ui.web.app/components/tab-panel). It works the same in a desktop web app and in the Capacitor shell.
|
|
56
|
+
|
|
57
|
+
## When to use
|
|
58
|
+
|
|
59
|
+
Use `bf-tab-group` to split one page's content into a few sections that the user switches in place, without leaving the page. In the Capacitor shell use [bf-tabs](https://bitforge-ui.web.app/components/tabs) with [bf-tab-bar](https://bitforge-ui.web.app/components/tab-bar) instead for the app's top-level destinations at the bottom of the screen: those are routes, these are sections of one page. Use [bf-top-nav](https://bitforge-ui.web.app/components/top-nav) with `a[bfNavItem]` links instead when each tab is its own route (a URL to share or open in a new tab): links are navigation, tabs switch content in place. Use [bf-segmented](https://bitforge-ui.web.app/components/segmented) for a two-to-four-way switch of one view, such as a chart's range.
|
|
60
|
+
|
|
61
|
+
## Badges and icons
|
|
62
|
+
|
|
63
|
+
A panel's `badge` adds a count pill after its tab's label, announced as "3 new"; an icon in the panel's `bfSlot="icon"` shows in the tab before the label. A `disabled` panel's tab cannot be picked.
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
import { Component } from '@angular/core';
|
|
67
|
+
import { BfTabGroupComponent, BfTabPanelComponent } from '@bitforgehq/ui';
|
|
68
|
+
import { BfIconComponent } from '@bitforgehq/ui/icons';
|
|
69
|
+
import { Activity, FileText, LayoutDashboard, Lock } from 'lucide';
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Tabs with icons, a count badge and a disabled tab.
|
|
73
|
+
*
|
|
74
|
+
* Each panel's `bfSlot="icon"` shows in its tab before the label; `badge` adds an announced
|
|
75
|
+
* count, and `disabled` takes a tab out of the selection and the keyboard navigation.
|
|
76
|
+
*/
|
|
77
|
+
@Component({
|
|
78
|
+
selector: 'app-example-tab-group-badges-and-icons',
|
|
79
|
+
imports: [BfTabGroupComponent, BfTabPanelComponent, BfIconComponent],
|
|
80
|
+
template: `
|
|
81
|
+
<bf-tab-group label="Project with icons">
|
|
82
|
+
<bf-tab-panel value="overview" label="Overview">
|
|
83
|
+
<bf-icon bfSlot="icon" [icon]="dashboard" />
|
|
84
|
+
<p class="text-body">The project at a glance.</p>
|
|
85
|
+
</bf-tab-panel>
|
|
86
|
+
<bf-tab-panel value="activity" label="Activity" [badge]="12">
|
|
87
|
+
<bf-icon bfSlot="icon" [icon]="activity" />
|
|
88
|
+
<p class="text-body">12 updates since your last visit.</p>
|
|
89
|
+
</bf-tab-panel>
|
|
90
|
+
<bf-tab-panel value="files" label="Files">
|
|
91
|
+
<bf-icon bfSlot="icon" [icon]="files" />
|
|
92
|
+
<p class="text-body">The brief and two mock-ups.</p>
|
|
93
|
+
</bf-tab-panel>
|
|
94
|
+
<bf-tab-panel value="billing" label="Billing" [disabled]="true">
|
|
95
|
+
<bf-icon bfSlot="icon" [icon]="lock" />
|
|
96
|
+
<p class="text-body">Billing is managed by the workspace owner.</p>
|
|
97
|
+
</bf-tab-panel>
|
|
98
|
+
</bf-tab-group>
|
|
99
|
+
`,
|
|
100
|
+
})
|
|
101
|
+
export class TabGroupBadgesAndIconsExampleComponent {
|
|
102
|
+
protected readonly dashboard = LayoutDashboard;
|
|
103
|
+
protected readonly activity = Activity;
|
|
104
|
+
protected readonly files = FileText;
|
|
105
|
+
protected readonly lock = Lock;
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Lazy content
|
|
110
|
+
|
|
111
|
+
Put a panel's content in an `ng-template` inside it and the content is created only while its tab is selected, and destroyed when another tab shows: a heavy chart or a feed costs nothing until it is opened. `keepAlive` keeps it in the DOM, hidden, once it has been shown, so its scroll position and form input survive switching tabs. Plain content (not in an `ng-template`) renders at once and is only hidden.
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
import { Component, signal } from '@angular/core';
|
|
115
|
+
import { BfInputComponent, BfTabGroupComponent, BfTabPanelComponent } from '@bitforgehq/ui';
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Lazy panels, one kept alive.
|
|
119
|
+
*
|
|
120
|
+
* Each panel's content is an `ng-template`, created when its tab shows. Notes is `keepAlive`:
|
|
121
|
+
* type in it, switch tabs and come back, and the text is still there; Comments is created anew
|
|
122
|
+
* each time.
|
|
123
|
+
*/
|
|
124
|
+
@Component({
|
|
125
|
+
selector: 'app-example-tab-group-lazy',
|
|
126
|
+
imports: [BfInputComponent, BfTabGroupComponent, BfTabPanelComponent],
|
|
127
|
+
template: `
|
|
128
|
+
<bf-tab-group label="Task details" [(selected)]="tab">
|
|
129
|
+
<bf-tab-panel value="notes" label="Notes" keepAlive>
|
|
130
|
+
<ng-template>
|
|
131
|
+
<bf-input label="Note" placeholder="Type, then switch tabs" />
|
|
132
|
+
</ng-template>
|
|
133
|
+
</bf-tab-panel>
|
|
134
|
+
<bf-tab-panel value="comments" label="Comments">
|
|
135
|
+
<ng-template>
|
|
136
|
+
<bf-input label="Comment" placeholder="Cleared when you leave this tab" />
|
|
137
|
+
</ng-template>
|
|
138
|
+
</bf-tab-panel>
|
|
139
|
+
</bf-tab-group>
|
|
140
|
+
`,
|
|
141
|
+
})
|
|
142
|
+
export class TabGroupLazyExampleComponent {
|
|
143
|
+
readonly tab = signal('notes');
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Selection
|
|
148
|
+
|
|
149
|
+
`[(selected)]` holds the shown panel's `value`; `(changed)` emits the new value after a user change, never for a write of `selected`. While `selected` matches no panel (unset, or a value that went away), the first enabled panel shows; `selected` itself keeps its value until the user picks a tab.
|
|
150
|
+
|
|
151
|
+
## Sizes and width
|
|
152
|
+
|
|
153
|
+
`size` sets the tabs' height: `sm` 32 px, `md` 40 px, `lg` 48 px. Unset, it follows `provideBfDefaults()`'s `controlSize`, `md` by default. `fitted` stretches the tabs to share the group's width (two or three tabs on a phone). When the tabs do not fit, the row scrolls sideways; focusing a tab with the keyboard scrolls it into view.
|
|
154
|
+
|
|
155
|
+
## Desktop
|
|
156
|
+
|
|
157
|
+
Under a mouse an unselected tab takes a `surface-muted` fill and `text` on hover, with the pointer cursor, and `surface-pressed` while pressed; a disabled tab shows `not-allowed` and does not react. The focus ring shows on keyboard focus only, on the tab and on the panel (which is in the tab order so its content can be reached and scrolled by keyboard). Under a fine pointer the tab's visual box is its hit area; under a coarse pointer (touch) every tab is at least 44 px tall. Desktop density is the default `md` step; `provideBfDefaults({ controlSize: 'sm' })` in a component's `providers` makes a dense sub-tree ([density guide](https://bitforge-ui.web.app/density)).
|
|
158
|
+
|
|
159
|
+
## Theming
|
|
160
|
+
|
|
161
|
+
- Tabs: `text-muted` at rest, hover `surface-muted` with `text`, pressed `surface-pressed`; top corners `rounded-tile`.
|
|
162
|
+
- Selected tab: `primary-text` with a 2 px `primary` underline on its bottom edge, so the selection never rests on colour alone (`CanvasText` under forced colours; `text-disabled` on a disabled selected tab).
|
|
163
|
+
- Disabled tab: `text-disabled` (never opacity).
|
|
164
|
+
- The row: a 1 px `border` line under the tabs, across the group's width.
|
|
165
|
+
- Badge: `primary` with `on-primary` text, `rounded-control`, `text-micro`.
|
|
166
|
+
- Focus ring: `ring`, on the tabs and on the panel (`rounded-tile`).
|
|
167
|
+
- Type: `text-label-xs` / `text-label-sm` / `text-label` for `sm` / `md` / `lg`, bold.
|
|
168
|
+
|
|
169
|
+
## Accessibility
|
|
170
|
+
|
|
171
|
+
The row is a `role="tablist"` named by `label`, else `provideBfLabels()`'s `tabGroup` ("Tabs"); give each group on a page its own `label`. Each tab is a native `<button role="tab">` with `aria-selected` and `aria-controls` naming its panel; each `bf-tab-panel` is a `role="tabpanel"` with `aria-labelledby` naming its tab and `tabindex="0"`, and an unselected panel is `hidden`. Only the selected tab is in the tab order (a roving tab stop), so <kbd>Tab</kbd> moves from it into the panel. Selection follows focus (automatic activation): the arrow keys, <kbd>Home</kbd> and <kbd>End</kbd> move to a tab and show its panel at once, so keep a panel's first render quick. A disabled tab is native-disabled and skipped. A badge's pill is `aria-hidden`; its count is read after the label from visually hidden text (`tabBadge(n)`, "3 new").
|
|
172
|
+
|
|
173
|
+
### Keyboard
|
|
174
|
+
|
|
175
|
+
| Key | Action |
|
|
176
|
+
| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------- |
|
|
177
|
+
| <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> | Moves focus to the selected tab, then into the shown panel, and out again |
|
|
178
|
+
| <kbd>ArrowRight</kbd> | Selects and focuses the next enabled tab, wrapping at the end (the previous one under `dir="rtl"`) |
|
|
179
|
+
| <kbd>ArrowLeft</kbd> | Selects and focuses the previous enabled tab, wrapping at the start (the next one under `dir="rtl"`) |
|
|
180
|
+
| <kbd>Home</kbd> / <kbd>End</kbd> | Selects and focuses the first / last enabled tab |
|
|
181
|
+
| <kbd>Enter</kbd> / <kbd>Space</kbd> | Selects the focused tab (it is already selected after an arrow key) |
|
|
182
|
+
|
|
183
|
+
## Inputs
|
|
184
|
+
|
|
185
|
+
| Name | Type | Default | Description |
|
|
186
|
+
| ---------- | ----------------------------------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
187
|
+
| `label` | `string` | `''` | Accessible name of the tablist; empty falls back to `provideBfLabels()`'s `tabGroup` ("Tabs"). |
|
|
188
|
+
| `size` | `BfSize \| undefined` (`'sm' \| 'md' \| 'lg' \| undefined`) | `undefined` | Height step of the tabs: sm 32, md 40, lg 48 px (at least 44 px under a coarse pointer); unset follows `provideBfDefaults()` (`controlSize`, `md` by default). |
|
|
189
|
+
| `fitted` | `boolean` | `false` | Stretches the tabs to share the group's width. |
|
|
190
|
+
| `selected` | `string` | `''` | The shown panel's `value`; two-way bindable. Unset or unknown shows the first enabled panel (the model keeps its value until the user picks a tab). Two-way: `[(selected)]`. |
|
|
191
|
+
|
|
192
|
+
## Outputs
|
|
193
|
+
|
|
194
|
+
| Name | Payload | Description |
|
|
195
|
+
| --------- | -------- | -------------------------------------------------------------------- |
|
|
196
|
+
| `changed` | `string` | Emitted with the newly selected panel's `value` after a user change. |
|
|
197
|
+
|
|
198
|
+
## Slots
|
|
199
|
+
|
|
200
|
+
- `default`: The panels, each a `bf-tab-panel`.
|
|
201
|
+
|
|
202
|
+
## Notes
|
|
203
|
+
|
|
204
|
+
### Do
|
|
205
|
+
|
|
206
|
+
- Keep tab labels to one or two short words, and the tabs to a handful that fit the usual width.
|
|
207
|
+
- Put heavy content in an `ng-template` so it renders only when its tab is opened.
|
|
208
|
+
|
|
209
|
+
### Don't
|
|
210
|
+
|
|
211
|
+
- Don't use tabs to navigate between pages; use `bf-top-nav` links, which the router and the browser understand.
|
|
212
|
+
- Don't nest a tab group in another tab group's panel; split the page instead.
|
|
213
|
+
|
|
214
|
+
## Migration notes
|
|
215
|
+
|
|
216
|
+
### 0.3.1
|
|
217
|
+
|
|
218
|
+
New `bf-tab-group`: in-page tabs over the content they switch, in both shells. A `tablist` of
|
|
219
|
+
`button role="tab"` from its `bf-tab-panel` children, the `selected` model of the shown panel's
|
|
220
|
+
`value` with `changed` after a user change (unset or unknown shows the first enabled panel),
|
|
221
|
+
named by `label` (else the new `tabGroup` label, "Tabs"), `size` from `controlSize` (44 px at
|
|
222
|
+
least under a coarse pointer) and `fitted` to share the width. A roving tab stop and automatic
|
|
223
|
+
activation: the arrow keys (mirrored under `dir="rtl"`), Home and End select and focus a tab,
|
|
224
|
+
skipping disabled ones. The selected tab is underlined in `primary`; the row scrolls sideways
|
|
225
|
+
when the tabs do not fit. New `BfTabGroupHarness` in `@bitforgehq/ui/testing` (`getTabLabels()`,
|
|
226
|
+
`getSelectedLabel()`, `selectTab(label)`, `getPanelText()`).
|