@bitforgehq/ui 0.3.0 → 0.3.1
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 +482 -0
- package/README.md +17 -11
- package/ai/MANIFEST.index.json +288 -0
- package/ai/MANIFEST.json +8716 -3926
- package/ai/REFERENCE.index.json +413 -0
- package/ai/REFERENCE.json +4710 -2047
- package/ai/UI_GUIDE.md +185 -57
- 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 +63 -2
- 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/header.md +67 -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.md +363 -0
- package/ai/components/skeleton.md +215 -0
- package/ai/components/sparkline.md +188 -0
- package/ai/components/stat.md +233 -0
- package/ai/components/tab-group.md +226 -0
- package/ai/components/tab-panel.md +163 -0
- package/ai/components/table.md +568 -0
- package/ai/components/tabs.md +42 -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 +289 -0
- package/ai/components/tooltip.md +230 -0
- package/ai/components/top-nav.md +159 -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-labels.md +67 -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-size.md +12 -0
- package/ai/reference/bf-skeleton-shape.md +28 -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-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_theme_storage.md +3 -1
- package/ai/reference/provide-bf-color-scheme.md +20 -0
- package/ai/reference/provide-bf-labels.md +12 -4
- package/ai/reference/slide-in-from-end.md +24 -0
- package/ai/reference/slide-out-to-end.md +24 -0
- 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 +6436 -33
- package/fesm2022/bitforgehq-ui-overlay.mjs.map +1 -1
- package/fesm2022/bitforgehq-ui-testing.mjs +618 -1
- package/fesm2022/bitforgehq-ui-testing.mjs.map +1 -1
- package/fesm2022/bitforgehq-ui.mjs +5092 -153
- package/fesm2022/bitforgehq-ui.mjs.map +1 -1
- package/llms-full.txt +25177 -14398
- package/llms.txt +95 -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 +1844 -7
- package/types/bitforgehq-ui-testing.d.ts +748 -1
- package/types/bitforgehq-ui.d.ts +2136 -44
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# bf-page-header
|
|
2
|
+
|
|
3
|
+
Desktop page header with breadcrumb, title, subtitle, badges and a row of actions
|
|
4
|
+
|
|
5
|
+
`BfPageHeaderComponent` from `@bitforgehq/ui` · Navigation · since 0.3.1
|
|
6
|
+
|
|
7
|
+
## Example
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { Component, signal } from '@angular/core';
|
|
11
|
+
import { RouterLink } from '@angular/router';
|
|
12
|
+
import {
|
|
13
|
+
BfBadgeComponent,
|
|
14
|
+
BfBreadcrumbComponent,
|
|
15
|
+
BfBreadcrumbItemComponent,
|
|
16
|
+
BfButtonComponent,
|
|
17
|
+
BfPageHeaderComponent,
|
|
18
|
+
} from '@bitforgehq/ui';
|
|
19
|
+
import { BfIconComponent } from '@bitforgehq/ui/icons';
|
|
20
|
+
import {
|
|
21
|
+
BfMenuButtonComponent,
|
|
22
|
+
BfMenuComponent,
|
|
23
|
+
BfMenuItemDirective,
|
|
24
|
+
} from '@bitforgehq/ui/overlay';
|
|
25
|
+
import { Ellipsis, Plus } from 'lucide';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The bf-page-header overview.
|
|
29
|
+
*
|
|
30
|
+
* Live demo of `bf-page-header`: a breadcrumb above the page title, a subtitle, two badges, and
|
|
31
|
+
* the actions (a ghost Share, a primary New task and an icon-only "More actions" menu button)
|
|
32
|
+
* that report to the result line and wrap under the title in a narrow window.
|
|
33
|
+
*/
|
|
34
|
+
@Component({
|
|
35
|
+
selector: 'app-example-page-header-overview',
|
|
36
|
+
imports: [
|
|
37
|
+
RouterLink,
|
|
38
|
+
BfBadgeComponent,
|
|
39
|
+
BfBreadcrumbComponent,
|
|
40
|
+
BfBreadcrumbItemComponent,
|
|
41
|
+
BfButtonComponent,
|
|
42
|
+
BfIconComponent,
|
|
43
|
+
BfMenuButtonComponent,
|
|
44
|
+
BfMenuComponent,
|
|
45
|
+
BfMenuItemDirective,
|
|
46
|
+
BfPageHeaderComponent,
|
|
47
|
+
],
|
|
48
|
+
template: `
|
|
49
|
+
<div class="flex flex-col gap-3">
|
|
50
|
+
<bf-page-header
|
|
51
|
+
title="Atlas rebrand"
|
|
52
|
+
subtitle="12 of 18 tasks done · due Friday"
|
|
53
|
+
[headingLevel]="2"
|
|
54
|
+
>
|
|
55
|
+
<bf-breadcrumb bfSlot="breadcrumb" label="Page header breadcrumb">
|
|
56
|
+
@for (crumb of trail; track crumb.path) {
|
|
57
|
+
<li bfBreadcrumbItem>
|
|
58
|
+
<a [routerLink]="crumb.path">{{ crumb.label }}</a>
|
|
59
|
+
</li>
|
|
60
|
+
}
|
|
61
|
+
<li bfBreadcrumbItem>Atlas rebrand</li>
|
|
62
|
+
</bf-breadcrumb>
|
|
63
|
+
<bf-badge bfSlot="meta" variant="success">On track</bf-badge>
|
|
64
|
+
<bf-badge bfSlot="meta" variant="info">Design</bf-badge>
|
|
65
|
+
<bf-button bfSlot="actions" variant="ghost" (clicked)="status.set('Shared.')"
|
|
66
|
+
>Share</bf-button
|
|
67
|
+
>
|
|
68
|
+
<bf-button bfSlot="actions" (clicked)="status.set('New task.')">
|
|
69
|
+
<bf-icon bfSlot="icon-start" [icon]="plus" />
|
|
70
|
+
New task
|
|
71
|
+
</bf-button>
|
|
72
|
+
<bf-menu-button
|
|
73
|
+
bfSlot="actions"
|
|
74
|
+
[menu]="more"
|
|
75
|
+
iconOnly
|
|
76
|
+
label="More actions"
|
|
77
|
+
variant="text"
|
|
78
|
+
placement="below-end"
|
|
79
|
+
>
|
|
80
|
+
<bf-icon [icon]="ellipsis" />
|
|
81
|
+
</bf-menu-button>
|
|
82
|
+
</bf-page-header>
|
|
83
|
+
<p data-status aria-live="polite" class="text-caption text-text-muted">{{ status() }}</p>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<ng-template #more>
|
|
87
|
+
<bf-menu>
|
|
88
|
+
<button bfMenuItem (triggered)="status.set('Renamed.')">Rename</button>
|
|
89
|
+
<button bfMenuItem (triggered)="status.set('Archived.')">Archive</button>
|
|
90
|
+
</bf-menu>
|
|
91
|
+
</ng-template>
|
|
92
|
+
`,
|
|
93
|
+
})
|
|
94
|
+
export class PageHeaderOverviewExampleComponent {
|
|
95
|
+
protected readonly plus = Plus;
|
|
96
|
+
protected readonly ellipsis = Ellipsis;
|
|
97
|
+
protected readonly status = signal('Pick an action.');
|
|
98
|
+
protected readonly trail: readonly { path: string; label: string }[] = [
|
|
99
|
+
{ path: '/', label: 'Home' },
|
|
100
|
+
{ path: '/components', label: 'Projects' },
|
|
101
|
+
];
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Overview
|
|
106
|
+
|
|
107
|
+
The top of a desktop page: a breadcrumb above, then the page title with its subtitle and
|
|
108
|
+
badges, and the page's actions at the inline end, wrapping under the title in a narrow window.
|
|
109
|
+
|
|
110
|
+
The top of a desktop page: a breadcrumb above, the page title with an optional subtitle and badges, and the page's actions at the inline end. `bf-page-header` lays out what you project; it draws nothing of its own.
|
|
111
|
+
|
|
112
|
+
`bf-page-header` and the shell's [bf-header](https://bitforge-ui.web.app/components/header) are two siblings, not one shell-aware widget. A shell-aware mode would need the root entry to read the shell's platform and scroll state (a service and an import of `@bitforgehq/ui/mobile`, both forbidden in a root widget), and the large-title collapse and the scroll-under surface depend on `bf-content`, which a desktop page does not have. The two share the slot names that mean the same thing.
|
|
113
|
+
|
|
114
|
+
## When to use
|
|
115
|
+
|
|
116
|
+
Use `bf-page-header` once at the top of every page of a desktop web app, and with `headingLevel` 2 for a section further down a long page. In the Capacitor shell use [bf-header](https://bitforge-ui.web.app/components/header) instead (large title, scroll-under surface).
|
|
117
|
+
|
|
118
|
+
## Basic usage
|
|
119
|
+
|
|
120
|
+
Set `title` and `subtitle`, put the trail in the `breadcrumb` slot, badges in `meta` and buttons in `actions`. The main action is a primary [bf-button](https://bitforge-ui.web.app/components/button), secondary ones `ghost` or `outline`, and the rest go into an icon-only [bf-menu-button](https://bitforge-ui.web.app/components/menu-button) labelled "More actions". In a narrow window the actions wrap under the title. The example at the top of this page shows all of them.
|
|
121
|
+
|
|
122
|
+
## Badges and rich titles
|
|
123
|
+
|
|
124
|
+
The `meta` slot sits beside the title in a wrapping row, so badges drop under a long title instead of squeezing it. When the title needs more than text, leave `title` empty and project the content into the `title` slot; it renders inside the heading.
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import { Component } from '@angular/core';
|
|
128
|
+
import { BfBadgeComponent, BfPageHeaderComponent } from '@bitforgehq/ui';
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Badges beside the title.
|
|
132
|
+
*
|
|
133
|
+
* The `meta` slot holds badges (and other short facts) beside the title; they wrap under it when
|
|
134
|
+
* the title is long or the window narrow. The `title` slot takes rich title content when the
|
|
135
|
+
* plain `title` input is not enough.
|
|
136
|
+
*/
|
|
137
|
+
@Component({
|
|
138
|
+
selector: 'app-example-page-header-meta',
|
|
139
|
+
imports: [BfBadgeComponent, BfPageHeaderComponent],
|
|
140
|
+
template: `
|
|
141
|
+
<div class="flex flex-col gap-8">
|
|
142
|
+
<bf-page-header title="Quarterly brand refresh for the Atlas launch" [headingLevel]="2">
|
|
143
|
+
<bf-badge bfSlot="meta" variant="warning">At risk</bf-badge>
|
|
144
|
+
<bf-badge bfSlot="meta" variant="info">Design</bf-badge>
|
|
145
|
+
<bf-badge bfSlot="meta">Q4</bf-badge>
|
|
146
|
+
</bf-page-header>
|
|
147
|
+
<bf-page-header subtitle="Version 3, edited today" [headingLevel]="2">
|
|
148
|
+
<span bfSlot="title">Atlas rebrand <span class="text-text-muted">/ Brand book</span></span>
|
|
149
|
+
<bf-badge bfSlot="meta" variant="primary">Draft</bf-badge>
|
|
150
|
+
</bf-page-header>
|
|
151
|
+
</div>
|
|
152
|
+
`,
|
|
153
|
+
})
|
|
154
|
+
export class PageHeaderMetaExampleComponent {}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Heading level
|
|
158
|
+
|
|
159
|
+
The title is the page's `h1`. Set `headingLevel` to `2` for a header that opens a section of a page that already has one. The examples on this page use `2`, because the docs page has its own `h1`.
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
import { Component } from '@angular/core';
|
|
163
|
+
import { BfButtonComponent, BfPageHeaderComponent } from '@bitforgehq/ui';
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* A section header with an h2.
|
|
167
|
+
*
|
|
168
|
+
* The page's own header keeps the `h1`; a header for a section further down the same page sets
|
|
169
|
+
* `headingLevel` to `2`, so the page outline stays in order. (Every example on this docs page is
|
|
170
|
+
* at level 2, because the docs page has its own `h1`.)
|
|
171
|
+
*/
|
|
172
|
+
@Component({
|
|
173
|
+
selector: 'app-example-page-header-heading-level',
|
|
174
|
+
imports: [BfButtonComponent, BfPageHeaderComponent],
|
|
175
|
+
template: `
|
|
176
|
+
<div class="flex flex-col gap-8">
|
|
177
|
+
<bf-page-header title="Milestones" subtitle="3 upcoming" [headingLevel]="2">
|
|
178
|
+
<bf-button bfSlot="actions" variant="outline" size="sm">Add milestone</bf-button>
|
|
179
|
+
</bf-page-header>
|
|
180
|
+
</div>
|
|
181
|
+
`,
|
|
182
|
+
})
|
|
183
|
+
export class PageHeaderHeadingLevelExampleComponent {}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Desktop
|
|
187
|
+
|
|
188
|
+
`bf-page-header` is for desktop web pages only: it has no large-title collapse, no scroll-under surface and no safe-area padding. It sits at the top of the page's main column, under the app's top nav or beside its sidebar. On a nested page put a `bf-breadcrumb` in its `breadcrumb` slot; above a list or table, the page's `bf-toolbar` (search, filters, view switch) follows it as its own row, not inside the `actions` slot, which holds only actions on the page as a whole. Every empty slot collapses, so a header with only a title takes one line. Hover, focus and density come from the controls you project.
|
|
189
|
+
|
|
190
|
+
## Theming
|
|
191
|
+
|
|
192
|
+
- Title: `text-title` type, `text` color. Subtitle: `text-body` type, `text-muted` color.
|
|
193
|
+
- The header adds no background, border or shadow; the projected breadcrumb, badges and buttons theme themselves.
|
|
194
|
+
|
|
195
|
+
The actions row sits at the inline end: on the right, and on the left under `dir="rtl"`.
|
|
196
|
+
|
|
197
|
+
## Accessibility
|
|
198
|
+
|
|
199
|
+
The host renders a `<header>`; the title is an `<h1>` by default and an `<h2>` when `headingLevel` is `2`, so keep one `h1` per page. The subtitle is a plain paragraph after the heading. The header handles no keys and moves no focus: the breadcrumb's links and the actions keep their own names and keyboard behaviour, in reading order (breadcrumb, title, badges, actions). Give an icon-only action a `label`.
|
|
200
|
+
|
|
201
|
+
## Inputs
|
|
202
|
+
|
|
203
|
+
| Name | Type | Default | Description |
|
|
204
|
+
| -------------- | ------------------------------------- | ------- | ------------------------------------------------------------------------------- |
|
|
205
|
+
| `title` | `string` | `''` | The page title, rendered as the heading; empty to use the `title` slot instead. |
|
|
206
|
+
| `subtitle` | `string` | `''` | One line under the title, such as a status or a count. |
|
|
207
|
+
| `headingLevel` | `BfPageHeaderHeadingLevel` (`1 \| 2`) | `1` | Heading level of the title so the header fits the page outline. |
|
|
208
|
+
|
|
209
|
+
## Slots
|
|
210
|
+
|
|
211
|
+
- `breadcrumb`: The trail above the title, such as `bf-breadcrumb`.
|
|
212
|
+
- `meta`: Badges beside the title, wrapping under it when they do not fit.
|
|
213
|
+
- `actions`: The page's buttons and menu buttons at the inline end.
|
|
214
|
+
- `title`: Rich title content inside the heading, when the `title` input is empty.
|
|
215
|
+
|
|
216
|
+
## Notes
|
|
217
|
+
|
|
218
|
+
### Do
|
|
219
|
+
|
|
220
|
+
- Keep one primary action; put the rest in a ghost button or the "More actions" menu.
|
|
221
|
+
- Keep the subtitle to one short line, such as progress or a due date.
|
|
222
|
+
|
|
223
|
+
### Don't
|
|
224
|
+
|
|
225
|
+
- Don't use `bf-page-header` inside the Capacitor shell; that is `bf-header`.
|
|
226
|
+
- Don't put search fields or filters in `actions`; they belong in a `bf-toolbar` below the header.
|
|
227
|
+
|
|
228
|
+
## Migration notes
|
|
229
|
+
|
|
230
|
+
### 0.3.1
|
|
231
|
+
|
|
232
|
+
New `bf-page-header` in `@bitforgehq/ui`, the desktop page header: a `breadcrumb` slot above,
|
|
233
|
+
the `title` as an `h1` (`headingLevel` 2 for an `h2`, `BfPageHeaderHeadingLevel`) with an
|
|
234
|
+
optional `subtitle`, a `meta` slot for badges beside the title, a `title` slot for rich title
|
|
235
|
+
content, and an `actions` slot at the inline end that wraps under the title in a narrow window.
|
|
236
|
+
Empty slots take no space. It is a sibling of `bf-header` (`/mobile`), not a shell-aware mode of
|
|
237
|
+
it: no large title, no scroll-under surface, no safe areas.
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# bf-pagination
|
|
2
|
+
|
|
3
|
+
Previous, next and numbered page buttons for a long list or table
|
|
4
|
+
|
|
5
|
+
`BfPaginationComponent` from `@bitforgehq/ui` · Navigation · since 0.3.1
|
|
6
|
+
|
|
7
|
+
## Example
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { Component, computed, signal } from '@angular/core';
|
|
11
|
+
import { BfPaginationComponent } from '@bitforgehq/ui';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The bf-pagination overview.
|
|
15
|
+
*
|
|
16
|
+
* Live demo of `bf-pagination`: twelve pages of search results, the first and last page always
|
|
17
|
+
* shown, one page on each side of the current one and an ellipsis for the rest. The result line
|
|
18
|
+
* reports the page the buttons set.
|
|
19
|
+
*/
|
|
20
|
+
@Component({
|
|
21
|
+
selector: 'app-example-pagination-overview',
|
|
22
|
+
imports: [BfPaginationComponent],
|
|
23
|
+
template: `
|
|
24
|
+
<div class="flex flex-col gap-3">
|
|
25
|
+
<bf-pagination label="Result pages" [(page)]="page" [pageCount]="12" />
|
|
26
|
+
<p data-status aria-live="polite" class="text-caption text-text-muted">{{ status() }}</p>
|
|
27
|
+
</div>
|
|
28
|
+
`,
|
|
29
|
+
})
|
|
30
|
+
export class PaginationOverviewExampleComponent {
|
|
31
|
+
readonly page = signal(1);
|
|
32
|
+
|
|
33
|
+
readonly status = computed(() => {
|
|
34
|
+
const first = (this.page() - 1) * 20 + 1;
|
|
35
|
+
return `Page ${this.page()}: results ${first}–${first + 19} of 240.`;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Overview
|
|
41
|
+
|
|
42
|
+
Page navigation for a long list or table: Previous and Next buttons around the page numbers,
|
|
43
|
+
the current one marked, the hidden runs shown as an ellipsis. `compact` shows "Page 3 of 12"
|
|
44
|
+
between the arrows instead, for narrow screens. The page count comes from `pageCount`, or from
|
|
45
|
+
`total` and `pageSize`; the consumer shows the rows of `page`.
|
|
46
|
+
|
|
47
|
+
Pagination splits a long list or table into pages and lets the user move between them: Previous and Next buttons around the page numbers, the current page marked, the first and the last page always in reach, and an ellipsis for each run of pages in between. `bf-pagination` holds only the page number. You bind `[(page)]` and show that page's rows, sliced on the client or fetched from the server. It works the same in both shells: in a desktop web app and in the Capacitor shell.
|
|
48
|
+
|
|
49
|
+
## When to use
|
|
50
|
+
|
|
51
|
+
Use `bf-pagination` under a table or a list of results the user moves through in steps and refers back to by position ("it was on page 3"): invoices, search results, an audit log. Use a "Load more" [bf-button](https://bitforge-ui.web.app/components/button) at the end of the list instead when the user only reads on, as in a feed, and in the Capacitor shell for a phone list that scrolls. Use [bf-tab-bar](https://bitforge-ui.web.app/components/tab-bar) or a [bf-top-nav](https://bitforge-ui.web.app/components/top-nav) for the sections of an app, not pagination.
|
|
52
|
+
|
|
53
|
+
## Page count
|
|
54
|
+
|
|
55
|
+
The number of pages comes from `pageCount`, or from `total` (the number of items) and `pageSize` (items per page, default 10), as `Math.ceil(total / pageSize)`. When `pageCount` is above 0 it wins and `total` is ignored; with neither set, or no items, there is one page. The count is never below 1, so an empty table still shows "1" with both arrows disabled.
|
|
56
|
+
|
|
57
|
+
`page` is 1-based and starts at 1. A `page` beyond the count shows as the last page (and below 1 as the first), but the component never writes the clamped value back: when the total shrinks, after a filter or a delete, set `page` back in range yourself.
|
|
58
|
+
|
|
59
|
+
## Pages around the current one
|
|
60
|
+
|
|
61
|
+
The row always shows the first and the last page, the current page and `siblings` pages on each side of it (default 1). Every run of hidden pages becomes an ellipsis; a run of only one page shows that page instead. So the row keeps the same number of slots (`2 × siblings + 5`) wherever the current page is, and the buttons do not shift under the pointer as the user steps through. Set `siblings` to `0` for the shortest row, or to `2` on a wide page. With few pages every page shows.
|
|
62
|
+
|
|
63
|
+
## Compact
|
|
64
|
+
|
|
65
|
+
`compact` replaces the page buttons with the position as text, "Page 3 of 12" (`provideBfLabels()`'s `pageOf`), between the arrows. Use it where the full row does not fit: a narrow column, a phone, a side panel. A responsive page can render the full row from `md` up and the compact one below it (a `hidden md:block` and a `md:hidden` wrapper around two `bf-pagination`s bound to the same `page`).
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
import { Component, signal } from '@angular/core';
|
|
69
|
+
import { BfPaginationComponent } from '@bitforgehq/ui';
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Compact pagination for a narrow space.
|
|
73
|
+
*
|
|
74
|
+
* `compact` shows "Page 3 of 12" between the arrows instead of the page buttons; the text is a
|
|
75
|
+
* polite live region, so each step is announced.
|
|
76
|
+
*/
|
|
77
|
+
@Component({
|
|
78
|
+
selector: 'app-example-pagination-compact',
|
|
79
|
+
imports: [BfPaginationComponent],
|
|
80
|
+
template: `<bf-pagination
|
|
81
|
+
label="Compact result pages"
|
|
82
|
+
compact
|
|
83
|
+
[(page)]="page"
|
|
84
|
+
[pageCount]="12"
|
|
85
|
+
/>`,
|
|
86
|
+
})
|
|
87
|
+
export class PaginationCompactExampleComponent {
|
|
88
|
+
readonly page = signal(3);
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## With a table
|
|
93
|
+
|
|
94
|
+
Put the pagination under the table it pages, aligned to the inline end or centred, and show only the current page's rows: slice them in a `computed()` from `page`, or fetch the page from the server in a `httpResource()` keyed on it. When the user sorts or filters, set `page` back to 1. The [table](https://bitforge-ui.web.app/components/table) page has a paged example.
|
|
95
|
+
|
|
96
|
+
## Sizes
|
|
97
|
+
|
|
98
|
+
`size` sets the button height: `sm` 32 px, `md` 40 px (default), `lg` 48 px; the page buttons are at least as wide as they are tall and grow for long numbers. Unset, it follows `provideBfDefaults()`'s `controlSize`, so a dense page's `{ controlSize: 'sm' }` makes it small with the rest of the page. Under a coarse pointer every button's hit area extends to 44 px tall without changing its look. When the row wraps (a narrow container), the lines keep 8 px apart, so the extended hit areas never cover a button on the next line.
|
|
99
|
+
|
|
100
|
+
## Desktop
|
|
101
|
+
|
|
102
|
+
The page buttons take the quiet ghost look: no fill at rest, `surface-muted` on hover, a step darker while pressed; the arrows are text icon buttons with a CSS chevron. The current page takes the `primary-soft` fill. The pointer cursor shows on every live button, the not-allowed cursor on an arrow at the end of the range. The focus ring shows on keyboard focus only. The arrows point to the inline start and end, so they mirror under `dir="rtl"`. Inside `provideBfShell()` (the Capacitor shell) it renders the same; prefer `compact` on a phone.
|
|
103
|
+
|
|
104
|
+
## Theming
|
|
105
|
+
|
|
106
|
+
- Page buttons: `text-muted` with a transparent fill; hover `surface-muted` and `text`, pressed `surface-pressed`; the current page `primary-soft` with `primary-strong` text, hover `primary-soft-hover`. Under forced colours the current page is outlined.
|
|
107
|
+
- Arrows: `text-muted` with a transparent fill, hover `surface-muted`, pressed `primary-soft-pressed`; at the ends `text-disabled`. The chevron is drawn with 1.5 px borders in the text colour.
|
|
108
|
+
- Ellipsis and the compact "Page 3 of 12": `text-muted`, bold, tabular figures.
|
|
109
|
+
- Shape: `rounded-control` buttons, the `ring` focus ring. Type: `text-label-xs`, `text-label-sm` and `text-label` per size.
|
|
110
|
+
|
|
111
|
+
## Accessibility
|
|
112
|
+
|
|
113
|
+
The host renders a `nav` landmark named by `label`, else "Pagination" (`provideBfLabels()`'s `pagination`), around a list of native `button`s; give each pagination on a page its own `label` ("Invoice pages"). A page button reads as "Page 4" (`goToPage`), so its visible number is part of its name, and the current page carries `aria-current="page"`. The ellipses are `aria-hidden`, so the list announces only the buttons. Previous and Next are named "Previous page" and "Next page" (`previousPage`, `nextPage`); the chevrons are `aria-hidden`.
|
|
114
|
+
|
|
115
|
+
At the first and the last page the arrow is disabled with `aria-disabled="true"`, not native `disabled`: it stays focusable and does nothing when pressed. This follows the WAI-ARIA guidance for controls that turn unavailable while focused: a user who presses Next until the last page keeps focus on the button instead of losing it to the page. In `compact` mode the "Page 3 of 12" text is a polite live region, so the new position is announced after each step; in the full row the pressed page button becomes the current one in place. Moving to another page does not move focus: the content that changed is the consumer's, so announce a new result count there if it matters.
|
|
116
|
+
|
|
117
|
+
### Keyboard
|
|
118
|
+
|
|
119
|
+
| Key | Action |
|
|
120
|
+
| ------------------------------------------------ | -------------------------------------------------------------------------- |
|
|
121
|
+
| <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> | Moves through Previous, the page buttons and Next, including disabled ends |
|
|
122
|
+
| <kbd>Enter</kbd> / <kbd>Space</kbd> | Goes to the focused page, or to the previous or next one |
|
|
123
|
+
|
|
124
|
+
## Inputs
|
|
125
|
+
|
|
126
|
+
| Name | Type | Default | Description |
|
|
127
|
+
| ----------- | ----------------------------------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
128
|
+
| `pageCount` | `number` | `0` | The number of pages; wins over `total` and `pageSize` when above 0. |
|
|
129
|
+
| `total` | `number` | `0` | The number of items, used with `pageSize` while `pageCount` is 0. |
|
|
130
|
+
| `pageSize` | `number` | `10` | Items per page, used with `total` while `pageCount` is 0. |
|
|
131
|
+
| `siblings` | `number` | `1` | How many pages to show on each side of the current one. |
|
|
132
|
+
| `compact` | `boolean` | `false` | Shows "Page 3 of 12" between the arrows instead of the page buttons, for narrow screens. |
|
|
133
|
+
| `size` | `BfSize \| undefined` (`'sm' \| 'md' \| 'lg' \| undefined`) | `undefined` | Button height step: sm 32, md 40, lg 48 px (44 px hit area under a coarse pointer); unset follows `provideBfDefaults()` (`controlSize`, `md` by default). |
|
|
134
|
+
| `label` | `string` | `''` | Accessible name of the navigation; empty falls back to `provideBfLabels()`'s `pagination` ("Pagination"). |
|
|
135
|
+
| `page` | `number` | `1` | The current page, 1-based; two-way bindable. The buttons set it; show the rows of this page. Two-way: `[(page)]`. |
|
|
136
|
+
|
|
137
|
+
## Methods
|
|
138
|
+
|
|
139
|
+
### `go(target: number): void`
|
|
140
|
+
|
|
141
|
+
Goes to a page: sets `page` when it is another page within 1…count; else does nothing.
|
|
142
|
+
|
|
143
|
+
| Parameter | Type | Description |
|
|
144
|
+
| --------- | -------- | -------------------------- |
|
|
145
|
+
| `target` | `number` | The 1-based page to go to. |
|
|
146
|
+
|
|
147
|
+
## Notes
|
|
148
|
+
|
|
149
|
+
### Do
|
|
150
|
+
|
|
151
|
+
- Show the current page's rows and nothing else, and go back to page 1 after a sort or a filter.
|
|
152
|
+
- Give every pagination a `label` when a page has more than one.
|
|
153
|
+
- Use `compact` in a narrow space, and keep the full row where it fits.
|
|
154
|
+
|
|
155
|
+
### Don't
|
|
156
|
+
|
|
157
|
+
- Don't page a short list that fits on one screen; show it whole.
|
|
158
|
+
- Don't use pagination to switch between sections or steps; it is for pages of one list.
|
|
159
|
+
|
|
160
|
+
## Migration notes
|
|
161
|
+
|
|
162
|
+
### 0.3.1
|
|
163
|
+
|
|
164
|
+
New `bf-pagination` in `@bitforgehq/ui`, page navigation for both shells: a `nav` (named by
|
|
165
|
+
`label`, else the new `pagination` label) with Previous and Next buttons around the page buttons.
|
|
166
|
+
`[(page)]` is 1-based; the count comes from `pageCount`, else `total` over `pageSize` (default 10).
|
|
167
|
+
The row keeps the first and the last page and `siblings` (default 1) on each side of the current
|
|
168
|
+
one, which carries `aria-current="page"`, and turns hidden runs into an `aria-hidden` ellipsis, at
|
|
169
|
+
a constant number of slots. `compact` shows "Page 3 of 12" (`pageOf`) between the arrows as a
|
|
170
|
+
polite live region. At the ends the arrows are `aria-disabled` and stay focusable. `size` follows
|
|
171
|
+
`controlSize`; the chevrons are CSS and mirror under `dir="rtl"`. New `BfPaginationHarness` in
|
|
172
|
+
`@bitforgehq/ui/testing` (`getCurrentPage()`, `getPageCount()`, `getVisiblePages()`, `goTo()`,
|
|
173
|
+
`next()`, `previous()`).
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# bf-popover
|
|
2
|
+
|
|
3
|
+
Anchored, non-modal panel for filters, details or a small form
|
|
4
|
+
|
|
5
|
+
`BfPopoverComponent` from `@bitforgehq/ui/overlay` · Overlays · since 0.3.1
|
|
6
|
+
|
|
7
|
+
## Example
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { Component, computed, signal } from '@angular/core';
|
|
11
|
+
import { BfButtonComponent, BfCheckboxComponent } from '@bitforgehq/ui';
|
|
12
|
+
import { BfPopoverComponent, BfPopoverTriggerDirective } from '@bitforgehq/ui/overlay';
|
|
13
|
+
|
|
14
|
+
type Status = 'open' | 'progress' | 'done';
|
|
15
|
+
|
|
16
|
+
const STATUS_LABELS: Record<Status, string> = {
|
|
17
|
+
open: 'Open',
|
|
18
|
+
progress: 'In progress',
|
|
19
|
+
done: 'Done',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The bf-popover overview.
|
|
24
|
+
*
|
|
25
|
+
* Live demo of `bf-popover`: a Filters button opens a small filter form anchored below it. Focus
|
|
26
|
+
* moves to the first checkbox; Apply copies the choices to the task list and closes the popover
|
|
27
|
+
* through the exported trigger, while `Escape` or a click outside closes it without applying. The
|
|
28
|
+
* result line reports the filter in effect.
|
|
29
|
+
*/
|
|
30
|
+
@Component({
|
|
31
|
+
selector: 'app-example-popover-overview',
|
|
32
|
+
imports: [BfButtonComponent, BfCheckboxComponent, BfPopoverComponent, BfPopoverTriggerDirective],
|
|
33
|
+
template: `
|
|
34
|
+
<div class="flex flex-col items-start gap-3">
|
|
35
|
+
<bf-button
|
|
36
|
+
variant="outline"
|
|
37
|
+
[bfPopoverTriggerFor]="filters"
|
|
38
|
+
#filtersTrigger="bfPopoverTrigger"
|
|
39
|
+
(bfPopoverOpened)="draft.set(applied())"
|
|
40
|
+
>
|
|
41
|
+
Filters
|
|
42
|
+
</bf-button>
|
|
43
|
+
<p data-status aria-live="polite" class="text-caption text-text-muted">{{ status() }}</p>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<ng-template #filters>
|
|
47
|
+
<bf-popover class="w-72">
|
|
48
|
+
<h2 class="text-label text-text mb-3">Filter tasks</h2>
|
|
49
|
+
<div role="group" aria-label="Status" class="flex flex-col gap-2">
|
|
50
|
+
@for (status of statuses; track status) {
|
|
51
|
+
<bf-checkbox
|
|
52
|
+
[label]="labels[status]"
|
|
53
|
+
[checked]="draft().includes(status)"
|
|
54
|
+
(changed)="toggle(status, $event)"
|
|
55
|
+
/>
|
|
56
|
+
}
|
|
57
|
+
</div>
|
|
58
|
+
<div class="mt-4 flex justify-end gap-2">
|
|
59
|
+
<bf-button variant="text" size="sm" (clicked)="draft.set([])">Clear</bf-button>
|
|
60
|
+
<bf-button size="sm" (clicked)="applied.set(draft()); filtersTrigger.close()">
|
|
61
|
+
Apply
|
|
62
|
+
</bf-button>
|
|
63
|
+
</div>
|
|
64
|
+
</bf-popover>
|
|
65
|
+
</ng-template>
|
|
66
|
+
`,
|
|
67
|
+
})
|
|
68
|
+
export class PopoverOverviewExampleComponent {
|
|
69
|
+
protected readonly statuses: readonly Status[] = ['open', 'progress', 'done'];
|
|
70
|
+
protected readonly labels = STATUS_LABELS;
|
|
71
|
+
readonly applied = signal<readonly Status[]>(['open', 'progress']);
|
|
72
|
+
readonly draft = signal<readonly Status[]>(['open', 'progress']);
|
|
73
|
+
|
|
74
|
+
readonly status = computed(() => {
|
|
75
|
+
const applied = this.applied();
|
|
76
|
+
return applied.length
|
|
77
|
+
? `Showing ${applied.map((s) => STATUS_LABELS[s]).join(', ')} tasks.`
|
|
78
|
+
: 'Showing all tasks.';
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
toggle(status: Status, checked: boolean): void {
|
|
82
|
+
this.draft.update((draft) =>
|
|
83
|
+
checked ? [...draft, status] : draft.filter((s) => s !== status),
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Overview
|
|
90
|
+
|
|
91
|
+
Non-modal panel anchored to a trigger, for filters, details or a small form (role `dialog`).
|
|
92
|
+
Put it in an `ng-template` that a `[bfPopoverTriggerFor]` opens; it takes the id the trigger's
|
|
93
|
+
`aria-controls` points at and is named by `bfPopoverLabel`, else by its first heading, else by
|
|
94
|
+
the trigger.
|
|
95
|
+
|
|
96
|
+
Anchored panels for desktop and web apps, from `@bitforgehq/ui/overlay` (needs `@angular/cdk`, no
|
|
97
|
+
Capacitor). A `bf-popover` is the panel; put it in an `ng-template` and open it from a button with
|
|
98
|
+
`[bfPopoverTriggerFor]`. It opens beside its trigger, flips when it has no room, and leaves the
|
|
99
|
+
page usable: it is non-modal, so there is no backdrop, focus is not trapped and the page does not
|
|
100
|
+
become inert. It follows the
|
|
101
|
+
[APG disclosure](https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/) pattern with a non-modal
|
|
102
|
+
`dialog` panel. Popovers open in the CDK overlay container, so they work inside a
|
|
103
|
+
`BfDialogService` dialog.
|
|
104
|
+
|
|
105
|
+
Import `BfPopoverComponent` and `BfPopoverTriggerDirective` from `@bitforgehq/ui/overlay`.
|
|
106
|
+
|
|
107
|
+
## When to use
|
|
108
|
+
|
|
109
|
+
Use `bf-popover` for a few fields or details that belong to one control and are used for a moment:
|
|
110
|
+
filters for a list, the details of a value, a small form such as "Rename". Use
|
|
111
|
+
[bf-menu](https://bitforge-ui.web.app/components/menu) instead for a list of actions or view settings,
|
|
112
|
+
[BfDialogService](https://bitforge-ui.web.app/api/bf-dialog-service) for a task that needs the user's full attention or more
|
|
113
|
+
than a few fields, a [bf-tooltip](https://bitforge-ui.web.app/components/tooltip) for a short hint with nothing to interact
|
|
114
|
+
with, and in a Capacitor app a [bf-sheet](https://bitforge-ui.web.app/components/sheet).
|
|
115
|
+
|
|
116
|
+
## Opening and closing
|
|
117
|
+
|
|
118
|
+
The trigger toggles the popover on a click (and on `Enter` or `Space`, which click a button).
|
|
119
|
+
Focus moves to the first control inside (or to the element marked `autofocus`, or to the panel
|
|
120
|
+
when it holds no control). `Escape`, a click outside, `Tab` past the panel's last control or a
|
|
121
|
+
navigation to another URL path close it. `Escape` and a click on nothing focusable put focus back
|
|
122
|
+
on the trigger; a click on another control leaves focus there.
|
|
123
|
+
|
|
124
|
+
Export the trigger as `bfPopoverTrigger` to close the popover from inside it, for example from an
|
|
125
|
+
Apply button: `#trigger="bfPopoverTrigger"`, then `trigger.close()`. It also has `open()`,
|
|
126
|
+
`toggle()` and `isOpen()`. `(bfPopoverOpened)` and `(bfPopoverClosed)` report its state.
|
|
127
|
+
|
|
128
|
+
The trigger can be a native `button` or a `bf-button`: on a host that does not take focus itself,
|
|
129
|
+
the ARIA state goes on the first control inside it.
|
|
130
|
+
|
|
131
|
+
## Placement
|
|
132
|
+
|
|
133
|
+
`bfPopoverPlacement` sets where the panel opens: `'below-start'` (default), `'below-end'`,
|
|
134
|
+
`'above-start'` or `'above-end'`, 4 px from the trigger. `start` and `end` follow the text
|
|
135
|
+
direction. When the panel would overflow the viewport, it flips to the other side vertically
|
|
136
|
+
first, then to the other alignment. It scrolls past the viewport height minus 2 rem. The
|
|
137
|
+
placement is read each time it opens.
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
import { Component } from '@angular/core';
|
|
141
|
+
import { BfButtonComponent } from '@bitforgehq/ui';
|
|
142
|
+
import { BfPopoverComponent, BfPopoverTriggerDirective } from '@bitforgehq/ui/overlay';
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* A details popover aligned to the trigger's end.
|
|
146
|
+
*
|
|
147
|
+
* `bfPopoverPlacement="below-end"` opens the panel under the trigger, aligned to its inline end;
|
|
148
|
+
* `bfPopoverLabel` names it when it has no heading.
|
|
149
|
+
*/
|
|
150
|
+
@Component({
|
|
151
|
+
selector: 'app-example-popover-placement',
|
|
152
|
+
imports: [BfButtonComponent, BfPopoverComponent, BfPopoverTriggerDirective],
|
|
153
|
+
template: `
|
|
154
|
+
<div class="flex justify-end">
|
|
155
|
+
<bf-button
|
|
156
|
+
variant="text"
|
|
157
|
+
[bfPopoverTriggerFor]="details"
|
|
158
|
+
bfPopoverPlacement="below-end"
|
|
159
|
+
bfPopoverLabel="Delivery details"
|
|
160
|
+
>
|
|
161
|
+
Delivery
|
|
162
|
+
</bf-button>
|
|
163
|
+
</div>
|
|
164
|
+
|
|
165
|
+
<ng-template #details>
|
|
166
|
+
<bf-popover class="w-64">
|
|
167
|
+
<p class="text-body text-text">Arrives Thursday between 9 and 12.</p>
|
|
168
|
+
<p class="text-caption text-text-muted mt-1">Tracking updates every hour.</p>
|
|
169
|
+
</bf-popover>
|
|
170
|
+
</ng-template>
|
|
171
|
+
`,
|
|
172
|
+
})
|
|
173
|
+
export class PopoverPlacementExampleComponent {}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Naming
|
|
177
|
+
|
|
178
|
+
The panel is a `dialog` named by `bfPopoverLabel` on the trigger when you set it, else by the
|
|
179
|
+
first heading inside it, else by the trigger's text. Give a popover a heading when it holds a
|
|
180
|
+
form, so its purpose is announced when focus moves in.
|
|
181
|
+
|
|
182
|
+
## Desktop
|
|
183
|
+
|
|
184
|
+
Built for a mouse and a keyboard: the panel opens next to its trigger and the page stays usable
|
|
185
|
+
around it, so a click elsewhere closes it and lands where it was aimed. `Tab` walks through the
|
|
186
|
+
panel and on to the control after the trigger, closing the panel, so keyboard users never get
|
|
187
|
+
stuck in it. Set the panel's width with a class on `bf-popover` (`w-72`); it never grows past the
|
|
188
|
+
viewport width minus 2 rem.
|
|
189
|
+
|
|
190
|
+
Under a coarse pointer it works the same, with the panel's controls at their own touch sizes;
|
|
191
|
+
in the Capacitor shell a `bf-sheet` suits phone screens better.
|
|
192
|
+
|
|
193
|
+
## Theming
|
|
194
|
+
|
|
195
|
+
- Panel: `bg-surface`, `text-text`, a 1 px `border-border`, `rounded-card`, `shadow-floating`,
|
|
196
|
+
`p-card` (18 px).
|
|
197
|
+
- The content is yours: use the library's type and field tokens inside it.
|
|
198
|
+
|
|
199
|
+
## Accessibility
|
|
200
|
+
|
|
201
|
+
- The trigger's control has `aria-haspopup="dialog"`, `aria-expanded` and, while open,
|
|
202
|
+
`aria-controls` pointing at the panel.
|
|
203
|
+
- The panel is `role="dialog"` without `aria-modal`, named as described under Naming. It gets the
|
|
204
|
+
id that `aria-controls` names.
|
|
205
|
+
- Focus: opening moves focus into the panel; `Escape` returns it to the trigger. `Tab` is not
|
|
206
|
+
trapped: past the last control the panel closes and focus continues after the trigger, and
|
|
207
|
+
`Shift+Tab` before the first control closes it onto the trigger.
|
|
208
|
+
- `Escape` goes to the popover first: inside a dialog, the first `Escape` closes the popover and
|
|
209
|
+
the next one the dialog.
|
|
210
|
+
|
|
211
|
+
### Keyboard
|
|
212
|
+
|
|
213
|
+
| Key | Where | Action |
|
|
214
|
+
| ----------------------------------- | ---------------- | ------------------------------------------------------- |
|
|
215
|
+
| <kbd>Enter</kbd> / <kbd>Space</kbd> | Trigger | Opens the popover (closes it when open), focus moves in |
|
|
216
|
+
| <kbd>Tab</kbd> | Panel | Next control; past the last one closes, focus moves on |
|
|
217
|
+
| <kbd>Shift</kbd>+<kbd>Tab</kbd> | First control | Closes the popover, focus back on the trigger |
|
|
218
|
+
| <kbd>Escape</kbd> | Panel or trigger | Closes the popover, focus back on the trigger |
|
|
219
|
+
|
|
220
|
+
## Slots
|
|
221
|
+
|
|
222
|
+
- `default`: The panel's content: a heading, text, fields and actions.
|
|
223
|
+
|
|
224
|
+
## Notes
|
|
225
|
+
|
|
226
|
+
### Do
|
|
227
|
+
|
|
228
|
+
- Keep a popover small: a heading, a few fields and one or two actions.
|
|
229
|
+
- Give a popover with a form a heading, or set `bfPopoverLabel`.
|
|
230
|
+
- Apply a filter from an explicit button, or as each field changes, and say which in the panel.
|
|
231
|
+
|
|
232
|
+
### Don't
|
|
233
|
+
|
|
234
|
+
- Don't put a long form or a multi-step task in a popover; use a dialog.
|
|
235
|
+
- Don't open a popover on hover; it opens on a click.
|
|
236
|
+
- Don't use a popover for a list of actions; use a menu.
|
|
237
|
+
- Don't nest popovers.
|
|
238
|
+
|
|
239
|
+
## Migration notes
|
|
240
|
+
|
|
241
|
+
### 0.3.1
|
|
242
|
+
|
|
243
|
+
New `[bfPopoverTriggerFor]` (`BfPopoverTriggerDirective`, exported as `bfPopoverTrigger`) and the
|
|
244
|
+
`bf-popover` panel (`BfPopoverComponent`) in `@bitforgehq/ui/overlay`: a non-modal `role="dialog"`
|
|
245
|
+
panel anchored to its trigger, for filters, details or a small form. `bfPopoverPlacement`
|
|
246
|
+
(`BfPopoverPlacement`, the menu's four placements), `bfPopoverLabel`, `(bfPopoverOpened)` and
|
|
247
|
+
`(bfPopoverClosed)`, and `open()`, `close()`, `toggle()`, `isOpen()`. The trigger's control gets
|
|
248
|
+
`aria-haspopup="dialog"`, `aria-expanded` and `aria-controls`; focus moves in on open, `Tab` is not
|
|
249
|
+
trapped (it closes the panel past either end), and `Escape`, a click outside or a navigation close
|
|
250
|
+
it and return focus. New `BfPopoverHarness` in `@bitforgehq/ui/testing`.
|