@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,240 @@
|
|
|
1
|
+
# bf-time-input
|
|
2
|
+
|
|
3
|
+
Typed time-of-day field with a popover list of times, valued as an ISO string
|
|
4
|
+
|
|
5
|
+
`BfTimeInputComponent` from `@bitforgehq/ui/overlay` · Forms · since 0.3.1
|
|
6
|
+
|
|
7
|
+
## Example
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { Component, computed, signal } from '@angular/core';
|
|
11
|
+
import { BfTimeInputComponent } from '@bitforgehq/ui/overlay';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The bf-time-input overview.
|
|
15
|
+
*
|
|
16
|
+
* Live demo of `bf-time-input`: a start time typed as hour and minute (24-hour in British
|
|
17
|
+
* English) or picked from the list of times every 30 minutes, which opens scrolled to the value.
|
|
18
|
+
* The value is an ISO time string; the status line shows it.
|
|
19
|
+
*/
|
|
20
|
+
@Component({
|
|
21
|
+
selector: 'app-example-time-input-overview',
|
|
22
|
+
imports: [BfTimeInputComponent],
|
|
23
|
+
template: `
|
|
24
|
+
<div class="flex max-w-xs flex-col gap-3">
|
|
25
|
+
<bf-time-input label="Start" locale="en-GB" [(value)]="start" />
|
|
26
|
+
<p data-status aria-live="polite" class="text-caption text-text-muted">{{ status() }}</p>
|
|
27
|
+
</div>
|
|
28
|
+
`,
|
|
29
|
+
})
|
|
30
|
+
export class TimeInputOverviewExampleComponent {
|
|
31
|
+
/** The start time, 'HH:mm'; null while empty or incomplete. */
|
|
32
|
+
readonly start = signal<string | null>('09:30');
|
|
33
|
+
|
|
34
|
+
readonly status = computed(() => `Start: ${this.start() ?? 'none'}`);
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Overview
|
|
39
|
+
|
|
40
|
+
Time field typed part by part (hour, minute, optional seconds, AM/PM under a 12-hour clock)
|
|
41
|
+
with a popover list of times, valued as an ISO time string.
|
|
42
|
+
|
|
43
|
+
A time-of-day field typed part by part: the hour and the minute (and the seconds with `seconds`) in the locale's order and clock, with an AM/PM part under a 12-hour clock. A clock button opens a list of times every `step` minutes in a popover, scrolled to the value. The value is an ISO time string (`'14:30'`, `'14:30:05'` with seconds) on a 24-hour clock whatever the field shows, with no date and no time zone.
|
|
44
|
+
|
|
45
|
+
Import `BfTimeInputComponent` from `@bitforgehq/ui/overlay` (it needs `@angular/cdk` for the popover). The parts work as in [bf-date-input](https://bitforge-ui.web.app/components/date-input); for a date and a time in one field use [bf-datetime-input](https://bitforge-ui.web.app/components/datetime-input).
|
|
46
|
+
|
|
47
|
+
## When to use
|
|
48
|
+
|
|
49
|
+
Use `bf-time-input` for a time of day in a form: a start time, an alarm, opening hours. Use [bf-select](https://bitforge-ui.web.app/components/select) or [bf-segmented](https://bitforge-ui.web.app/components/segmented) instead for a handful of fixed slots (morning, afternoon), and [bf-number-input](https://bitforge-ui.web.app/components/number-input) for a duration ("90 minutes"), which is not a time of day. It works in both shells; in a Capacitor app set `native`, so a phone shows its own time picker (see Native picker).
|
|
50
|
+
|
|
51
|
+
## Typing a time
|
|
52
|
+
|
|
53
|
+
Each part takes digits and moves on once it is complete ("7" in the hour of a 24-hour clock waits for a second digit, "3" moves on), and a part takes its value only then or when you leave it, so typing "30" minutes never passes through 03. <kbd>ArrowUp</kbd> and <kbd>ArrowDown</kbd> step a part and wrap (59 to 00), <kbd>Backspace</kbd> clears it, <kbd>ArrowLeft</kbd> and <kbd>ArrowRight</kbd> move between parts and <kbd>:</kbd> moves on. `value` is `null` while any part is empty, and `(changed)` emits the new value right after a user change.
|
|
54
|
+
|
|
55
|
+
## 12-hour clock
|
|
56
|
+
|
|
57
|
+
The clock follows the locale (`Intl.DateTimeFormat`'s `hourCycle`): American English counts 1 to 12 with an AM/PM part, British English and German count 0 to 23. `hourCycle` forces `h12` or `h23`. The AM/PM part takes the first letter of the locale's AM or PM name, or the arrows; the names come from `Intl`, its spinbutton name from the `timeInputDayPeriod` label. The value stays a 24-hour time: 2:30 PM is `'14:30'`, 12:05 AM is `'00:05'`.
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { Component, signal } from '@angular/core';
|
|
61
|
+
import { BfTimeInputComponent } from '@bitforgehq/ui/overlay';
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* A 12-hour clock.
|
|
65
|
+
*
|
|
66
|
+
* American English counts hours 1 to 12 with an AM/PM part (type A or P, or press the arrows);
|
|
67
|
+
* `hourCycle="h12"` forces it in any locale. The value stays a 24-hour ISO time: 2:30 PM is
|
|
68
|
+
* '14:30'.
|
|
69
|
+
*/
|
|
70
|
+
@Component({
|
|
71
|
+
selector: 'app-example-time-input-twelve-hour',
|
|
72
|
+
imports: [BfTimeInputComponent],
|
|
73
|
+
template: `<bf-time-input class="max-w-xs" label="Alarm" locale="en-US" [(value)]="alarm" />`,
|
|
74
|
+
})
|
|
75
|
+
export class TimeInputTwelveHourExampleComponent {
|
|
76
|
+
readonly alarm = signal<string | null>('14:30');
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## List of times
|
|
81
|
+
|
|
82
|
+
The clock button opens a list of times every `step` minutes from midnight (30 by default, any number from 1 to 720), written in the locale's clock. It opens scrolled to the value (else to the next time from now) with keyboard focus on it; <kbd>ArrowUp</kbd> and <kbd>ArrowDown</kbd> move, <kbd>Enter</kbd> or a click picks a time, closes the list and returns focus to the button. `min` and `max` disable the times outside them, and a typed time outside them shows `timeInputMin` / `timeInputMax` once focus leaves the field; an incomplete time shows `timeInputInvalid`. `picker="none"` leaves the field typed only.
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
import { Component, signal } from '@angular/core';
|
|
86
|
+
import { BfTimeInputComponent } from '@bitforgehq/ui/overlay';
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Step, min and max.
|
|
90
|
+
*
|
|
91
|
+
* `step="15"` lists a time every 15 minutes, and `min` and `max` disable the times outside
|
|
92
|
+
* opening hours; a typed time outside them shows an error once focus leaves the field.
|
|
93
|
+
*/
|
|
94
|
+
@Component({
|
|
95
|
+
selector: 'app-example-time-input-step',
|
|
96
|
+
imports: [BfTimeInputComponent],
|
|
97
|
+
template: `
|
|
98
|
+
<bf-time-input
|
|
99
|
+
class="max-w-xs"
|
|
100
|
+
label="Pickup"
|
|
101
|
+
locale="en-GB"
|
|
102
|
+
helpText="Between 08:00 and 18:00."
|
|
103
|
+
step="15"
|
|
104
|
+
min="08:00"
|
|
105
|
+
max="18:00"
|
|
106
|
+
[(value)]="pickup"
|
|
107
|
+
/>
|
|
108
|
+
`,
|
|
109
|
+
})
|
|
110
|
+
export class TimeInputStepExampleComponent {
|
|
111
|
+
readonly pickup = signal<string | null>(null);
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Seconds
|
|
116
|
+
|
|
117
|
+
`seconds` adds a seconds part, and the value becomes `'HH:mm:ss'`; the list's times then end in `:00`. Leave it off unless the seconds matter (a log filter, a timer).
|
|
118
|
+
|
|
119
|
+
## Native picker
|
|
120
|
+
|
|
121
|
+
`native` renders an `input type="time"` with the same label, help and error text when the primary pointer is coarse (`matchMedia('(pointer: coarse)')`), and the segmented field under a mouse. `min`, `max` and `seconds` go to the native input. Recommended in the Capacitor shell: phones show their own time wheel, and typing into the parts needs a hardware keyboard.
|
|
122
|
+
|
|
123
|
+
## With Signal Forms
|
|
124
|
+
|
|
125
|
+
Bind the field with `[formField]`: required, touched, errors, disabled and readonly flow in, the first error shows once focus has left the field, and `focus()` focuses the first empty part. The [bf-date-input](https://bitforge-ui.web.app/components/date-input) page shows a booking form with both fields. Under `[formField]` the form owns `min` and `max`: give them through the schema with a metadata key (`metadata(path.time, OPENS, () => '09:00')` and `metadata(path.time, MIN, () => OPENS)`, with `OPENS = createMetadataKey<string | undefined>()`). In specs, `BfTimeInputHarness` from `@bitforgehq/ui/testing` types a time (`setValue('2:30 PM')`) or picks one from the list (`pickTime('14:30')`).
|
|
126
|
+
|
|
127
|
+
## Desktop
|
|
128
|
+
|
|
129
|
+
The field shows the `primary` border and focus ring while any part has focus, the focused part tinted `primary-soft`. The list's times tint `surface-muted` on hover; the keyboard's active time carries the focus ring while the list has keyboard focus. Fields default to `md` (40 px); `size` and `provideBfDefaults({ fieldSize })` set the step as on `bf-input` ([density guide](https://bitforge-ui.web.app/density)).
|
|
130
|
+
|
|
131
|
+
## Theming
|
|
132
|
+
|
|
133
|
+
- Field, parts and button: as `bf-date-input` (`surface`, `border-strong`, `primary` focus, `primary-soft` focused part, `text-muted` placeholders).
|
|
134
|
+
- List: times `text` with `rounded-control` pills, hover `surface-muted`; the selected time `primary` with `on-primary`, bold; disabled times `text-disabled`; the active time `ring`.
|
|
135
|
+
- Popover: `surface`, `border`, `rounded-card`, `shadow-floating`.
|
|
136
|
+
|
|
137
|
+
## Accessibility
|
|
138
|
+
|
|
139
|
+
The parts are a `role="group"` named by `label` (else `ariaLabel` or `ariaLabelledby`); each part is a `role="spinbutton"` named from the `timeInputHour`, `timeInputMinute`, `timeInputSecond` and `timeInputDayPeriod` labels, with `aria-valuemin`, `aria-valuemax`, `aria-valuenow` and `aria-valuetext` (the AM or PM name), and the field's `aria-invalid`, `aria-errormessage`, `aria-required`, `aria-readonly` and `aria-disabled`. The clock button is named by `timeInputOpenList`, with `aria-haspopup="dialog"`, `aria-expanded` and `aria-controls`. The list is a `role="listbox"` named by the same label, its times `role="option"` with `aria-selected` and `aria-disabled`; focus stays on the list and `aria-activedescendant` names the active time.
|
|
140
|
+
|
|
141
|
+
### Keyboard
|
|
142
|
+
|
|
143
|
+
| Key | Where | Action |
|
|
144
|
+
| ------------------------------------------------ | ----- | -------------------------------------------------------- |
|
|
145
|
+
| <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> | Field | Moves through the parts, then to the clock button |
|
|
146
|
+
| <kbd>0</kbd>–<kbd>9</kbd> | Part | Types into the part; moves on once no further digit fits |
|
|
147
|
+
| <kbd>A</kbd> / <kbd>P</kbd> | AM/PM | Sets the day period (the first letter of its name) |
|
|
148
|
+
| <kbd>ArrowUp</kbd> / <kbd>ArrowDown</kbd> | Part | Next / previous value, wrapping |
|
|
149
|
+
| <kbd>ArrowRight</kbd> / <kbd>ArrowLeft</kbd> | Part | Next / previous part (swapped under `dir="rtl"`) |
|
|
150
|
+
| <kbd>Backspace</kbd> / <kbd>Delete</kbd> | Part | Clears the part |
|
|
151
|
+
| <kbd>:</kbd> | Part | Moves on to the next part |
|
|
152
|
+
| <kbd>Alt</kbd>+<kbd>ArrowDown</kbd> | Part | Opens the list |
|
|
153
|
+
| <kbd>ArrowDown</kbd> / <kbd>ArrowUp</kbd> | List | Next / previous time (skipping disabled ones) |
|
|
154
|
+
| <kbd>Home</kbd> / <kbd>End</kbd> | List | First / last time |
|
|
155
|
+
| <kbd>Enter</kbd> / <kbd>Space</kbd> | List | Picks the active time and closes the list |
|
|
156
|
+
| <kbd>Escape</kbd> | List | Closes the list, focus back on the button |
|
|
157
|
+
| <kbd>Tab</kbd> | List | Closes the list and moves on |
|
|
158
|
+
|
|
159
|
+
## Inputs
|
|
160
|
+
|
|
161
|
+
| Name | Type | Default | Description |
|
|
162
|
+
| ---------------- | ------------------------------------------------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
163
|
+
| `min` | `string \| undefined` | `undefined` | The earliest valid time ('HH:mm'): earlier times show an error and are disabled in the list. Set by `[formField]`; do not bind it yourself. |
|
|
164
|
+
| `max` | `string \| undefined` | `undefined` | The latest valid time ('HH:mm'): later times show an error and are disabled in the list. Set by `[formField]`; do not bind it yourself. |
|
|
165
|
+
| `step` | `number` | `30` | Minutes between the times of the list, from midnight (1–720). |
|
|
166
|
+
| `seconds` | `boolean` | `false` | Adds a seconds part; the value becomes 'HH:mm:ss'. |
|
|
167
|
+
| `hourCycle` | `BfTimeInputHourCycle \| undefined` (`'h12' \| 'h23' \| undefined`) | `undefined` | Forces a 12-hour (`h12`, with AM/PM) or 24-hour (`h23`) clock; unset follows the locale. |
|
|
168
|
+
| `picker` | `BfTimeInputPicker` (`'list' \| 'none'`) | `'list'` | `list` adds the button that opens the list of times; `none` keeps the field typed only. |
|
|
169
|
+
| `native` | `boolean` | `false` | Renders the platform's time picker (`input type="time"`) under a coarse pointer (touch). |
|
|
170
|
+
| `locale` | `string` | `''` | BCP 47 locale of the part order, the separators, the clock and the AM/PM names; empty follows the document's `lang`, else English. |
|
|
171
|
+
| `size` | `BfSize \| undefined` (`'sm' \| 'md' \| 'lg' \| undefined`) | `undefined` | Height step: sm 32, md 40, lg 48 px; unset follows `provideBfDefaults()` (`fieldSize`, `md` by default). |
|
|
172
|
+
| `label` | `string` | `''` | Visible label above the field. |
|
|
173
|
+
| `ariaLabel` | `string` | `''` | Accessible name when no visible label is rendered. Ignored when `label` is set. |
|
|
174
|
+
| `ariaLabelledby` | `string` | `''` | Id(s) of the element(s) that name the field when no visible label is rendered. Ignored when `label` is set. |
|
|
175
|
+
| `helpText` | `string` | `''` | Helper text under the field. |
|
|
176
|
+
| `errorMessage` | `string` | `''` | Explicit error text; takes precedence over the field's own errors and Signal Forms errors. |
|
|
177
|
+
| `disabled` | `boolean` | `false` | Disables the field and its list button. Set by `[formField]`; do not bind it yourself. |
|
|
178
|
+
| `readonly` | `boolean` | `false` | Shows the time but blocks changes; the parts stay focusable and expose `aria-readonly`. Set by `[formField]`; do not bind it yourself. |
|
|
179
|
+
| `required` | `boolean` | `false` | Marks the field required. Set by `[formField]`; do not bind it yourself. |
|
|
180
|
+
| `invalid` | `boolean` | `false` | Field validity from Signal Forms. Set by `[formField]`; do not bind it yourself. |
|
|
181
|
+
| `touched` | `boolean` | `false` | Whether the user has left the field. Set by `[formField]`; do not bind it yourself. |
|
|
182
|
+
| `errors` | `readonly ValidationError.WithOptionalFieldTree[]` | `[]` | Validation errors from Signal Forms. Set by `[formField]`; do not bind it yourself. |
|
|
183
|
+
| `name` | `string` | `''` | Native name attribute of the platform input (`native` under a coarse pointer). Set by `[formField]`; do not bind it yourself. |
|
|
184
|
+
| `id` | `string` | `generated` | Field id; generated when not set. |
|
|
185
|
+
| `value` | `string \| null` | `null` | The time as an ISO string ('HH:mm', 'HH:mm:ss' with `seconds`), `null` while empty or incomplete; two-way bindable and bound by `[formField]`. Two-way: `[(value)]`. |
|
|
186
|
+
|
|
187
|
+
## Outputs
|
|
188
|
+
|
|
189
|
+
| Name | Payload | Description |
|
|
190
|
+
| --------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
|
191
|
+
| `changed` | `string \| null` | Emitted with the new time right after a user change: `null` once it is cleared or incomplete. |
|
|
192
|
+
| `touch` | `void` | Emitted when focus leaves the field (not into its list), so Signal Forms marks it touched. Set by `[formField]`; do not bind it yourself. |
|
|
193
|
+
|
|
194
|
+
## Methods
|
|
195
|
+
|
|
196
|
+
### `focus(options?: FocusOptions): void`
|
|
197
|
+
|
|
198
|
+
Focuses the first empty part (else the first part); Signal Forms calls it from
|
|
199
|
+
`focusBoundControl()`.
|
|
200
|
+
|
|
201
|
+
| Parameter | Type | Description |
|
|
202
|
+
| ---------- | -------------- | --------------------------------------------------------------------- |
|
|
203
|
+
| `options?` | `FocusOptions` | Passed on to the native `focus()`, such as `{ preventScroll: true }`. |
|
|
204
|
+
|
|
205
|
+
### `openPicker(): void`
|
|
206
|
+
|
|
207
|
+
Opens the list of times; does nothing without a picker, or while disabled or read-only.
|
|
208
|
+
|
|
209
|
+
### `closePicker(): void`
|
|
210
|
+
|
|
211
|
+
Closes the list of times, focus back on its button; does nothing while it is closed.
|
|
212
|
+
|
|
213
|
+
## Notes
|
|
214
|
+
|
|
215
|
+
### Do
|
|
216
|
+
|
|
217
|
+
- Give the field a `label`.
|
|
218
|
+
- Store the value as the ISO time it is, and combine it with a date only where you know the time zone.
|
|
219
|
+
- Pick a `step` that matches the slots you accept (15 for appointments).
|
|
220
|
+
|
|
221
|
+
### Don't
|
|
222
|
+
|
|
223
|
+
- Don't use it for a duration; a time input is a time of day.
|
|
224
|
+
- Don't add `seconds` for times people pick by the quarter hour.
|
|
225
|
+
- Don't format the value yourself for display in the field; it follows the locale's clock.
|
|
226
|
+
|
|
227
|
+
## Migration notes
|
|
228
|
+
|
|
229
|
+
### 0.3.1
|
|
230
|
+
|
|
231
|
+
New `bf-time-input` in `@bitforgehq/ui/overlay`: a time-of-day field typed as hour and minute
|
|
232
|
+
(and second with `seconds`), with an AM/PM part under a 12-hour clock. The clock follows the
|
|
233
|
+
locale; `hourCycle` (`BfTimeInputHourCycle`: `'h12'` or `'h23'`) forces one. The value is an ISO
|
|
234
|
+
time on a 24-hour clock (`'HH:mm'`, `'HH:mm:ss'` with seconds). `picker` (`BfTimeInputPicker`:
|
|
235
|
+
`'list'`, the default, or `'none'`) adds a button that opens a listbox of times every `step`
|
|
236
|
+
minutes (30 by default), scrolled to the value; `min` and `max` disable the times outside them
|
|
237
|
+
and show an error for a typed one. `native` renders `input type="time"` under a coarse pointer.
|
|
238
|
+
New label keys `timeInputHour`, `timeInputMinute`, `timeInputSecond`, `timeInputDayPeriod`,
|
|
239
|
+
`timeInputOpenList`, `timeInputInvalid`, `timeInputMin` and `timeInputMax`, and a
|
|
240
|
+
`BfTimeInputHarness` in `@bitforgehq/ui/testing` (`setValue(text)`, `pickTime(time)`).
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
# bf-toolbar
|
|
2
|
+
|
|
3
|
+
Dense row of search, filters, view switch, result count and actions
|
|
4
|
+
|
|
5
|
+
`BfToolbarComponent` from `@bitforgehq/ui` · Actions · since 0.3.1
|
|
6
|
+
|
|
7
|
+
## Example
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { Component, computed, signal } from '@angular/core';
|
|
11
|
+
import {
|
|
12
|
+
BfButtonComponent,
|
|
13
|
+
BfInputComponent,
|
|
14
|
+
BfSegmentedComponent,
|
|
15
|
+
BfSelectComponent,
|
|
16
|
+
BfToolbarComponent,
|
|
17
|
+
type BfSegmentOption,
|
|
18
|
+
type BfSelectOption,
|
|
19
|
+
} from '@bitforgehq/ui';
|
|
20
|
+
|
|
21
|
+
type ProjectStatus = 'active' | 'paused' | 'done';
|
|
22
|
+
type StatusFilter = ProjectStatus | 'all';
|
|
23
|
+
type View = 'list' | 'grid';
|
|
24
|
+
|
|
25
|
+
interface Project {
|
|
26
|
+
readonly name: string;
|
|
27
|
+
readonly status: ProjectStatus;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The bf-toolbar overview.
|
|
32
|
+
*
|
|
33
|
+
* Live demo of `bf-toolbar`: a search field and a status filter narrow a list of projects, a
|
|
34
|
+
* view switch lays it out as a list or a grid, the toolbar's status announces the count, and a
|
|
35
|
+
* primary action adds a project. Every control in the row is `sm` from the toolbar's density.
|
|
36
|
+
*/
|
|
37
|
+
@Component({
|
|
38
|
+
selector: 'app-example-toolbar-overview',
|
|
39
|
+
imports: [
|
|
40
|
+
BfButtonComponent,
|
|
41
|
+
BfInputComponent,
|
|
42
|
+
BfSegmentedComponent,
|
|
43
|
+
BfSelectComponent,
|
|
44
|
+
BfToolbarComponent,
|
|
45
|
+
],
|
|
46
|
+
template: `
|
|
47
|
+
<div class="flex flex-col gap-3">
|
|
48
|
+
<bf-toolbar label="Project filters" [status]="count()">
|
|
49
|
+
<bf-input
|
|
50
|
+
type="search"
|
|
51
|
+
ariaLabel="Search projects"
|
|
52
|
+
placeholder="Search projects"
|
|
53
|
+
class="w-full sm:w-56"
|
|
54
|
+
[(value)]="query"
|
|
55
|
+
/>
|
|
56
|
+
<bf-select
|
|
57
|
+
ariaLabel="Status"
|
|
58
|
+
class="w-full sm:w-40"
|
|
59
|
+
[options]="statuses"
|
|
60
|
+
[(value)]="filter"
|
|
61
|
+
/>
|
|
62
|
+
<bf-segmented bfSlot="view" label="View" [options]="views" [(value)]="view" />
|
|
63
|
+
<bf-button bfSlot="actions" (clicked)="add()">New project</bf-button>
|
|
64
|
+
</bf-toolbar>
|
|
65
|
+
<ul [class]="view() === 'grid' ? 'grid grid-cols-2 gap-2' : 'flex flex-col gap-1'">
|
|
66
|
+
@for (project of visible(); track project.name) {
|
|
67
|
+
<li class="bg-surface rounded-field text-body px-3 py-2">{{ project.name }}</li>
|
|
68
|
+
}
|
|
69
|
+
</ul>
|
|
70
|
+
<p data-status aria-live="polite" class="text-caption text-text-muted">{{ status() }}</p>
|
|
71
|
+
</div>
|
|
72
|
+
`,
|
|
73
|
+
})
|
|
74
|
+
export class ToolbarOverviewExampleComponent {
|
|
75
|
+
protected readonly query = signal('');
|
|
76
|
+
protected readonly filter = signal<StatusFilter | null>('all');
|
|
77
|
+
protected readonly view = signal<View | null>('list');
|
|
78
|
+
protected readonly projects = signal<readonly Project[]>([
|
|
79
|
+
{ name: 'Atlas rebrand', status: 'active' },
|
|
80
|
+
{ name: 'Billing revamp', status: 'active' },
|
|
81
|
+
{ name: 'Carbon audit', status: 'paused' },
|
|
82
|
+
{ name: 'Docs refresh', status: 'done' },
|
|
83
|
+
{ name: 'Onboarding flow', status: 'active' },
|
|
84
|
+
]);
|
|
85
|
+
|
|
86
|
+
protected readonly statuses: BfSelectOption<StatusFilter>[] = [
|
|
87
|
+
{ value: 'all', label: 'All statuses' },
|
|
88
|
+
{ value: 'active', label: 'Active' },
|
|
89
|
+
{ value: 'paused', label: 'Paused' },
|
|
90
|
+
{ value: 'done', label: 'Done' },
|
|
91
|
+
];
|
|
92
|
+
protected readonly views: BfSegmentOption<View>[] = [
|
|
93
|
+
{ value: 'list', label: 'List' },
|
|
94
|
+
{ value: 'grid', label: 'Grid' },
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
protected readonly visible = computed(() => {
|
|
98
|
+
const query = this.query().trim().toLowerCase();
|
|
99
|
+
const filter = this.filter() ?? 'all';
|
|
100
|
+
return this.projects().filter(
|
|
101
|
+
(p) =>
|
|
102
|
+
(filter === 'all' || p.status === filter) &&
|
|
103
|
+
(!query || p.name.toLowerCase().includes(query)),
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
protected readonly count = computed(() => {
|
|
108
|
+
const n = this.visible().length;
|
|
109
|
+
return n === 1 ? '1 project' : `${n} projects`;
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
protected readonly status = computed(
|
|
113
|
+
() => `Showing ${this.visible().length} of ${this.projects().length} · ${this.view()} view`,
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
protected add(): void {
|
|
117
|
+
const next = this.projects().length + 1;
|
|
118
|
+
this.projects.update((list) => [...list, { name: `Project ${next}`, status: 'active' }]);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Overview
|
|
124
|
+
|
|
125
|
+
Row of search, filters, a view switch and trailing actions above a list or table. Every
|
|
126
|
+
control projected into it is `sm` unless it sets `size` itself.
|
|
127
|
+
|
|
128
|
+
A dense row above a list or table: the search field and filters, a view switch, a live result count and the trailing actions. `bf-toolbar` lays the row out, wraps it in a narrow column and makes every control in it `sm`; the controls themselves are ordinary `bf-input`, `bf-select`, `bf-chip-group`, `bf-segmented` and `bf-button`s.
|
|
129
|
+
|
|
130
|
+
## When to use
|
|
131
|
+
|
|
132
|
+
Use `bf-toolbar` on a desktop page for the controls that narrow or act on the collection below it: search, filters, list or grid, "New". Use [bf-action-bar](https://bitforge-ui.web.app/components/action-bar) instead for actions on a selection while it lasts. In the Capacitor shell, put the main action in [bf-header](https://bitforge-ui.web.app/components/header) actions and move filters into a [bf-sheet](https://bitforge-ui.web.app/components/sheet) instead.
|
|
133
|
+
|
|
134
|
+
## Layout
|
|
135
|
+
|
|
136
|
+
Project the search field and the filters as content, in the order they read. `bfSlot="view"` takes a view switch after them, and `bfSlot="actions"` the trailing actions, pushed to the inline end. Each field needs its own `ariaLabel` (or `label`), since the row shows no field labels. Bind `status` to the result count.
|
|
137
|
+
|
|
138
|
+
## Status
|
|
139
|
+
|
|
140
|
+
`status` renders a polite live region (`role="status"`) after the view switch, in muted caption text. Keep it short and countable ("24 results", "No matches"); it is announced each time it changes, without moving focus. The region is always in the DOM, so the first count after an empty one is announced too; while it is empty it takes no space.
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
import { Component, computed, signal } from '@angular/core';
|
|
144
|
+
import { BfChipComponent, BfChipGroupComponent, BfToolbarComponent } from '@bitforgehq/ui';
|
|
145
|
+
|
|
146
|
+
type Filter = 'all' | 'open' | 'done';
|
|
147
|
+
|
|
148
|
+
interface Task {
|
|
149
|
+
readonly title: string;
|
|
150
|
+
readonly done: boolean;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* A result count in the status line.
|
|
155
|
+
*
|
|
156
|
+
* The toolbar's `status` is a polite live region: picking a filter changes the count, and a
|
|
157
|
+
* screen reader announces it without moving focus.
|
|
158
|
+
*/
|
|
159
|
+
@Component({
|
|
160
|
+
selector: 'app-example-toolbar-status',
|
|
161
|
+
imports: [BfChipComponent, BfChipGroupComponent, BfToolbarComponent],
|
|
162
|
+
template: `
|
|
163
|
+
<bf-toolbar label="Task filters" [status]="count()">
|
|
164
|
+
<bf-chip-group label="Status" [(value)]="filter">
|
|
165
|
+
<bf-chip variant="ink" value="all">All</bf-chip>
|
|
166
|
+
<bf-chip variant="ink" value="open">Open</bf-chip>
|
|
167
|
+
<bf-chip variant="ink" value="done">Done</bf-chip>
|
|
168
|
+
</bf-chip-group>
|
|
169
|
+
</bf-toolbar>
|
|
170
|
+
`,
|
|
171
|
+
})
|
|
172
|
+
export class ToolbarStatusExampleComponent {
|
|
173
|
+
protected readonly filter = signal<Filter | null>('all');
|
|
174
|
+
private readonly tasks: readonly Task[] = [
|
|
175
|
+
{ title: 'Draft the brief', done: true },
|
|
176
|
+
{ title: 'Review the logo', done: false },
|
|
177
|
+
{ title: 'Book the venue', done: false },
|
|
178
|
+
];
|
|
179
|
+
|
|
180
|
+
protected readonly count = computed(() => {
|
|
181
|
+
const filter = this.filter() ?? 'all';
|
|
182
|
+
const n = this.tasks.filter((t) => filter === 'all' || t.done === (filter === 'done')).length;
|
|
183
|
+
return n === 1 ? '1 task' : `${n} tasks`;
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Wrapping
|
|
189
|
+
|
|
190
|
+
The row is a wrapping flex row with an 8 px gap between items and between rows. The status and the actions are one group that takes the rest of the last row: on a wide row the status follows the view switch and the actions sit at the inline end; when the group no longer fits beside the filters, it wraps onto a row of its own as a whole, with the status at the start and the actions at the end. So the actions never end up alone on a row, and they stay on the last filter row whenever the group fits there. Give a search field a width (`class="w-full sm:w-56"`) so it fills a phone-wide row and stays compact on a wide one.
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
import { Component } from '@angular/core';
|
|
194
|
+
import {
|
|
195
|
+
BfButtonComponent,
|
|
196
|
+
BfInputComponent,
|
|
197
|
+
BfSelectComponent,
|
|
198
|
+
BfToolbarComponent,
|
|
199
|
+
type BfSelectOption,
|
|
200
|
+
} from '@bitforgehq/ui';
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* A toolbar that wraps.
|
|
204
|
+
*
|
|
205
|
+
* In a narrow column the filters wrap onto new rows 8 px apart, and the status and the actions
|
|
206
|
+
* move down together: the count at the start of the last row, the actions at the inline end.
|
|
207
|
+
*/
|
|
208
|
+
@Component({
|
|
209
|
+
selector: 'app-example-toolbar-wrapping',
|
|
210
|
+
imports: [BfButtonComponent, BfInputComponent, BfSelectComponent, BfToolbarComponent],
|
|
211
|
+
template: `
|
|
212
|
+
<div class="max-w-sm">
|
|
213
|
+
<bf-toolbar label="Invoice filters" status="18 invoices">
|
|
214
|
+
<bf-input type="search" ariaLabel="Search invoices" placeholder="Search invoices" />
|
|
215
|
+
<bf-select ariaLabel="Customer" [options]="customers" value="all" />
|
|
216
|
+
<bf-select ariaLabel="Period" [options]="periods" value="month" />
|
|
217
|
+
<bf-button bfSlot="actions" variant="outline">Export</bf-button>
|
|
218
|
+
<bf-button bfSlot="actions">New invoice</bf-button>
|
|
219
|
+
</bf-toolbar>
|
|
220
|
+
</div>
|
|
221
|
+
`,
|
|
222
|
+
})
|
|
223
|
+
export class ToolbarWrappingExampleComponent {
|
|
224
|
+
protected readonly customers: BfSelectOption[] = [
|
|
225
|
+
{ value: 'all', label: 'All customers' },
|
|
226
|
+
{ value: 'acme', label: 'Acme' },
|
|
227
|
+
];
|
|
228
|
+
protected readonly periods: BfSelectOption[] = [
|
|
229
|
+
{ value: 'month', label: 'This month' },
|
|
230
|
+
{ value: 'year', label: 'This year' },
|
|
231
|
+
];
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Phone width
|
|
236
|
+
|
|
237
|
+
At phone width (in a desktop web app's narrow window or a narrow column) the search takes a row, the filters share the next, and the status and actions share the last row. A toolbar without actions keeps the status as a plain item after the filters.
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
import { Component, signal } from '@angular/core';
|
|
241
|
+
import {
|
|
242
|
+
BfButtonComponent,
|
|
243
|
+
BfInputComponent,
|
|
244
|
+
BfSelectComponent,
|
|
245
|
+
BfToolbarComponent,
|
|
246
|
+
type BfSelectOption,
|
|
247
|
+
} from '@bitforgehq/ui';
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* A toolbar at phone width.
|
|
251
|
+
*
|
|
252
|
+
* In a phone-wide column the search fills its own row, the two filters share the next one, and
|
|
253
|
+
* the status and the Refresh action share the last row: the count at the start, the action at the
|
|
254
|
+
* end, never alone on a row of its own.
|
|
255
|
+
*/
|
|
256
|
+
@Component({
|
|
257
|
+
selector: 'app-example-toolbar-narrow',
|
|
258
|
+
imports: [BfButtonComponent, BfInputComponent, BfSelectComponent, BfToolbarComponent],
|
|
259
|
+
template: `
|
|
260
|
+
<div class="max-w-xs">
|
|
261
|
+
<bf-toolbar label="Order filters" [status]="count()">
|
|
262
|
+
<bf-input
|
|
263
|
+
type="search"
|
|
264
|
+
ariaLabel="Search orders"
|
|
265
|
+
placeholder="Search orders"
|
|
266
|
+
class="w-full"
|
|
267
|
+
/>
|
|
268
|
+
<bf-select ariaLabel="Status" [options]="statuses" value="open" />
|
|
269
|
+
<bf-select ariaLabel="Period" [options]="periods" value="week" />
|
|
270
|
+
<bf-button bfSlot="actions" variant="outline" (clicked)="refresh()">Refresh</bf-button>
|
|
271
|
+
</bf-toolbar>
|
|
272
|
+
</div>
|
|
273
|
+
`,
|
|
274
|
+
})
|
|
275
|
+
export class ToolbarNarrowExampleComponent {
|
|
276
|
+
protected readonly count = signal('12 orders');
|
|
277
|
+
protected readonly statuses: BfSelectOption[] = [
|
|
278
|
+
{ value: 'open', label: 'Open' },
|
|
279
|
+
{ value: 'shipped', label: 'Shipped' },
|
|
280
|
+
];
|
|
281
|
+
protected readonly periods: BfSelectOption[] = [
|
|
282
|
+
{ value: 'week', label: 'This week' },
|
|
283
|
+
{ value: 'month', label: 'This month' },
|
|
284
|
+
];
|
|
285
|
+
|
|
286
|
+
protected refresh(): void {
|
|
287
|
+
this.count.set('12 orders, updated');
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
## Desktop
|
|
293
|
+
|
|
294
|
+
`bf-toolbar` is a desktop pattern. Every control and field projected into it resolves its unset `size` to `sm` (32 px tall), because its `providers` set `provideBfDefaults({ size: 'sm' })` for its content; a control that sets `size` itself keeps it, and nothing outside the toolbar changes ([density guide](https://bitforge-ui.web.app/density)). Hover, the pointer cursor and the `focus-visible` ring come from each control; the row adds none of its own. Under a fine pointer each control's visual box is its hit area; under a coarse pointer the `sm` touch extensions reach up to 7.5 px past the visual edge, which the 8 px gap keeps off the neighbouring row.
|
|
295
|
+
|
|
296
|
+
Place it directly under `bf-page-header` and above the `bf-table` or `bf-list` it filters, with the page's own vertical gap (`gap-4`) between them: the page header names the page and holds its page-level actions, the toolbar holds what narrows the collection, and the count in `status` describes the rows below.
|
|
297
|
+
|
|
298
|
+
## Theming
|
|
299
|
+
|
|
300
|
+
The row has no surface, border or radius of its own; it sits on the page background, and each projected control themes itself.
|
|
301
|
+
|
|
302
|
+
- Status text: `text-caption` in `text-muted`.
|
|
303
|
+
- Layout: `gap-2` (8 px) between items and rows, in logical properties, so under `dir="rtl"` the row mirrors and the actions sit at the left end.
|
|
304
|
+
|
|
305
|
+
## Accessibility
|
|
306
|
+
|
|
307
|
+
The host is `role="group"`, named by `aria-label` from `label` ("Project filters"). It is a group, not `role="toolbar"`: that role promises arrow-key movement between its controls with one tab stop, and the search field needs the arrow keys to move its caret, so each control keeps its own tab stop and its own keys. Without `label` the group is left unnamed rather than given a generic default: a page has one toolbar under a heading that already names the context, and a stock name such as "Toolbar" would add noise to every focus entry without telling the user anything. Pass a `label` when the page has more than one toolbar.
|
|
308
|
+
|
|
309
|
+
The `status` element is `role="status"` (an `aria-live="polite"` region), always rendered, so a changed count is read after the user's current input finishes. The toolbar sets no focus and handles no keys; <kbd>Tab</kbd> moves through the projected controls in DOM order (filters, view switch, actions).
|
|
310
|
+
|
|
311
|
+
## Inputs
|
|
312
|
+
|
|
313
|
+
| Name | Type | Default | Description |
|
|
314
|
+
| -------- | -------- | ------- | -------------------------------------------------------------------------------------- |
|
|
315
|
+
| `label` | `string` | `''` | The group's accessible name, such as "Project filters"; unnamed when empty. |
|
|
316
|
+
| `status` | `string` | `''` | The result count or a short state, such as "24 results", announced politely on change. |
|
|
317
|
+
|
|
318
|
+
## Slots
|
|
319
|
+
|
|
320
|
+
- `default`: The search field and the filters (bf-input, bf-select, bf-chip-group), in order.
|
|
321
|
+
- `view`: A view switch after the filters, such as a bf-segmented of list and grid.
|
|
322
|
+
- `actions`: The trailing actions, kept together at the inline end of the row, beside the status.
|
|
323
|
+
|
|
324
|
+
## Notes
|
|
325
|
+
|
|
326
|
+
### Do
|
|
327
|
+
|
|
328
|
+
- Name each field with `ariaLabel` (or a `label`), and keep the order: search, filters, view, actions.
|
|
329
|
+
- Bind `status` to the number of rows the filters leave, and update it as they change.
|
|
330
|
+
|
|
331
|
+
### Don't
|
|
332
|
+
|
|
333
|
+
- Don't put the page's title or page-level actions in it; they belong in `bf-page-header`.
|
|
334
|
+
- Don't use it in the Capacitor shell; use `bf-header` actions and a `bf-sheet` of filters.
|
|
335
|
+
|
|
336
|
+
## Migration notes
|
|
337
|
+
|
|
338
|
+
### 0.3.2
|
|
339
|
+
|
|
340
|
+
On a narrow row the actions no longer end up alone on a row of their own: the status and the
|
|
341
|
+
actions are now one group that takes the rest of the last row and wraps as a whole, with the
|
|
342
|
+
status at the start and the actions at the inline end. The wide layout is unchanged, and a
|
|
343
|
+
toolbar without actions keeps the status as a plain item of the row. New `toolbar/narrow`
|
|
344
|
+
example at phone width.
|
|
345
|
+
|
|
346
|
+
### 0.3.1
|
|
347
|
+
|
|
348
|
+
New `bf-toolbar` in `@bitforgehq/ui`: a wrapping `role="group"` row for the search field and
|
|
349
|
+
filters (default slot), a view switch (`bfSlot="view"`) and the trailing actions
|
|
350
|
+
(`bfSlot="actions"`, kept together at the inline end). `label` names the group; `status` renders
|
|
351
|
+
a polite `role="status"` live region for the result count. Its providers set
|
|
352
|
+
`provideBfDefaults({ size: 'sm' })`, so every projected control and field is `sm` unless it sets
|
|
353
|
+
`size` itself. Desktop only; in the Capacitor shell use `bf-header` actions and a `bf-sheet` of
|
|
354
|
+
filters.
|